mining: add submitBlock IPC method to Mining interface
What changed, and why it matters
This commit adds a new internal 'submitBlock' method to Bitcoin Core's Mining interface, used by external mining software (like Stratum v2 servers) to submit fully assembled blocks for validation. It is a feature addition that mirrors the existing submitblock RPC. There is no direct evidence in the commit of a security vulnerability, but it exposes block submission over the IPC interface, which carries the same general risks as any block-submission path: a compromised or buggy caller could submit invalid or duplicate blocks. The implementation explicitly rejects duplicates and requires a complete block, including coinbase witness data when needed.
Review the IPC access controls for the Mining interface to ensure only authorized mining clients can call submitBlock. Verify that SubmitBlock and ProcessNewBlock handle untrusted block data safely (DoS limits, memory usage, validation cost). Consider fuzzing the new IPC method with malformed blocks. No immediate patch is indicated by the diff alone.
Security signals we found
New IPC-exposed block submission surface added
Implementation reuses existing SubmitBlock/ProcessNewBlock logic rather than introducing novel validation
Explicit duplicate rejection and requirement for complete coinbase witness
No mention of authentication, authorization, or sandboxing changes in the commit
No direct diff evidence of memory corruption, injection, or consensus bug
Evidence from the diff
The change extends the Mining IPC interface (C++ abstract class and Cap’n Proto schema) with submitBlock(), implemented in src/node/interfaces.cpp by wrapping SubmitBlock(chainman(), block, &new_block, reason, debug). Success is returned only when the block is accepted and new (accepted && new_block && reason.empty()). The test in miner_tests.cpp replaces direct ProcessNewBlock calls with alternating submitBlock/submitSolution calls and verifies duplicate detection. The implementation does not auto-fix missing coinbase witness reserved values, unlike the RPC path, so callers must supply a fully valid block.
Changed components
src/interfaces/mining.hsrc/ipc/capnp/mining.capnpsrc/node/interfaces.cppsrc/test/miner_tests.cppInspect captured patch +50 / −7
diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h
index a95bc220..ff4f8710 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 64cad4d4..a6dd8d71 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 28f8f2e8..2f68f414 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<const CBlock>(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 2ddc1307..fd9559b5 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<const CBlock> shared_pblock = std::make_shared<const CBlock>(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)));
}
Why this scored 23/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.