Datum Work is a mining-work monitor for AlphaPool, PyBlock, XOR, and your own BLAKE2b block templates. The home dashboard brings these sources together while retaining their source and algorithm information. PyBlock defaults to its BLAKE2b WAVICLES endpoint at b.pyblock.xyz:23115; its SHA256 endpoint is a separate configuration described in the monitor guide.
This project is a fork of bboerst/stratum-work. The original Bitcoin Stratum dashboard remains available at /table. Credit for the original visualization idea belongs to 0xB10C, and for the upstream implementation to its contributors.
Use Node.js 22.14 or newer and Python 3.10 or newer. The monitor uses the Python standard library and needs no Python package installation. From this checkout:
cd web
npm ci
npx prisma generate
npm run devOpen localhost:3000. In a second terminal at the repository root, start the monitor:
python3 monitor/service.pyThe web app reads the monitor at http://127.0.0.1:8810 by default. For a different monitor address, set DATUM_MONITOR_URL when starting the web app. See monitor/README.md for pool endpoints, optional pool authorization, the SHA256 PyBlock override, and your own gateway template configuration.
After installing the web dependencies, you can also start both local services from the repository root:
./run-local.sh --check
./run-local.shThe helper checks dependencies and refuses to start when either port is occupied. It binds both services to loopback and stops them together on Ctrl+C. Set WEB_PORT=3001 or DATUM_MONITOR_PORT=8811 to use different ports. PYTHON_BIN and NODE_BIN can select installed runtimes. Stop any separately started web app or monitor before using the helper.
The legacy SHA256 view at /table requires the separate Bitcoin stack: configured collectors, RabbitMQ, MongoDB, and a Bitcoin node with RPC and ZMQ. It cannot show live or historical data from a web-only startup. The legacy stack and data flow are documented below.
From web/:
npm test
npx tsc --noEmit
npm run buildSee Helm deployment configuration for fork-specific image repositories and publishing settings.
The following architecture and Docker Compose instructions describe the inherited Bitcoin Stratum application. They are separate from the Datum Work monitor service and its BLAKE2b template source.
The inherited application visualizes mining.notify messages from Bitcoin Stratum pools and identifies the pools that mined confirmed blocks.
Stratum Work consists of three main components:
- Collector: Connects to Bitcoin mining pools via the Stratum protocol, captures
mining.notifymessages, and forwards them to RabbitMQ. - Backend: Processes Bitcoin blocks, identifies mining pools, and provides block data to the web application.
- Web Application: Displays real-time mining notifications and block information in an interactive interface.
- Multiple collector instances connect to different mining pools
- When a pool sends a
mining.notifymessage, the collector:- Stores the message in MongoDB
- Publishes the message to RabbitMQ
- The web application consumes these messages from RabbitMQ and displays them in real-time
- The backend processes new blocks from a Bitcoin node and identifies which pool mined each block
The Stratum protocol is used by mining pools to coordinate miners. The mining.notify message is particularly important as it contains the template for a new block that miners should work on. Each message contains:
- Job ID: A unique identifier for this mining job
- Previous Block Hash: The hash of the last block in the chain
- Coinbase (Part 1 & 2): The coinbase transaction split into two parts
- Merkle Branches: Hashes needed to construct the merkle root
- Version: Block version
- nBits: Target difficulty
- nTime: Current timestamp
- Clean Jobs: Boolean indicating if previous jobs should be discarded
A key design principle of Stratum Work is trustless data processing. While the server collects and streams the raw data, all decoding, formatting, and visualization is performed in the client browser.
The application provides a Server-Sent Events (SSE) endpoint:
GET /api/stream
This endpoint delivers the same real-time data that powers the web interface, allowing for custom integrations or alternative visualizations.
The collector performs minimal processing to maintain data integrity:
See collector/main.py:294-323 for the notification document creation function.
The web application performs extensive processing to make the data more readable and visually informative:
-
Coinbase Transaction Reconstruction:
See web/utils/formatters.ts:38-45 -
Coinbase Script ASCII Extraction:
See web/utils/bitcoinUtils.ts:48-76 -
Coinbase Output Analysis:
See web/utils/bitcoinUtils.ts:95-126 -
First Transaction Extraction:
See web/utils/bitcoinUtils.ts:129-139 -
Fee Rate Calculation:
See web/utils/bitcoinUtils.ts:142-168
The backend identifies which pool mined each block by analyzing the coinbase transaction:
See backend/main.py:815-890 for the block processing function.
The backend supports a pluggable analytics system that runs whenever a new block is processed. These analytics inspect the set of mining.notify templates for the block’s height and may emit structured findings that get saved on the block document and forwarded to RabbitMQ.
-
Location:
backend/analytics/prev_hash_divergence.py– detects pools advertising different previous block hashesinvalid_coinbase_no_merkle.py– flags templates with no merkle branches where the coinbase output exceeds the block subsidypool_identification.py– owns pool identification and thePoolsManager
-
Output shape (stored on
db.blocksand in the SSE payload):analysis.flags[]: list of small findings with compact metadata- Each item:
{ key: string, icon: string, details: {...} } - Example (prev-hash fork):
{ "key": "prev_hash_fork", "icon": "fork", "details": { "groups": [ { "prev_hash": "…", "pools": ["PoolA", "PoolB"] } ] } }
- Each item:
analysis.pool_identification: richer object for the winning pool{ mining_pool: {...}, method: "address|tag", addresses_considered: [] }
-
Create a new module in
backend/analytics/, e.g.my_new_analysis.py, and export a function that returns either a finding dict orNone:from typing import Any, Dict, List, Optional def analyze_my_feature(templates: List[Dict[str, Any]], logger) -> Optional[Dict[str, Any]]: # inspect templates … if not interesting: return None return { "key": "my_feature", "icon": "star", "details": { "…": "…" } }
-
Import and append it in
backend/main.pyinsiderun_block_analyses(...)(the function already collects and passes the MongoDBmining_notifyrecords for the block height):from analytics.my_new_analysis import analyze_my_feature … finding = analyze_my_feature(templates, logger) if finding: flags.append(finding)
-
Rebuild the backend image so the new module is included (the Dockerfile copies
backend/analytics/).
Guidelines:
- Keep findings small (store details needed to render UI, avoid large payloads).
- Prefer deterministic, height-scoped logic using the templates fetched for that block height.
- Use the shared logger for traceability; avoid printing secrets.
- Real-time display of
mining.notifymessages from Stratum pools - Customizable table columns for displaying relevant data
- Light and dark mode
- Live and historical data views
- MongoDB integration for data storage and retrieval
- Most 'work' is done client-side for trustless data processing
- Raw data access via
/api/streamendpoint
To simplify local development and testing, you can use docker-compose to run all the components on your machine. This allows you to quickly spin up RabbitMQ, MongoDB, a web application container, and one or more collector containers.
-
Open your Datum Work checkout:
cd datum-work -
Customize collectors:
In the
./docker-compose.ymlfile, you will see a collector-base service along with one or more collectors (e.g., collector-f2pool). Each collector points to a specific Stratum pool. To add more pools, just duplicate one of the collector services and update the --pool-name and --url fields:collector-newpool: <<: *collector-base command: > /usr/local/bin/python main.py --pool-name "NewPool" --url "stratum+tcp://newpool.com:3333" --userpass "someuser:somepass" --rabbitmq-host "rabbitmq" --rabbitmq-username "mquser" --rabbitmq-password "mqpassword" --db-url "mongodb" --db-name "stratum-logger" --db-username "mongouser" --db-password "mongopassword" --log-level "DEBUG"
The <<: *collector-base line pulls in all the shared environment variables and dependencies. All you need to do is adjust pool-specific configuration.
-
Build and start the services:
docker-compose up --build
This command will:
- Build the images for the webapp and collector services.
- Start RabbitMQ, MongoDB, and your configured collectors.
- Start the web application.
Once everything is running, the web application should be accessible at http://localhost:3000/table.
-
View Logs:
As everything starts, you can see logs in the terminal where you ran docker-compose up.These logs will show connections to pools,RabbitMQ events, and requests to the webapp.
-
Stop the services:
To stop and remove the containers, press
Ctrl + Cin your terminal. To remove containers, networks, and images created by docker-compose, run:docker-compose down
To also remove the mongodb volume:
docker-compose down -v