This is P2Block's fork of iohzrd/ratum,
the Rust DATUM gateway for BLAKE2b (BTCB2) hardware. It is what runs behind P2Block's hosted
stratum endpoints. The fork adds one feature, solo mode, and otherwise tracks upstream.
Branch p2block carries the fork; main mirrors upstream.
Upstream ratum-gateway has two ways to build a coinbase: the split the pool dictates over
DATUM (pooled), and mining.pool_address for everything (non-pooled). Solo mode is a third:
every connected miner works on a coinbase that pays the miner's own address the whole block
reward minus a pool fee, the way ckpool-style solo pools do. Whoever finds the block gets paid
directly by the coinbase; the gateway operator never holds funds.
"datum": { "pool_host": "" },
"mining": { "pool_address": "<fee address>", "solo": true, "solo_fee_bps": 100,
"solo_fee_address": "" }mining.solo(defaultfalse) turns it on. It needsdatum.pool_hostempty: solo work is built locally from the node's template, never from a pool assignment.mining.solo_fee_bps(0..10000, default 0) is taken off the miner's output and paid tomining.solo_fee_address, or tomining.pool_addresswhen that is empty. A fee of 0 builds a single-output coinbase.stratum.require_address_usernameis forced on: the username's address part is the payout, so a worker that authorizes without a payable address is refused.
How it works inside the gateway: each template is published as one job, and the job keeps a
per-address table of coinbases (ids 2..0xFE, so up to 253 distinct payout addresses per
template; more than that are refused work until the next template). A miner's mining.notify
carries its own coinbase id, a submit is only accepted on an id that belongs to the miner's
address, and a job is held back until the miner has authorized (before that there is no
address to build a coinbase for). Everything else, vardiff, bans, the stats API and the
status page, is unchanged; stats.json gains "solo": {"fee_bps", "fee_address"} and the
status line reads Solo Mode.
The upstream README follows.
ratum-gateway builds block templates from a local Knots node, serves version 2 (164-byte) headers to
BLAKE2b hardware over the Siacoin dialect of Stratum v1, takes the coinbase payout split from
the pool over DATUM, and submits blocks to the node.
It reimplements the C gateway at https://github.com/CONVOYMining/datum_gateway and reads its configuration file unchanged; the wire formats are the C gateway's byte for byte.
cargo run --bin ratum-gateway -- -c gateway.json
or
cargo build --workspace --release
target/release/ratum-gateway -c gateway.json
or, if you're using the release
ratum-gateway -c gateway.json
The file is the C gateway's JSON schema with the same defaults. Required: bitcoind.rpcurl
with rpcuser/rpcpassword or rpccookiefile, and mining.pool_address. The node's
template decides when BLAKE2b (version 2) headers apply: until it lists the !blake2b rule
no work is served. mining.blake2b_activation_height and mining.blake2b_headline are
ignored. RUST_LOG overrides logger.log_level_console.
-
SIGUSR1 is a block notification, as in C (
blocknotify=kill -USR1 <pid>);/NOTIFYon the API port does the same over HTTP. Unix only. -
api.miner_listen_portdefaults to8000and serves one endpoint, the miner lookupGET /?addr=<address>(the C gateway leaves the lookup off). It answers JSON and is unauthenticated: it reports only what the given address is already mining under. The page that used to render it now lives in a separate frontend project. -
datum.pool_url(not a C key; empty by default) names the pool's web page, and the status page links the pool host to it when it is set. -
/clientsand/coinbaserare not served:/loginprompts forapi.admin_password, after which the status page renders both tables from/stats.json. Authentication is HTTP Basic, not Digest: keep the API behind TLS or on a private interface./cmdtakes form fields with the page's token and is refused without an admin password. -
/configis the settings page. It requiresapi.admin_password; saving requiresapi.modify_conftoo. A save writes the edited keys into the configuration file (the other keys and the key order are kept), validates it as at startup, and restarts the gateway on the same command line to apply it: every change restarts, where the C gateway applies some without one. The field names and thepool_host(old)convention are the C gateway's;datum.pool_url, the gateway fee, the stratum port,stratum.vardiff_minandstratum.require_address_usernameare editable in addition to the C page's fields. -
A block share is charged the gateway fee like any other share when it passes the share checks (the C gateway exempts it); a block a check refuses is still sent under the miner's name.
-
One thread per stratum connection;
stratum.max_clientslimits the total and the per-thread settings size the duplicate-share table and share queue.empty_threaddisconnects every client;/threadsis not served. -
The extranonce1 session id is the 32-bit connection counter, so it never repeats for a live connection.
-
A new tip builds three immutable jobs (empty, priority, coinbaser) where C rewrites one, so
datum.protocol_job_slotsmust leave two extra slots. -
The type 2 coinbase puts every output after the OP_RETURN extranonce output and keeps that output with an empty split; it pays the same, the txid differs from C's.
-
mining.pool_addressanddatum.pool_pubkeyare checked at startup. -
Log level 5 keeps errors; higher silences the sink. Timestamps are UTC.
-
Every message to the pool is padded, the block-transactions response included.
-
A refused template is logged once per reason.
-
Version 3 protocol (
datum.protocol_v3, the CONVOYMining gateway): the gateway commits its work to the pool's anti-block-withholding assignment and sends the slot with every share, but retains no proofs and audits no reveal (the pool relays every block), sends no bulk-framed replies (a parent fetch, 0x50 0x14, is served from its node in one frame), and logs a migration request (0xA4) without following it. While the pool has announced no active assignment it serves no work (the C gateway builds solo work then). On a disconnect it discards its queued and unanswered shares and replays none, keeping only the resume token; the shares its miners submit before the next session holds an assignment wait for it, and are sent when the pool resumed the session (their assignment is announced again) and discarded when it did not.
Not served: the PROXY protocol (stratum.trust_proxy), daily rotation
and SIGHUP (logger.log_rotate_daily; the file is held open, so rotate it with logrotate's
copytruncate), the
open-file limit warning, datum.always_pay_self, the per-client pacing of job updates, the
testnet fast-forward, --help, --example-conf, --test and /assets. Set values among
these are reported at startup.
The coinbase a pooled job commits to carries every output the pool dictated that the
block has room for. Under the version 2 header the mining machine never receives the
coinbase: the bytes it is sent (CBlockHeader::GetHash) are a fixed 35-byte coinb1
(three zero bytes and H2, the commitment to the header's first stage, which carries the
merkle root) and the 16-byte extranonce the header carries, so a coinbase of two
outputs and one of two thousand give a miner the same job. The size classes the C gateway
builds per miner (its Antminer-safe default holds about 17 outputs) exist because SHA256d
miners reconstruct and hash the coinbase; this gateway builds one pooled coinbase and one
subsidy-only coinbase per job, and serves the pooled one to every miner.
What bounds it is the block: the weight limit (4,000,000; 800,000 while RDTS is active,
every block from the fork height until the parent's median-time-past reaches
2027-09-01), the sigop limit (80,000; a legacy P2PKH output costs four, a segwit output
none), and, while RDTS is active, output scripts of at most 34 bytes. The gateway sizes the
coinbase to the template's sizelimit, weightlimit and sigoplimit less its
transactions, and at 33,791 bytes in all, the largest coinbase section the pool accepts
(sized to the 512-output, 32,767-byte split a coinbaser response carries). The room a template leaves is the node's
-blockreservedweight (8,000 by default, about 40 taproot outputs or 55 P2WPKH) once transactions fill it, so a
node serving a pool with many identities is run with more: about 172 weight units per
output (a taproot output; 124 for P2WPKH) plus about 1,400 for the rest of the coinbase. The pool dictates at most 512
outputs (the DATUM coinbaser cap), and records what a coinbase leaves out as owed (see
"Owed blocks" under Prime).
RATUM Prime is a DATUM pool for the Bitcoin Knots BLAKE2b hardfork chain, written in Rust. Gateways beside the miners' nodes build the templates and serve stratum; the pool dictates where the coinbase pays, verifies the shares and relays the blocks.
The workspace holds the core library (the protocol, the version 2 header, the RPC client and
the code the binaries share), ratum-prime (the pool), ratum-gateway (a reimplementation of
the CONVOY DATUM Gateway, see Gateway)
and sia-test-miner (a CPU miner that mines against a gateway). The header hash and share
format are byte-coupled between the pool and the gateway, so they are one release.
cargo build --workspace --release # target/release/ratum-prime, ratum-gateway, sia-test-miner
cargo test --workspace
cargo test --workspace --release -- --ignored # searches ~2^32 hashes for the test nonces
e2e/full_stack.sh # the activation block
e2e/multi_miner.sh # three miners, two gateways: credit and payout split
e2e/gateway_fee.sh # a gateway charging a fee beside one charging none
core/tests/header_vectors.rs reproduces the five version 2 header vectors in
core/tests/data/block_header_v2.json, taken from the C implementation: the serialization,
the tagged SHA-256 chain, the BLAKE2b work root, the ASIC input of each of the four profiles
and the XOR mask. core/tests/decoders.rs feeds every decoder random and damaged input and
requires that none panics and that whatever decodes re-encodes to a fixed point.
The e2e scripts need a Knots build with the BLAKE2b change (BITCOIND, BITCOIN_CLI);
DATUM_GATEWAY runs another gateway build instead of this workspace's.
The gateway GitHub Actions workflow builds ratum-gateway for x86_64 and aarch64 Linux
(static musl) and x86_64 Windows on every push and pull request (each an artifact of the
run) and attaches the archives and their SHA-256 sums to a release on a v* tag.
git config core.hooksPath .githooks enables the pre-commit hook that bumps the workspace
version's patch component (and Cargo.lock) on every commit; a reword-only amend, a commit
that already changes the version line, and NO_BUMP=1 are left alone.
Every setting is a flag; --config, or ratum.toml in --data-dir, may hold them under the
flags' names without the dashes, and a flag given as well overrides the file.
rpc = "http://127.0.0.1:8332" # the node, on this host or a private link
rpc-user = "ratum"
rpc-pass = "..." # or --rpc-cookie <file>
min-diff = 16384 # smallest share difficulty credited, a power of two
min-payout = 546 # smallest output written; a miner under it leaves the splitRUST_LOG selects the level (info default; debug adds every frame and share).
--allow-agent (comma-separated prefixes, e.g. ratum-gateway/) refuses at hello any
gateway whose user agent matches none of them; empty (the default) accepts every agent.
The agent is self-reported: this refuses builds known to mishandle the coinbaser (their
blocks pay no split), it does not authenticate anyone.
--require-split (true, the default, or false) refuses with reject code 43 (NoSplit)
a share whose job names a coinbaser response this pool sent while its coinbase pays none of
that response's outputs, once 10 seconds have passed since the response: the C gateway
serves its outputs-free coinbase 0 to a miner it notifies while the job's coinbaser is
awaited (a connect, a quick difficulty change, its 5 second timeout), and that miner holds
it until its next notify. A job naming no coinbaser (id 0: the job needed none, or the
request was refused or timed out), a response with no outputs, and subsidy-only work are
exempt, so the check covers builds that fetch the split and then mine coinbase 0; a share
paying any part of the split passes, and so does a block, whose value reached the pool's
script and is recorded as owed. Like --allow-agent, it is a check on misbuilt gateways,
not authentication: a build that never requests a coinbaser is not refused.
--require-v3 refuses at hello any gateway that does not use the version 3 protocol (its
hello carries no DRS extension). Off, the default, serves version 1 and version 3 gateways;
a version 1 client computes true block hashes and so can withhold blocks selectively. On,
every connection is under an anti-block-withholding assignment and that is no longer
possible; every gateway not yet on version 3 is refused. ratum-gateway sends a version 3
hello by default (datum.protocol_v3) and, when the pool responds with a version 1
configuration, runs that session under version 1.
A version 3 session's anti-block-withholding slots rotate on a new tip (once the active
slot is a quarter of --abw-reveal-after old), after 16384 shares, and after 10 minutes. A
retired slot's key is revealed --abw-reveal-after seconds (1 to 600, default 300) after its
retirement, not at the next rotation: the gateway audits every proof it retained on the slot
the moment it processes the reveal, so the reveal must come after the last share the gateway
can still submit on the slot's jobs (its stale-share rule allows share_stale_seconds + work_update_seconds, 160 s by default and 270 s at most; the default covers the most), and
it is sent only once every share received before it has been answered. The gateway holds one
proof per share until the reveal, in a cache of 65536, so the delay bounds the share rate one
gateway can sustain: about 160 shares per second at the default, 270 at a delay of 180 (which
covers the C default window only). A share on a revealed slot is
refused (its key is public) but still rebuilt with that key for its exact reference and its
receipt. When the connection closes the pool keeps the session for an hour under the
gateway's signing key, so a gateway that reconnects with its resume token continues the same
slots and the shares it replays verify; the reveals it may not have received are sent again
once the delay has passed from the resume, after its replayed shares are answered. A pool
restart declines every resume. Every share that is a block by the node's target or by its
job's own nbits (the measure of the gateway's reveal audit) gets a receipt, relayed or not.
Every accepted share is written to a redb database before
it is credited: --ledger names the file, --data-dir puts <chain>.redb inside, with
neither the window is in memory only. --ledger-keep <n> keeps the newest n × 2^20 shares.
A ledger is stamped with the node's chain and refused on another chain.
A payout is measured over the most recent shares whose difficulties sum to --window times
the network difficulty (8, OCEAN's TIDES rule), never below --window-floor. At the BLAKE2b
activation height Knots resets the target to the previous target shifted left by
Blake2bTargetShift bits (22 on mainnet, 20 elsewhere), so set --window-floor to hold
the intended span of work and keep the whole ledger across the fork.
One ledger serves every gateway; a block found by any pays the miners of all, in proportion
to their work in the window. A miner's identity is its stratum username up to the first .,
and it must be an address the node's validateaddress accepts whose script fits a coinbase
output (34 bytes, or 83 beginning OP_RETURN); other shares are rejected with BadUsername.
An identity past the 512 outputs a gateway accepts, under --min-payout, or unpayable when
the split is built is left out and its amount goes to the pool's payout script.
--fee-bps (0 to 100, default 0) is deducted from the coinbase before the split and paid to
the pool's payout script as the remainder.
Whatever a block's coinbase pays to the pool's payout script beyond the operator fee is owed
to the window, and the pool records it in the ledger's owed table at acceptance. Two cases
produce it. A coinbase that leaves dictated outputs out, paying their value to the pool's
script as its remainder: a gateway with less room than the split (a C gateway's size class,
this gateway before 0.1.23 with its 17-output default class, or any gateway on a block its
transactions fill; see "Coinbase size" under Gateway). The record names exactly those
outputs, from the split the pool dictated for the job. The other case is a coinbase that
pays the window nothing on a job with no recorded split (a subsidy-only job, served in the
interval between a tip change and the next coinbaser split); the record is the split a
coinbaser at that moment would have dictated, minus the operator fee. The amounts are logged, shown in the stats page's block table ("in
coinbase; X owed to N", or "owed by pool") and summed in its banner, and included in
/stats.json under owed. Settlement is an ordinary transaction from the operator's
wallet; afterwards ratum-prime --settle-block <block-hash> (with --ledger or
--data-dir, pool stopped) marks the record settled, and --settle-block list prints every
record. A recorded block that was rejected or orphaned (the pool's payout script never
received its value) is removed with --void-block <block-hash>, and --record-owed <block-hash> --owed <identity>=<sats> ... adds a record from command-line values for a block in the
history that has none.
--stats-listen <address> serves one endpoint, the read-only snapshot at /stats.json;
every other path is a 404. It carries the tip, the coinbase value, the fee, the connected gateways, the build (--version prints the
same string), an approximate hashrate (accepted-share difficulty over the last 10 minutes,
at 2^32 hashes per difficulty unit, for the pool and per miner) and each miner's share of the
window with payable, unpayable_reason and tag (the secondary coinbase tag of the
miner's newest share in the window, the gateway's mining.coinbase_tag_secondary). Every
accepted block is recorded in the ledger's blocks table and listed with its coinbase
amounts, finder, and the secondary coinbase tag its coinbase carried; from the record and
a cumulative work counter it derives a luck figure (blocks found over blocks
expected), and from the observed block spacing an expected time to the pool's next block and
the next difficulty adjustment (height, countdown, estimated factor). It also carries the
DATUM address, the public key and the values a datum_gateway config block needs to point a
gateway at the pool; --advertise-address host[:port] sets the address when the public one
differs. --public-gateway <url> names a gateway that accepts miners who do not run their
own (a value without a scheme is read as https://); unset, the field is null. It is
unauthenticated: bind it to 127.0.0.1 unless it is behind a reverse proxy.
The response carries X-Robots-Tag: noindex, so the snapshot is not a search result of its
own. Rendering it is the job of a separate frontend project, which serves the snapshot from
its own origin so the browser makes no cross-origin request.
https://github.com/OCEAN-xyz/datum_gateway https://github.com/SiaMining/Stratum/blob/master/Stratum.md bitcoinknots/bitcoin#359 https://ocean.xyz/docs/datum https://ocean.xyz/docs/tides