diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index a95bc220185..ff4f87109f9 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -60,8 +60,10 @@ public: * @param[in] nonce nonce block header field * @param[in] coinbase complete coinbase transaction (including witness) * - * @note unlike the submitblock RPC, this method does NOT add the - * coinbase witness automatically. + * @note Unlike the submitblock RPC, this method does not call + * UpdateUncommittedBlockStructures to add a missing coinbase witness + * reserved value. Callers must provide a complete coinbase transaction, + * including the witness when a witness commitment is present. * * @note for heights <= 16, the BIP34 height push in getCoinbaseTx().script_sig_prefix * is only one byte long, so the coinbase scriptSig needs at least @@ -157,6 +159,27 @@ public: */ virtual bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) = 0; + /** + * Process a fully assembled block. + * + * Similar to the submitblock RPC. Accepts a complete block, validates + * it, and if accepted as new, processes it into chainstate. Accepted + * blocks may then be announced to peers through normal validation signals. + * + * @param[in] block the complete block to submit + * @param[out] reason failure reason (BIP22) + * @param[out] debug more detailed rejection reason + * @returns true if the block was accepted as a new block. Returns + * false and sets reason if the block is a duplicate or + * the validation result is inconclusive. + * + * @note Unlike the submitblock RPC, this method does not call + * UpdateUncommittedBlockStructures to add a missing coinbase witness + * reserved value. Callers must submit a fully formed block, including + * the coinbase witness when a witness commitment is present. + */ + virtual bool submitBlock(const CBlock& block, std::string& reason, std::string& debug) = 0; + //! Get internal node context. Useful for RPC and testing, //! but not accessible across processes. virtual const node::NodeContext* context() { return nullptr; } diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp index 64cad4d49f2..a6dd8d71f31 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -25,6 +25,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") { createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions, cooldown: Bool = true) -> (result: BlockTemplate); checkBlock @5 (context :Proxy.Context, block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool); interrupt @6 () -> (); + submitBlock @7 (context :Proxy.Context, block: Data) -> (reason: Text, debug: Text, result: Bool); } interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 28f8f2e88ee..2f68f414f0d 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1023,6 +1023,17 @@ public: return state.IsValid(); } + bool submitBlock(const CBlock& block_in, std::string& reason, std::string& debug) override + { + auto block = std::make_shared(block_in); + bool new_block; + const bool accepted = SubmitBlock(chainman(), block, &new_block, reason, debug); + // ProcessNewBlock() can accept and store a block before it is checked + // for validity. Treat duplicates as errors for mining clients, and only + // return success when validation completed without setting a reason. + return accepted && new_block && reason.empty(); + } + const NodeContext* context() override { return &m_node; } ChainstateManager& chainman() { return *Assert(m_node.chainman); } KernelNotifications& notifications() { return *Assert(m_node.notifications); } diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 2ddc1307cba..fd9559b543c 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -858,12 +858,20 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) block.hashMerkleRoot = BlockMerkleRoot(block); block.nNonce = bi.nonce; } - std::shared_ptr shared_pblock = std::make_shared(block); - // Alternate calls between Chainman's ProcessNewBlock and submitSolution - // via the Mining interface. The former is used by net_processing as well - // as the submitblock RPC. + // Alternate calls between submitBlock and submitSolution via the + // Mining interface. if (current_height % 2 == 0) { - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)); + std::string reason{"stale reason"}; + std::string debug{"stale debug"}; + BOOST_REQUIRE(mining->submitBlock(block, reason, debug)); + BOOST_REQUIRE_EQUAL(reason, ""); + BOOST_REQUIRE_EQUAL(debug, ""); + + reason = "stale reason"; + debug = "stale debug"; + BOOST_REQUIRE(!mining->submitBlock(block, reason, debug)); + BOOST_REQUIRE_EQUAL(reason, "duplicate"); + BOOST_REQUIRE_EQUAL(debug, ""); } else { BOOST_REQUIRE(block_template->submitSolution(block.nVersion, block.nTime, block.nNonce, MakeTransactionRef(txCoinbase))); }