Stratum is the protocol that Bitcoin miners use to communicate with Bitcoin mining pools. The mining pool provides the miners with hash puzzles that correspond to new Bitcoin blocks. The miners respond with shares, which are solutions to the hash puzzle at a much lower difficulty. The mining pool adjusts the difficulty based on the hashpower of the miners, which it can estimate from the frequency of shares submitted. The miners are paid by the total difficulty for all shares submitted. Eventually one of these shares will solve the block and the mining pool gets paid. The miner has no reason not to submit this share because he can't use it for his own purposes but to him it's worth no more than any other share.
Stratum can also be used with the Boost POW protocol, a method of buying proof-of-work from miners.
Stratum is poorly documented. We have relied on three sources to produce the specification given here. This document unravels all clues that are spread out among our sources.
- Stratum Mining Protocol v2.1 - This document is a draft of a version of Stratum that would later become Stratum with extensions. It contains information about the base protocol as well as information that was later superseded by the next source.
- Stratum-Extensions -
A description of Stratum extensions, including
mining.configure
and extension version_rolling, which is necessary to support ASICBoost. - Stratum v1 Docs - This article contains a worked example that provides details not available elsewhere but is not a comprehensive description of Stratum.
- pooler/cpuminer - An implementation of a Stratum client in c.
Stratum begins with an optional mining.configure
request from the client. If
this message is sent, then the extended version of Stratum is being used.
Next, the client may send either a mining.authorize
, or mining.subscribe
request. After the server responds to a mining.subscribe
request, it sends
mining.set_difficulty
and mining.notify
. At this point, the client has
enough information to start mining. However, the server might not accept shares
until after the client has been authorized.
While the client is mining, it sends a mining.submit
message every time it
solves a share. Periodically, the server sends mining.set_difficulty
and
mining.notify
messages to the client. If the set_extranonce extension has
been enabled in mining.configure
, the server may also send
mining.set_extranonce
messages.
Stratum is a biderectional stream of lines containing JSON objects in JSON RPC format. Each message is a JSON object on one line terminated by a new line. Three message types are defined in JSON RPC.
- request:
{id: <integer or string>, method: <string>, params: <array of JSON values>}
- response:
{id: <integer or string>, error: null | [<integer>, <string>], result: <JSON value>}
- notification:
{id: null, method: <string>, params: <array of JSON values>}
A response corresponds to a request and must have the same id. Message ids should be updated with each request. However, we note that pooler/cpuminer does not do this properly. Servers ought to be able to expect that message ids will be different for requests that are being handled at the same time.
Unlike a typical JSON RPC protocol, Stratum methods may be client-to-server or server-to-client.
Response messages contain an error field. This field may be null
if there
is no error. Otherwise it is an array containing an error code and error
message. If a response contains an error, the result field may be null
regardless of the type of message it is.
There is no standard for the meaning of the error codes. Proposed error codes for mining service are
20: Other/Unknown
21: Job not found (=stale)
22: Duplicate share
23: Low difficulty share
24: Unauthorized worker
25: Not subscribed
The purpose of the authorize message is to log in so that the mining pool knows who to pay for the miner's submitted shares.
Request params:
- workername:
string
- password:
string
(optional)
Response result: true|false
A subtlety of methods with boolean responses is that result which is
logically false response may come with an error, which means that the result
is allowed to be null
.
Request params:
- user agent/version:
string
- extranonce1:
hex
(optional, 4 bytes) - extranonce1 is a value that is provided by the mining pool in the subscription response and is also called the session id. A user may optionally request a session id in the subscription request. This may be desired because the client had to reconnect and was already doing work with a given session id.
Response result: [[<subscription>...], <extranonce1>, <extranonce2_size>]
where
- subscription:
[<method>, <subscription id>]
- These values are not actually used. For the original Stratum protocol, there will be two subscriptions, formining.notify
, andmining.set_difficulty
. The extended protocol may have additional subscriptions formining.set_extranonce
andmining.set_version_mask
if extensions set_extranonce or version_rolling are respectively enabled. The subscription id can be some random hex string. It is not used. - extranonce1:
hex
(4 bytes) - aka the session id. The string is written exactly as it appears in the coinbase. - extranonce2_size:
natural
- required because extranonce2_size fits into the coinbase script, which is preceeded by size specifier. Thus, the size of the coinbase. Thus the miner cannot choose the size of extranonce2.
Set a new difficulty for the client. Goes into effect upon the next job received
via mining.notify
.
Notification params:
- difficulty:
natural | float
- Many Stratum clients appear not to support difficulty as a floating point. This is fine for building blocks because the difficulty is so high that the number of significant digits it has as an integer is good enough. It is not good for small values, which might be found when mining Boost POW.
Notification params:
- jobID:
string
- used by the client to specify the job they are claiming to have worked on later inmining.submit
. It does not need to have any particular format. - previous block hash:
hex
(32 bytes) - The correct format is to take the hex string of the previous block hash with every four bytes reversed. The hex string as a whole is not reversed, as a Bitcoin hash would typically be in a human-readible format. This format is completely weird and there is no explanation for it anywhere or any overt description in any of our sources but the worked example in Stratum v1 Docs proves it correct. - generation tx 1:
hex
- The beginning of the coinbase. - generation tx 2:
hex
- The end of the coinbase. - merkle branch:
[hex...]
- List of hashes in hex. These hashes, unlike previous block hash, are written just as they really are, but in hex. They are not reversed, as you would normally see in some Bitcoin human-readable format for hashes. Each is 32 bytes long. - version:
hex
(4 bytes) - reversed from the way it is written in the block header. - target:
hex
(4 bytes) - reversed from the way it is written in the block header. - timestamp:
hex
(4 bytes) - reversed from the way it is written in the block header. - clean: boolean - whether solutions to previous jobs will be rejected. This is needed because as a block is being built, many jobs will be generated as new txs are processed but old ones still define a valid block that is worth money for the mining pool. However, when a new block comes out, all previous jobs no longer correspond to a valid block.
Request params:
- worker name: string - No particular format
- jobID: string - same as in
mining.notify
above. - extranonce2:
hex
- written as it appears in the block, not reversed. Must have the same number of bytes as the value of extranonce2_size. - timestamp:
hex
(4 bytes) - reversed from the way it is written in the block header. - nonce:
hex
(4 bytes) - reversed from the way it is written in the block header. - version:
hex
(4 bytes, only present if extension version_rolling was enabled) - reversed from the way it is written in the block header.
Response result: true|false
Typically, a false result will come with an error message, in which case the
result is allowed to be null
.
The original version of Stratum did not include the mining.configure
message. If
extensions areStratum-Extensions supported, mining.configure
is the first message of the protocol
and is used to agree on the extensions that are enabled for this session and
to define their parameters.
Request params:
- extensions:
[string...]
- A list of names of requested extensions. - extensions-parameters:
string => <JSON value>
- A map of parameter values for the requested extensions. For a given extension x and parameter value v, values in the map are given as"x.v": <JSON value>
.
Response result: string => <JSON value>
- For every given extension x
that was requested, the map must include an entry "x": true|false|string
that indicates whether the server will support extension x.
The string is an error message and interpreted as a false value.
Additionally, for every parameter v of every extension that is supported,
the map must include an entry "x.v": <JSON value>
to a valid value
of that parameter, according to the specification of the given extension x.
Example (from Stratum-Extensions)
Request:
{"method": "mining.configure",
"id": 1,
"params": [["minimum-difficulty", "version-rolling"],
{"minimum-difficulty.value": 2048,
"version-rolling.mask": "1fffe000", "version-rolling.min-bit-count": 2}]}
Response:
{"error": null,
"id": 1,
"result": {"version-rolling": true,
"version-rolling.mask": "18000000",
"minimum-difficulty": true}}
This method requires extension set_extranonce to be enabled. The params are
the same as the last two in the mining.subscribe
response. As with
mining.set_difficulty
, the new extranonce goes into effect with the next job
received via mining.notify
.
Notification params:
- extranonce1:
hex
(4 bytes) - not considered to be a session id this time. Instead it is just an update to the value used in extranonce1. - extranonce2_size:
natural
This method requires extension rolling_version to be enabled.
Unlike with mining.set_difficulty
and mining.set_extranonce
, the new
version mask is applied immediately, not after the next job.
Notification params:
- mask:
hex
(4 bytes) - reversed from the way that the version field would appear in the block header.
Other methods are described in Stratum Mining Protocol v2.1 which are not required to do mining and may not all be supported by this library.
mining.get_transactions
(client to server, request/response) - This method, in particular, cannot be supported by modern implementations because the server cannot afford to send the client an entire block whenever it asks for it. This method derives from a bad way of thinking about the economics of mining, which is that the mining pool serves the miners rather than the bitcoin users. Miners provide hashpower and they get paid whether or not the mining pool's block is orphaned. Thus, they don't care what the block looks like and shouldn't want a method like this. The mining pool serves the Bitcoin users by processing their transactions into a block.mining.suggest_difficulty
(client to server, notification) -mining.suggest_target
(client to server, notification) - same as the previous method except that the difficulty is given in a different format.client.get_version
(server to client, request/response) - request the version of the client software from the client.client.reconnect
(server to client, notification)client.show_message
(server to client, notification) - display a human-readable message to the user.
When this first version of Stratum proved to be inadequate, ways of extending it were defined. In particular, the discovery of ASICBoost meant that miners would want to adjust bits earlier in the blockheader than the nonce in order to search for a solution more efficiently. If some option was not provided to better support ASICBoost, then the best option for miners in original Stratum was to adjust the timestamp field. This is not desirable insofar as doing so interferes with with Bitcoin's function as a timestamp server. Hence, an extension called version_rolling was defined which enables miners to adjust the version field. The orignial extensions document for Stratum specifies three additional extensions: set_extranonce, minimum_difficulty, and info. We describe version_rolling and set_extranonce in this document because these are necessary to support Boost POW with ASICBoost.
Enables client and server to agree on some bits in the version field that the client can edit and will be used like more extra nonce bits. Required to support ASICBoost.
Adds method mining.set_version_mask
and adds an extra field to mining.submit
.
request parameters:
- mask:
hex
(4 bytes) - Reversed from the way that the version field is written in the block header. A proposed mask that would determine which bits of the version field that can be altered by the client. The positive bits would be the ones that can be edited. - min_bit_count: natural number - the minimum number of bits that the client desires to be editable in the version field.
response parameters:
- mask:
hex
(4 bytes) - Reversed from the way that the version field is written in the block header. The number of positive bits must be at least min_bit_count and must be compatible with the mask sent by the client. In other words, it cannot have positive bits that are not positive in the client mask.
Adds method mining.set_extranonce
. This extension has no parameters.
generationTX1 = hex2bin(notify_params[2])
extranonce1 = hex2bin(subscribe_response_result[1])
extranonce2 = hex2bin(submit_request_params[3])
generationTX2 = hex2bin(notify_params[3])
coinbase = cat(generationTX1, extranonce1, extranonce2, generationTX2)
merkle_branch = notify_params[4]
merkle_root = fold((hash, node) -> Hash256(cat(hash, hex2bin(node))),
Hash256(coinbase), merkle_branch)
/* case 1: original protocol */
version = reverse(hex2bin(notify_params[5]))
/* case 2: version_rolling extension */
version_mask = reverse(hex2bin(configure_params[1]["version_rolling.mask"]))
version = (reverse(hex2bin(notify_params[5])) & ~version_mask) | (reverse(hex2bin(submit_request_params[5])) & version_mask)
prev_hash = reverse_every_4_bytes(hex2bin(notify_params[1]))
target = reverse(hex2bin(notify_params[9]))
timestamp = reverse(hex2bin(submit_request_params[3]))
nonce = reverse(hex2bin(submit_request_params[4]))
block_header = cat(version, prev_hash, merkle_root, target, timestamp, nonce)
An example from Stratum v1 Docs provides
some information not given elsewhere that clarifies the format of
mining.subscribe, mining.notify, and mining.submit, which are the three
methods used to construct the block header. In particular, the
previous block hash field in mining.notify
has a very strange format that is
not explained properly in any of the sources we used. It is given with every
four bytes reversed.
mining.subscribe
response result:{"id": 1, "result": [ [ ["mining.set_difficulty", "b4b6693b72a50c7116db18d6497cac52"], ["mining.notify", "ae6812eb4cd7735a302a8a9dd95cf71f"]], "08000002", 4], "error": null}
mining.notify
params:{"params": ["bf", "4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000", "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e5008","072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000", [], "00000002", "1c2ac4af", "504e86b9", false], "id": null, "method": "mining.notify"}
mining.submit
request params:{"params": ["slush.miner1", "bf", "00000001", "504e86ed", "b2957c02"], "id": 4, "method": "mining.submit"}
Coinbase: "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e50080800000200000001072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000"
Hash256 of Coinbase: 0xec9d69b1c30dd91529e2f5a5636354a310a79b9b83622cfd79c8da9daa4d4132
Merkle Root: 0xec9d69b1c30dd91529e2f5a5636354a310a79b9b83622cfd79c8da9daa4d4132
Prev Hash: 0x00000000440b921e1b77c6c0487ae5616de67f788f44ae2a5af6e2194d16b6f8
Block Header: "02000000f8b6164d19e2f65a2aae448f787fe66d61e57a48c0c6771b1e920b440000000032414daa9ddac879fd2c62839b9ba710a3546363a5f5e22915d90dc3b1699deced864e50afc42a1c027c95b2"
Hash256 of Block Header: 0x000000002076870fe65a2b6eeed84fa892c0db924f1482243a6247d931dcab32
From this worked example we can verify the correct format for fields such as previous block hash in the original protocol. The example does not explain the format for the merkle branch since it is empty. To check what we have said about that, one has to look into an implementation such as pooler/cpuminer.