From 8d1093327faea5609043cfa576a23b37dff06de4 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Wed, 17 Jul 2024 14:50:05 +0200 Subject: [PATCH 1/4] Start Template Provider with -sv2 --- src/init.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index faaf3353d070..c0012c0f6494 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -53,7 +53,7 @@ #include #include #include -#include +#include #include #include #include @@ -257,6 +257,9 @@ void Interrupt(NodeContext& node) InterruptRPC(); InterruptREST(); InterruptTorControl(); + if (node.sv2_template_provider) { + node.sv2_template_provider->Interrupt(); + } InterruptMapPort(); if (node.connman) node.connman->Interrupt(); @@ -296,6 +299,9 @@ void Shutdown(NodeContext& node) StopTorControl(); + // Stop Template Provider + if (node.sv2_template_provider) node.sv2_template_provider->StopThreads(); + if (node.chainman && node.chainman->m_thread_load.joinable()) node.chainman->m_thread_load.join(); // After everything has been shut down, but before things get flushed, stop the // the scheduler. After this point, SyncWithValidationInterfaceQueue() should not be called anymore @@ -309,6 +315,7 @@ void Shutdown(NodeContext& node) node.banman.reset(); node.addrman.reset(); node.netgroupman.reset(); + node.sv2_template_provider.reset(); if (node.mempool && node.mempool->GetLoadTried() && ShouldPersistMempool(*node.args)) { DumpMempool(*node.mempool, MempoolPath(*node.args)); @@ -659,6 +666,9 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-blockmaxweight=", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockmintxfee=", strprintf("Set lowest fee rate (in %s/kvB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockversion=", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION); + argsman.AddArg("-sv2", "Bitcoind will act as a Stratum v2 Template Provider (default: false)", ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); + argsman.AddArg("-sv2interval", strprintf("Template Provider block template update interval (default: %d seconds)", Sv2TemplateProviderOptions().fee_check_interval.count()), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); + argsman.AddArg("-sv2feedelta", strprintf("Minimum fee delta for Template Provider to send update upstream (default: %d sat)", uint64_t(Sv2TemplateProviderOptions().fee_delta)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcallowip=", "Allow JSON-RPC connections from specified source. Valid values for are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0), a network/CIDR (e.g. 1.2.3.4/24), all ipv4 (0.0.0.0/0), or all ipv6 (::/0). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); @@ -676,6 +686,8 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-rpcwhitelistdefault", "Sets default behavior for rpc whitelisting. Unless rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc server acts as if all rpc users are subject to empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault is set to 1 and no -rpcwhitelist is set, rpc server acts as if all rpc users are subject to empty whitelists.", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcworkqueue=", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + argsman.AddArg("-sv2bind=[:]", strprintf("Bind to given address and always listen on it (default: 127.0.0.1). Use [host]:port notation for IPv6."), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-sv2port=", strprintf("Listen for Stratum v2 connections on (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->Sv2Port(), testnetBaseParams->Sv2Port(), signetBaseParams->Sv2Port(), regtestBaseParams->Sv2Port()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); #if HAVE_DECL_FORK argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -1321,6 +1333,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) {"-onion", true}, {"-proxy", true}, {"-rpcbind", false}, + {"-sv2bind", false}, {"-torcontrol", false}, {"-whitebind", false}, {"-zmqpubhashblock", true}, @@ -1974,6 +1987,47 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return false; } + if (args.GetBoolArg("-sv2", false)) { + assert(!node.sv2_template_provider); + assert(node.mining); + + Sv2TemplateProviderOptions options{}; + + node.sv2_template_provider = std::make_unique(*node.mining); + + const std::string sv2_port_arg = args.GetArg("-sv2port", ""); + + if (sv2_port_arg.empty()) { + options.port = BaseParams().Sv2Port(); + } else { + if (!ParseUInt16(sv2_port_arg, &options.port) || options.port == 0) { + return InitError(InvalidPortErrMsg("sv2port", sv2_port_arg)); + } + } + + if (gArgs.IsArgSet("-sv2bind")) { // Specific bind address + std::optional sv2_bind{gArgs.GetArg("-sv2bind")}; + if (sv2_bind) { + if (!SplitHostPort(sv2_bind.value(), options.port, options.host)) { + throw std::runtime_error(strprintf("Invalid port %d", options.port)); + } + } + } + + options.fee_delta = gArgs.GetIntArg("-sv2feedelta", Sv2TemplateProviderOptions().fee_delta); + + if (gArgs.IsArgSet("-sv2interval")) { + if (args.GetIntArg("-sv2interval", 0) < 1) { + return InitError(Untranslated("-sv2interval must be at least one second")); + } + options.fee_check_interval = std::chrono::seconds(gArgs.GetIntArg("-sv2interval", 0)); + } + + if (!node.sv2_template_provider->Start(options)) { + return InitError(_("Unable to start Stratum v2 Template Provider")); + } + } + // ********************************************************* Step 13: finished // At this point, the RPC is "started", but still in warmup, which means it From 4c7f0d6d10f095c322f75cc9d796c5625020cf40 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 8 Dec 2023 15:08:01 +0100 Subject: [PATCH 2/4] signet: miner skips PSBT step for OP_TRUE --- contrib/signet/README.md | 3 +- contrib/signet/miner | 47 ++++++++++++++++++---------- test/functional/tool_signet_miner.py | 27 +++++++++++----- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/contrib/signet/README.md b/contrib/signet/README.md index 706b296c5494..3303740f579f 100644 --- a/contrib/signet/README.md +++ b/contrib/signet/README.md @@ -44,6 +44,8 @@ Adding the --ongoing parameter will then cause the signet miner to create blocks $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=$NBITS --ongoing +For custom signets with a trivial challenge a PSBT is not necessary. The miner detects this for `OP_TRUE`. + Other options ------------- @@ -80,4 +82,3 @@ These steps can instead be done explicitly: $CLI -signet -stdin submitblock This is intended to allow you to replace part of the pipeline for further experimentation (eg, to sign the block with a hardware wallet). - diff --git a/contrib/signet/miner b/contrib/signet/miner index 4216ada5fa63..f6787ca95acc 100755 --- a/contrib/signet/miner +++ b/contrib/signet/miner @@ -96,9 +96,10 @@ def do_decode_psbt(b64psbt): return from_binary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness def finish_block(block, signet_solution, grind_cmd): - block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution) - block.vtx[0].rehash() - block.hashMerkleRoot = block.calc_merkle_root() + if signet_solution is not None: + block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution) + block.vtx[0].rehash() + block.hashMerkleRoot = block.calc_merkle_root() if grind_cmd is None: block.solve() else: @@ -110,10 +111,12 @@ def finish_block(block, signet_solution, grind_cmd): block.rehash() return block -def generate_psbt(tmpl, reward_spk, *, blocktime=None): - signet_spk = tmpl["signet_challenge"] +def generate_psbt(block, signet_spk): signet_spk_bin = bytes.fromhex(signet_spk) + signme, spendme = signet_txs(block, signet_spk_bin) + return do_createpsbt(block, signme, spendme) +def new_block(tmpl, reward_spk, blocktime=None): cbtx = create_coinbase(height=tmpl["height"], value=tmpl["coinbasevalue"], spk=reward_spk) cbtx.vin[0].nSequence = 2**32-2 cbtx.rehash() @@ -135,9 +138,10 @@ def generate_psbt(tmpl, reward_spk, *, blocktime=None): block.vtx[0].wit.vtxinwit = [cbwit] block.vtx[0].vout.append(CTxOut(0, bytes(get_witness_script(witroot, witnonce)))) - signme, spendme = signet_txs(block, signet_spk_bin) + block.vtx[0].rehash() + block.hashMerkleRoot = block.calc_merkle_root() - return do_createpsbt(block, signme, spendme) + return block def get_reward_address(args, height): if args.address is not None: @@ -177,8 +181,10 @@ def get_reward_addr_spk(args, height): def do_genpsbt(args): tmpl = json.load(sys.stdin) + signet_spk = tmpl["signet_challenge"] _, reward_spk = get_reward_addr_spk(args, tmpl["height"]) - psbt = generate_psbt(tmpl, reward_spk) + block = new_block(tmpl, reward_spk) + psbt = generate_psbt(block, signet_spk) print(psbt) def do_solvepsbt(args): @@ -407,14 +413,23 @@ def do_generate(args): # mine block logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine) mined_blocks += 1 - psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time) - input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') - psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) - if not psbt_signed.get("complete",False): - logging.debug("Generated PSBT: %s" % (psbt,)) - sys.stderr.write("PSBT signing failed\n") - return 1 - block, signet_solution = do_decode_psbt(psbt_signed["psbt"]) + block = new_block(tmpl, reward_spk, blocktime=mine_time) + + # BIP325 allows omitting the signet commitment when scriptSig and + # scriptWitness are both empty. This is the case for trivial + # challenges such as OP_TRUE + signet_solution = None + signet_spk = tmpl["signet_challenge"] + if signet_spk != "51": + psbt = generate_psbt(block, signet_spk) + input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) + if not psbt_signed.get("complete",False): + logging.debug("Generated PSBT: %s" % (psbt,)) + sys.stderr.write("PSBT signing failed\n") + return 1 + block, signet_solution = do_decode_psbt(psbt_signed["psbt"]) + block = finish_block(block, signet_solution, args.grind_cmd) # submit block diff --git a/test/functional/tool_signet_miner.py b/test/functional/tool_signet_miner.py index bdefb92ae621..c2c918b121b0 100755 --- a/test/functional/tool_signet_miner.py +++ b/test/functional/tool_signet_miner.py @@ -26,26 +26,31 @@ def add_options(self, parser): def set_test_params(self): self.chain = "signet" self.setup_clean_chain = True - self.num_nodes = 1 + self.num_nodes = 2 # generate and specify signet challenge (simple p2wpkh script) privkey = ECKey() privkey.set(CHALLENGE_PRIVATE_KEY, True) pubkey = privkey.get_pubkey().get_bytes() challenge = key_to_p2wpkh_script(pubkey) - self.extra_args = [[f'-signetchallenge={challenge.hex()}']] + + self.extra_args = [ + ["-signetchallenge=51"], # OP_TRUE + [f'-signetchallenge={challenge.hex()}'], + ] def skip_test_if_missing_module(self): self.skip_if_no_cli() self.skip_if_no_wallet() self.skip_if_no_bitcoin_util() - def run_test(self): - node = self.nodes[0] - # import private key needed for signing block - node.importprivkey(bytes_to_wif(CHALLENGE_PRIVATE_KEY)) + def setup_network(self): + self.setup_nodes() + # Nodes with different signet networks are not connected - # generate block with signet miner tool + # generate block with signet miner tool + def mine_block(self, node): + assert_equal(node.getblockcount(), 0) base_dir = self.config["environment"]["SRCDIR"] signet_miner_path = os.path.join(base_dir, "contrib", "signet", "miner") subprocess.run([ @@ -60,6 +65,14 @@ def run_test(self): ], check=True, stderr=subprocess.STDOUT) assert_equal(node.getblockcount(), 1) + def run_test(self): + self.log.info("Signet node with trivial challenge (OP_RETURN)") + self.mine_block(self.nodes[0]) + + self.log.info("Signet node with single signature challenge") + # import private key needed for signing block + self.nodes[1].importprivkey(bytes_to_wif(CHALLENGE_PRIVATE_KEY)) + self.mine_block(self.nodes[1]) if __name__ == "__main__": SignetMinerTest(__file__).main() From d3a8092f44e0b6a30550b9c7d111a088ed2bce64 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Thu, 28 Dec 2023 13:49:58 +0100 Subject: [PATCH 3/4] doc: explain Stratum v2 design, testing and usage --- doc/stratum-v2.md | 399 ++++++++++++++++++++++++++++++++++++++++++++++ src/init.cpp | 2 +- 2 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 doc/stratum-v2.md diff --git a/doc/stratum-v2.md b/doc/stratum-v2.md new file mode 100644 index 000000000000..fab59d6d6911 --- /dev/null +++ b/doc/stratum-v2.md @@ -0,0 +1,399 @@ +# Stratum v2 + +## Design + +The Stratum v2 protocol specification can be found here: https://github.com/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](https://github.com/stratum-mining/sv2-spec/blob/main/08-Message-Types.md) +and [Protocol Overview](https://github.com/stratum-mining/sv2-spec/blob/main/03-Protocol-Overview.md) +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](https://github.com/stratum-mining/sv2-spec/blob/main/04-Protocol-Security.md) +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](https://zguide.zeromq.org/docs/chapter3/) +mode. Currently we only use the unidirectional `ZMQ_PUB` mode, see +[zmq_socket](http://api.zeromq.org/4-2: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. + +### 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](https://github.com/stratum-mining/sv2-spec/blob/main/06-Job-Declaration-Protocol.md). +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://github.com/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://github.com/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 testnet3 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 + +The following is untested. + +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: + +``` +src/bitcoind -testnet -sv2 -debug=sv2 +``` + +Build and run a Job Declator client: [stratum-mining/stratum/tree/main/roles/jd-client](https://github.com/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. +Try this config (see documentation for more): + +```toml +[[upstreams]] +# Listen for connecting miner or translator: +downstream_address = "127.0.0.1" +downstream_port = 34265 + +# Connect to upstream pool: +authority_pubkey = "3VANfft6ei6jQq1At7d8nmiZzVhBFS4CiQujdgim1ign" +pool_address = "75.119.150.111:34254" +jd_address = "75.119.150.111:34264" +pool_signature = "Stratum v2 SRI Pool" +``` + +Do not change the pool signature. It (currently) must match what the pool uses +or you will mine invalid blocks. + +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](https://github.com/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. + +```toml +# Local SRI Testnet JDC Upstream Connection +upstream_address = "127.0.0.1" +upstream_port = 34265 +upstream_authority_pubkey = "3VANfft6ei6jQq1At7d8nmiZzVhBFS4CiQujdgim1ign" +``` + +The `upstream_authority_pubkey` field is required but ignored. + +As soon as you turn on the translator, the Bitcoin Core log should show a `SetupConnection` [message](https://github.com/stratum-mining/sv2-spec/blob/main/08-Message-Types.md). + +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 testnet3, 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](../contrib/signet/README.md) + +If you use the default signet for anything else, create a fresh data directory. + +Add the following to `bitcoin.conf`: + +```ini +[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](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki). 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`: + +```sh +CLI="src/bitcoin-cli" +MINER="contrib/signet/miner" +GRIND="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: + +```sh +src/qt/bitcoin-qt -datadir=$HOME/.stratum/bitcoin -signet +``` + +Suggested config for the pool node: + +```ini +[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: + +```sh +src/bitcoind -datadir=$HOME/.stratum/bitcoin-pool -signet +``` + +Configure an SRI pool: + +``` +cd roles/pool +mkdir -p ~/.stratum +cp +``` + +Start the SRI pool: + +```sh +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](https://github.com/pooler/cpuminer) 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](https://academy.braiins.com/en/braiins-pool/stratum-v2-manual/#servers-and-ports). 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](https://dmnd.work/#solo-mine). + +### 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. diff --git a/src/init.cpp b/src/init.cpp index c0012c0f6494..fd3b48d27738 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -666,7 +666,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-blockmaxweight=", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockmintxfee=", strprintf("Set lowest fee rate (in %s/kvB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockversion=", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION); - argsman.AddArg("-sv2", "Bitcoind will act as a Stratum v2 Template Provider (default: false)", ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); + argsman.AddArg("-sv2", "Bitcoind will act as a Stratum v2 Template Provider, see doc/stratum-v2.md (default: false)", ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-sv2interval", strprintf("Template Provider block template update interval (default: %d seconds)", Sv2TemplateProviderOptions().fee_check_interval.count()), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-sv2feedelta", strprintf("Minimum fee delta for Template Provider to send update upstream (default: %d sat)", uint64_t(Sv2TemplateProviderOptions().fee_delta)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); From 40f2533c74218ad79c46d15953efa008c1443aba Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Thu, 18 Jan 2024 17:36:17 +0100 Subject: [PATCH 4/4] ci: skip Github CI on branch pushes for forks Similar to the previous commit, however the behavior isn't opt-in with NO_BRANCH, because Github CI lacks custom variables. --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 559ad4933fef..4ee4a2e6779e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,10 @@ on: # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push. push: branches: - - '**' + # Disable CI on branch pushes to forks. It will still run for pull requests. + # This prevents CI from running twice for typical pull request workflows. + - 'bitcoin/**' + - 'bitcoin-core/**' tags-ignore: - '**'