mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 14:53:43 +01:00
Merge bitcoin/bitcoin#31981: Add checkBlock() to Mining interface
a18e572328test: more template verification tests (Sjors Provoost)10c908808ftest: move gbt proposal mode tests to new file (Sjors Provoost)94959b8deeAdd checkBlock to Mining interface (Sjors Provoost)6077157531ipc: drop BlockValidationState special handling (Sjors Provoost)74690f4ed8validation: refactor TestBlockValidity (Sjors Provoost) Pull request description: This PR adds the IPC equivalent of the `getblocktemplate` RPC in `proposal` mode. In order to do so it has `TestBlockValidity` return error reasons as a string instead of `BlockValidationState`. This avoids complexity in IPC code for handling the latter struct. The new Mining interface method is used in `miner_tests`. It's not used by the `getblocktemplate` and `generateblock` RPC calls, see https://github.com/bitcoin/bitcoin/pull/31981#discussion_r2096473337 The `inconclusive-not-best-prevblk` check is moved from RPC code to `TestBlockValidity`. Test coverage is increased by `mining_template_verification.py`. Superseedes #31564 ## Background ### Verifying block templates (no PoW) Stratum v2 allows miners to generate their own block template. Pools may wish (or need) to verify these templates. This typically involves comparing mempools, asking miners to providing missing transactions and then reconstructing the proposed block.[^0] This is not sufficient to ensure a proposed block is actually valid. In some schemes miners could take advantage of incomplete validation[^1]. The Stratum Reference Implementation (SRI), currently the only Stratum v2 implementation, collects all missing mempool transactions, but does not yet fully verify the block.[^2]. It could use the `getblocktemplate` RPC in `proposal` mode, but using IPC is more performant, as it avoids serialising up to 4 MB of transaction data as JSON. (although SRI could use this PR, the Template Provider role doesn't need it, so this is _not_ part of #31098) [^0]: https://github.com/stratum-mining/sv2-spec/blob/main/06-Job-Declaration-Protocol.md [^1]: https://delvingbitcoin.org/t/pplns-with-job-declaration/1099/45?u=sjors [^2]: https://github.com/stratum-mining/stratum/blob/v1.1.0/roles/jd-server/src/lib/job_declarator/message_handler.rs#L196 ACKs for top commit: davidgumberg: reACKa18e572328achow101: ACKa18e572328TheCharlatan: ACKa18e572328ryanofsky: Code review ACKa18e572328just adding another NONFATAL_UNREACHABLE since last review Tree-SHA512: 1a6c29f45a1666114f10f55aed155980b90104db27761c78aada4727ce3129e6ae7a522d90a56314bd767bd7944dfa46e85fb9f714370fc83e6a585be7b044f1
This commit is contained in:
@@ -4600,42 +4600,78 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef&
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TestBlockValidity(BlockValidationState& state,
|
||||
const CChainParams& chainparams,
|
||||
Chainstate& chainstate,
|
||||
const CBlock& block,
|
||||
CBlockIndex* pindexPrev,
|
||||
bool fCheckPOW,
|
||||
bool fCheckMerkleRoot)
|
||||
|
||||
BlockValidationState TestBlockValidity(
|
||||
Chainstate& chainstate,
|
||||
const CBlock& block,
|
||||
const bool check_pow,
|
||||
const bool check_merkle_root)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
assert(pindexPrev && pindexPrev == chainstate.m_chain.Tip());
|
||||
CCoinsViewCache viewNew(&chainstate.CoinsTip());
|
||||
// Lock must be held throughout this function for two reasons:
|
||||
// 1. We don't want the tip to change during several of the validation steps
|
||||
// 2. To prevent a CheckBlock() race condition for fChecked, see ProcessNewBlock()
|
||||
AssertLockHeld(chainstate.m_chainman.GetMutex());
|
||||
|
||||
BlockValidationState state;
|
||||
CBlockIndex* tip{Assert(chainstate.m_chain.Tip())};
|
||||
|
||||
if (block.hashPrevBlock != *Assert(tip->phashBlock)) {
|
||||
state.Invalid({}, "inconclusive-not-best-prevblk");
|
||||
return state;
|
||||
}
|
||||
|
||||
// For signets CheckBlock() verifies the challenge iff fCheckPow is set.
|
||||
if (!CheckBlock(block, state, chainstate.m_chainman.GetConsensus(), /*fCheckPow=*/check_pow, /*fCheckMerkleRoot=*/check_merkle_root)) {
|
||||
// This should never happen, but belt-and-suspenders don't approve the
|
||||
// block if it does.
|
||||
if (state.IsValid()) NONFATAL_UNREACHABLE();
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* At this point ProcessNewBlock would call AcceptBlock(), but we
|
||||
* don't want to store the block or its header. Run individual checks
|
||||
* instead:
|
||||
* - skip AcceptBlockHeader() because:
|
||||
* - we don't want to update the block index
|
||||
* - we do not care about duplicates
|
||||
* - we already ran CheckBlockHeader() via CheckBlock()
|
||||
* - we already checked for prev-blk-not-found
|
||||
* - we know the tip is valid, so no need to check bad-prevblk
|
||||
* - we already ran CheckBlock()
|
||||
* - do run ContextualCheckBlockHeader()
|
||||
* - do run ContextualCheckBlock()
|
||||
*/
|
||||
|
||||
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, tip)) {
|
||||
if (state.IsValid()) NONFATAL_UNREACHABLE();
|
||||
return state;
|
||||
}
|
||||
|
||||
if (!ContextualCheckBlock(block, state, chainstate.m_chainman, tip)) {
|
||||
if (state.IsValid()) NONFATAL_UNREACHABLE();
|
||||
return state;
|
||||
}
|
||||
|
||||
// We don't want ConnectBlock to update the actual chainstate, so create
|
||||
// a cache on top of it, along with a dummy block index.
|
||||
CBlockIndex index_dummy{block};
|
||||
uint256 block_hash(block.GetHash());
|
||||
CBlockIndex indexDummy(block);
|
||||
indexDummy.pprev = pindexPrev;
|
||||
indexDummy.nHeight = pindexPrev->nHeight + 1;
|
||||
indexDummy.phashBlock = &block_hash;
|
||||
index_dummy.pprev = tip;
|
||||
index_dummy.nHeight = tip->nHeight + 1;
|
||||
index_dummy.phashBlock = &block_hash;
|
||||
CCoinsViewCache view_dummy(&chainstate.CoinsTip());
|
||||
|
||||
// NOTE: CheckBlockHeader is called by CheckBlock
|
||||
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev)) {
|
||||
LogError("%s: Consensus::ContextualCheckBlockHeader: %s\n", __func__, state.ToString());
|
||||
return false;
|
||||
// Set fJustCheck to true in order to update, and not clear, validation caches.
|
||||
if(!chainstate.ConnectBlock(block, state, &index_dummy, view_dummy, /*fJustCheck=*/true)) {
|
||||
if (state.IsValid()) NONFATAL_UNREACHABLE();
|
||||
return state;
|
||||
}
|
||||
if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) {
|
||||
LogError("%s: Consensus::CheckBlock: %s\n", __func__, state.ToString());
|
||||
return false;
|
||||
}
|
||||
if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev)) {
|
||||
LogError("%s: Consensus::ContextualCheckBlock: %s\n", __func__, state.ToString());
|
||||
return false;
|
||||
}
|
||||
if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) {
|
||||
return false;
|
||||
}
|
||||
assert(state.IsValid());
|
||||
|
||||
return true;
|
||||
// Ensure no check returned successfully while also setting an invalid state.
|
||||
if (!state.IsValid()) NONFATAL_UNREACHABLE();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/* This function is called from the RPC code for pruneblockchain */
|
||||
|
||||
Reference in New Issue
Block a user