Add checkBlock to Mining interface

Use it in miner_tests.

The getblocktemplate and generateblock RPC calls don't use this,
because it would make the code more verbose.
This commit is contained in:
Sjors Provoost 2025-05-20 09:25:03 +02:00
parent 6077157531
commit 94959b8dee
No known key found for this signature in database
GPG Key ID: 57FF9BDBCC301009
5 changed files with 81 additions and 1 deletions

View File

@ -114,6 +114,21 @@ public:
*/
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}) = 0;
/**
* Checks if a given block is valid.
*
* @param[in] block the block to check
* @param[in] options verification options: the proof-of-work check can be
* skipped in order to verify a template generated by
* external software.
* @param[out] reason failure reason (BIP22)
* @param[out] debug more detailed rejection reason
* @returns whether the block is valid
*
* For signets the challenge verification is skipped when check_pow is false.
*/
virtual bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) = 0;
//! Get internal node context. Useful for RPC and testing,
//! but not accessible across processes.
virtual node::NodeContext* context() { return nullptr; }

View File

@ -18,6 +18,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") {
getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool);
waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef);
createNewBlock @4 (options: BlockCreateOptions) -> (result: BlockTemplate);
checkBlock @5 (block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool);
}
interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
@ -44,3 +45,8 @@ struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") {
timeout @0 : Float64 $Proxy.name("timeout");
feeThreshold @1 : Int64 $Proxy.name("fee_threshold");
}
struct BlockCheckOptions $Proxy.wrap("node::BlockCheckOptions") {
checkMerkleRoot @0 :Bool $Proxy.name("check_merkle_root");
checkPow @1 :Bool $Proxy.name("check_pow");
}

View File

@ -984,6 +984,15 @@ public:
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
}
bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
{
LOCK(chainman().GetMutex());
BlockValidationState state{TestBlockValidity(chainman().ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root)};
reason = state.GetRejectReason();
debug = state.GetDebugMessage();
return state.IsValid();
}
NodeContext* context() override { return &m_node; }
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
KernelNotifications& notifications() { return *Assert(m_node.notifications); }

View File

@ -17,6 +17,7 @@
#include <cstddef>
#include <policy/policy.h>
#include <script/script.h>
#include <uint256.h>
#include <util/time.h>
namespace node {
@ -85,6 +86,17 @@ struct BlockWaitOptions {
CAmount fee_threshold{MAX_MONEY};
};
struct BlockCheckOptions {
/**
* Set false to omit the merkle root check
*/
bool check_merkle_root{true};
/**
* Set false to omit the proof-of-work check
*/
bool check_pow{true};
};
} // namespace node
#endif // BITCOIN_NODE_TYPES_H

View File

@ -22,6 +22,7 @@
#include <util/translation.h>
#include <validation.h>
#include <versionbits.h>
#include <pow.h>
#include <test/util/setup_common.h>
@ -666,7 +667,44 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
CScript scriptPubKey = CScript() << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"_hex << OP_CHECKSIG;
BlockAssembler::Options options;
options.coinbase_output_script = scriptPubKey;
std::unique_ptr<BlockTemplate> block_template;
// Create and check a simple template
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
{
CBlock block{block_template->getBlock()};
{
std::string reason;
std::string debug;
BOOST_REQUIRE(!mining->checkBlock(block, {.check_pow = false}, reason, debug));
BOOST_REQUIRE_EQUAL(reason, "bad-txnmrklroot");
BOOST_REQUIRE_EQUAL(debug, "hashMerkleRoot mismatch");
}
block.hashMerkleRoot = BlockMerkleRoot(block);
{
std::string reason;
std::string debug;
BOOST_REQUIRE(mining->checkBlock(block, {.check_pow = false}, reason, debug));
BOOST_REQUIRE_EQUAL(reason, "");
BOOST_REQUIRE_EQUAL(debug, "");
}
{
// A block template does not have proof-of-work, but it might pass
// verification by coincidence. Grind the nonce if needed:
while (CheckProofOfWork(block.GetHash(), block.nBits, Assert(m_node.chainman)->GetParams().GetConsensus())) {
block.nNonce++;
}
std::string reason;
std::string debug;
BOOST_REQUIRE(!mining->checkBlock(block, {.check_pow = true}, reason, debug));
BOOST_REQUIRE_EQUAL(reason, "high-hash");
BOOST_REQUIRE_EQUAL(debug, "proof of work failed");
}
}
// We can't make transactions until we have inputs
// Therefore, load 110 blocks :)