Skip to content

Latest commit

 

History

History
379 lines (264 loc) · 16 KB

stratum-v2.md

File metadata and controls

379 lines (264 loc) · 16 KB

Stratum v2

Design

The Stratum v2 protocol specification can be found here: https:/stratum-mining/sv2-spec

Bitcoin Core performs the Template Provider role, and for that it implements the Template Distribution Protocol. When launched with -sv2 we listen for connections from Job Declarator clients.

A Job Declarator client might run on the same machine, e.g. for a single ASIC hobby miner. In a more advanced setup it might run on another machine, on the same local network or remote. A third possible use case is where a miner relies on a node run by someone else to provide the templates. Trust may not go both ways in that scenario, see the section on DoS.

We send them a new block template whenenver out tip is updated, or when mempool fees have increased sufficiently. If the pool finds a block, we attempt to broadcast it based on a cached template.

Communication with other roles uses the Noise Protocol, which has been implemented to the extend necessary. Its cryptographic primitives were chosen so that they were already present in the Bitcoin Core project at the time of writing the spec.

Advantage over getblocktemplate RPC

Although under the hood the Template Provider uses CreateNewBlock() just like the getblocktemplate RPC, there's a number of advantages in running a server with a stateful connection, and avoiding JSON RPC in general.

  1. Stateful, so we can have back-and-forth, e.g. requesting transaction data, processing a block solution.
  2. Less (de)serializing and data sent over the wire, compared to plain text JSON
  3. Encrypted, safer (for now: less unsafe) to expose on the public internet
  4. Push based: new template is sent immediately when a new block is found rather than at the next poll interval. Combined with Cluster Mempool this can hopefully be done for higher fee templates too.
  5. Low friction deployment with other Stratum v2 software / devices

Message flow(s)

See the Message Types and Protocol Overview section of the spec for all messages and their details.

When a Job Declarator client connects to us, it first sends a SetupConnection message. We reply with SetupConnection.Success unless something went wrong, e.g. version mismatch, in which case we reply with SetupConnection.Error.

Next the client sends us their CoinbaseOutputDataSize. If this is invalid we disconnect. Otherwise we start the cycle below that repeats with every block.

We send a NewTemplate message with future_template set true, immedidately followed by SetNewPrevHash. We don't send any transaction information at this point. The Job Declarator client uses this to announce upstream that it wants to declare a new template.

In the simplest setup with SRI the Job Declarator client doubles as a proxy and sends these two messages to all connected mining devices. They will keep working on their previous job until the SetNewPrevHash message arrives. Future implementations could provide an empty or speculative template before a new block is found.

Meanwhile the pool will request, via the Job Declarator client, the transaction lists belonging to the template: RequestTransactionData. In case of a problem we reply with RequestTransactionData.Error. Otherwise we reply with the full[0] transaction data in RequestTransactionData.Success.

When we find a template with higher fees, we send a NewTemplate message with future_template set to false. This is not followed by SetNewPrevHash.

Finally, if we find an actual block, the client sends us SubmitSolution. We then lookup the template (may not be the most recent one), reconstruct the block and broadcast it. The pool will do the same.

[0]: When the Job Declarator client communicates with the Job Declarator server there is an intermediate message which sends short transaction ids first, followed by a ProvideMissingTransactions message. The spec could be modified to introduce a similar message here. This is especially useful when the Template Provider runs on a different machine than the Job Declarator client. Erlay might be useful here too, in a later stage.

Noise Protocol

As detailed in the Protocol Security section of the spec, Stratum v2 roles use the Noise Protocol to communicate.

We only implement the parts needed for inbound connections, although not much code would be needed to support outbound connections as well if this is required later.

The spec was written before BIP 324 peer-to-peer encryption was introduced. It has much in common with Noise, but for the purposes of Stratum v2 it currently lacks authentication. Perhaps a future version of Stratum will use this. Since we only communicate with the Job Declarator role, a transition to BIP 324 would not require waiting for the entire mining ecosystem to adopt it.

An alternative to implementing the Noise Protocol in Bitcoin Core is to use a unix socket instead and rely on the user to install a separate tool to convert to this protocol. Such a tool could be provided by developers of the Job Declarator client.

ZMQ may be slightly more convenient than a unix socket. Since the Stratum v2 protocol is stateful we would need to use the request-reply mode. Currently we only use the unidirectional ZMQ_PUB mode, see zmq_socket. But then Stratum v2 messages can be sent and received without dealing with low level sockets / buffers / bytes. This could be implemented as a ZmqTransport subclass of Transport. Whether this involves less new code than the Noise Protocol remains to be seen.

Mempool monitoring

The current design calls CreateNewBlock() internally every -sv2interval seconds. We then broadcast the resulting block template if fees have increased enough to make it worth the overhead (-sv2feedelta). A pool may have additional rate limiting in place.

This is better than the Stratum v1 model of a polling call to the getblocktemplate RPC. It avoids (de)serializing JSON, uses an encrypted connection and only sends data over the wire if fees increased.

But it's still a poll based model, as opposed to the push based approach whenever a new block arrives. It would be better if a new template is generated as soon as a potentially revenue-increasing transaction is added to the mempool. The Cluster Mempool project might enable that.

DoS and privacy

The current Template Provider should not be run on the public internet with unlimited access. It is not harneded against DoS attacks, nor against mempool probing.

There's currently no limit to the number of Job Declarator clients that can connect, which could exhaust memory. There's also no limit to the amount of raw transaction data that can be requested.

Templates reveal what is in the mempool without any delay or randomization.

This is why the use of -sv2allowip is required when -sv2bind is set to anything other than localhost on mainnet.

Future improvements should aim to reduce or eliminate the above concerns such that any node can run a Template Provider as a public service.

Usage

Using this in a production environment is not yet recommended, but see the testing guide below.

Build

Follow the instructions in build-unix.md, build-osx.md, etc, but add -DWITH_SV2=ON to cmake -B build.

Parameters

See also bitcoind --help.

Start Bitcoin Core with -sv2 to start a Template Provider server with default settings. The listening port can be changed with -sv2port.

By default it only accepts connections from localhost. This can be changed using -sv2bind, which requires the use of -sv2allowip. See DoS and Privacy below.

Use -debug=sv2 to see Stratum v2 related log messages. Set -loglevel=sv2:trace to see which messages are exchanged with the Job Declarator client.

The frequency at which new templates are generated can be controlled with -sv2interval. The new templates are only submitted to connected clients if they are for a new block, or if fees have increased by at least -sv2feedelta.

You may increase -sv2interval`` to something your node can handle, and then adjust -sv2feedelta` to limit back and forth with the pool.

You can use -debug=bench to see how long block generation typically takes on your machine, look for CreateNewBlock() ... (total ...ms). Another factor to consider is upstream rate limiting, see the Job Declaration Protocol. Mining hardware may also incur a performance dip when it receives a new job.

Testing Guide

Unfortunately testing still requires quite a few moving parts, and each setup has its own merits and issues.

To get help with the stratum side of things, this Discord may be useful: https://discord.gg/fsEW23wFYs

The Stratum Reference Implementation (SRI) provides example implementations of the various (other) Stratum v2 roles: https:/stratum-mining/stratum

You can set up an entire pool on your own machine. You can also connect to an existing pool and only run a limited set of roles on your machine, e.g. the Job Declarator client and Translator (v1 to v2).

SRI includes a v1 and v2 CPU miner, but at the time of writing neither seems to work. Another CPU miner that does work, when used with the Translator: https:/pooler/cpuminer

Regtest

TODO

This is also needed for functional test suite coverage. It's also the only test network doesn't need a standalone CPU miner or ASIC.

Perhaps a mock Job Declator client can be added. We also need a way mine a given block template, akin to generate.

To make testing easier it should be possible to use a connection without Noise Protocol.

Testnet

The difficulty on testnet4 varies wildly, but typically much too high for CPU mining. Even when using a relatively cheap second hand miner, e.g. an S9, it could take days to find a block.

The above means it's difficult to test the SubmitSolution message.

Bring your own ASIC, use external testnet pool

This uses an existing testnet pool. There's no need to create an account anywhere. The pool does not pay out the testnet coins it generates. It also currently doesn't censor anything, so you can't test the (solo mining) fallback behavior.

First start the node:

build/src/bitcoind -testnet4 -sv2 -debug=sv2

Build and run a Job Declator client: [stratum-mining/stratum/tree/main/roles/jd-client](https:/stratum-mining/stratum/tree/main/roles/jd-client

This client connects to your node to receive new block templates and then "declares" them to a Job Declarator server. Additionally it connects to the pool itself.

Copy jdc-config-hosted-example.toml to e.g. ~/.stratum/testnet4-jdc.toml, change tp_address to 127.0.0.1:48336 and comment out tp_authority_public_key.

The coinbase_outputs is used for fallback to solo mining. Generate an address of any type and then use the getaddressinfo RPC to find its public key.

Finally you most likely need to use the v1 to v2 translator: stratum-mining/stratum/tree/main/roles/translator, even when you have a stratum v2 capable miner (see notes on ASIC's and Firmware below).

You need to point the translator to your job declator client, which in turn takes care of connecting to the pool. Try tproxy-config-local-jdc-example.toml.

As soon as you turn on the translator, the Bitcoin Core log should show a SetupConnection message.

Now point your ASIC to the translator. At this point you should be seeing NewTemplate, SetNewPrevHash and SetNewPrevHash messages.

If the pool is down, notify someone on the above mentioned Discord.

Custom Signet

Unlike testnet4, signet(s) use the regular difficulty adjustment mechanism. Although the default signet has very low difficulty, you can't mine on it, because to do so requires signing blocks using a private key that only two people have.

It's possible to create a signet that does not require signatures. There's no such public network, because it would risk being "attacked" by very powerful ASIC's. They could massively increase the difficulty and then disappear, making it impossible for a CPU miner to append new blocks.

Instead, you can create your own custom unsigned signet. Unlike regtest this network does have difficulty (adjustment). This allows you to test if e.g. pool software correctly sets and adjusts the share difficulty for each participant. Although for the Template Provider role this is not relevant.

Creating the signet

See also signet/README.md

If you use the default signet for anything else, create a fresh data directory.

Add the following to bitcoin.conf:

[signet]
# OP_TRUE
signetchallenge=51

This challenge represents "the special case where an empty solution is valid (i.e. scriptSig and scriptWitness are both empty)", see BIP 325. For mining software things will look just like testnet.

The new chain needs to have at least 16 blocks, or the SRI software will panick. So we'll mine those using bitcoin-util grind:

CLI="build/src/bitcoin-cli"
MINER="contrib/signet/miner"
GRIND="build/src/bitcoin-util grind"
ADDR=...
NBITS=1d00ffff
$MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=$NBITS

Mining

The cleanest setup involves two connected nodes, each with their own data directory: one for the pool and one for the miner. By selectively breaking the connection you can inspect how unknown transactions in the template are requested by the pool, and how a newly found block is submitted is submitted both by the pool and the miner.

However things should work fine with just one node.

Start the miner node first, with a GUI for convenience:

src/qt/bitcoin-qt -datadir=$HOME/.stratum/bitcoin -signet

Suggested config for the pool node:

[signet]
# OP_TRUE
signetchallenge=51
server=0
listen=0
connect=127.0.0.1

The above disables its RPC server and p2p listening to avoid a port conflict.

Start the pool node:

build/src/bitcoind -datadir=$HOME/.stratum/bitcoin-pool -signet

Configure an SRI pool:

cd roles/pool
mkdir -p ~/.stratum
cp

Start the SRI pool:

cargo run -p pool_sv2 -- -c ~/.stratum/signet-pool.toml

For the Job Declarator client and Translator, see Testnet above.

Now use the CPU miner and point it to the translator:

./minerd -a sha256d -o stratum+tcp://localhost:34255 -q -D -P

Mining after being away

The Template Provider will not start until the node is caught up. Use bitcoin-util grind as explained above for it to catch up.

Mainnet

See testnet for how to use an external pool. See signet for how to configure your own pool.

Pools that support Stratum v2 on mainnet:

  • Braiins: unclear if they are currently compatible with latest spec. URL's are listed here. There's no Job Declarator server.
  • DEMAND : No account needed for solo mining. Both the pool and Job Declarator server are at dmnd.work:2000. Requires a custom SRI branch, see instructions.

Notes on ASIC's and Firmware:

BraiinsOS

  • v22.08.1 uses an (incompatible) older version of Stratum v2
  • v23.12 is untested (and not available on S9)
  • v22.08.1 when used in Stratum v1 mode, does not work with the SRI Translator

Antminer stock OS

This should work with the Translator, but has not been tested.