diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index edb73628484..28f8f2e88ee 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -920,7 +920,9 @@ public: bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) override { AddMerkleRootAndCoinbase(m_block_template->block, std::move(coinbase), version, timestamp, nonce); - return chainman().ProcessNewBlock(std::make_shared(m_block_template->block), /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr); + std::string reason; + std::string debug; + return SubmitBlock(chainman(), std::make_shared(m_block_template->block), /*new_block=*/nullptr, reason, debug); } std::unique_ptr waitNext(BlockWaitOptions options) override diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 5c0df502e83..ccd9cc7c57a 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -357,6 +358,62 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t block.fChecked = false; } +namespace { +class SubmitBlockStateCatcher final : public CValidationInterface +{ +public: + uint256 m_hash; + bool m_found{false}; + BlockValidationState m_state; + + explicit SubmitBlockStateCatcher(const uint256& hash) : m_hash{hash} {} + +protected: + void BlockChecked(const std::shared_ptr& block, const BlockValidationState& state) override + { + if (block->GetHash() != m_hash) return; + // ProcessNewBlock emits BlockChecked synchronously while holding cs_main, + // so SubmitBlock can read these fields after ProcessNewBlock returns + // without extra synchronization. + m_found = true; + m_state = state; + } +}; +} // namespace + +bool SubmitBlock(ChainstateManager& chainman, const std::shared_ptr& block, bool* new_block, std::string& reason, std::string& debug) +{ + reason.clear(); + debug.clear(); + + // This follows the submitblock RPC's validation-state capture pattern, but + // is intentionally kept separate from the RPC implementation. The RPC entry + // point decodes hex, formats BIP22/JSONRPC results, and calls + // UpdateUncommittedBlockStructures() for legacy witness handling. IPC + // callers submit already-formed blocks and need bool + reason/debug + // results, while submitSolution() preserves its duplicate-as-success + // behavior. + auto sc = std::make_shared(block->GetHash()); + CHECK_NONFATAL(chainman.m_options.signals)->RegisterSharedValidationInterface(sc); + bool accepted = chainman.ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/new_block); + CHECK_NONFATAL(chainman.m_options.signals)->UnregisterSharedValidationInterface(sc); + + if (new_block && !*new_block && accepted) { + reason = "duplicate"; + } else if (!sc->m_found) { + // A block can be accepted and stored without being connected, for + // example if it does not have more work than the current tip. In that + // case no BlockChecked callback is emitted, so the validation result is + // inconclusive. Mining::submitBlock treats this as an error for mining + // clients, but it does not mean the block is invalid. + reason = "inconclusive"; + } else if (!sc->m_state.IsValid()) { + reason = sc->m_state.GetRejectReason(); + debug = sc->m_state.GetDebugMessage(); + } + return accepted; +} + void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait) { LOCK(kernel_notifications.m_tip_block_mutex); diff --git a/src/node/miner.h b/src/node/miner.h index 14780833c02..af327307329 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -18,6 +18,7 @@ #include #include #include +#include #include class CBlockIndex; @@ -129,6 +130,9 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman); /* Compute the block's merkle root, insert or replace the coinbase transaction and the merkle root into the block */ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t version, uint32_t timestamp, uint32_t nonce); +//! Submit a block and capture the validation state via the BlockChecked callback. +//! Returns whether ProcessNewBlock accepted the block. +bool SubmitBlock(ChainstateManager& chainman, const std::shared_ptr& block, bool* new_block, std::string& reason, std::string& debug); /* Interrupt a blocking call. */ void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait);