Skip to content

Commit 74690f4

Browse files
Sjorsryanofsky
andcommitted
validation: refactor TestBlockValidity
Comments are expanded. Return BlockValidationState instead of passing a reference. Lock Chainman mutex instead of cs_main. Remove redundant chainparams and pindexPrev arguments. Drop defaults for checking proof-of-work and merkle root. The ContextualCheckBlockHeader check is moved to after CheckBlock, which is more similar to normal validation where context-free checks are done first. Validation failure reasons are no longer printed through LogError(), since it depends on the caller whether this implies an actual bug in the node, or an externally sourced block that happens to be invalid. When called from getblocktemplate, via BlockAssembler::CreateNewBlock(), this method already throws an std::runtime_error if validation fails. Additionally it moves the inconclusive-not-best-prevblk check from RPC code to TestBlockValidity. There is no behavior change when callling getblocktemplate with proposal. Previously this would return a BIP22ValidationResult which can throw for state.IsError(). But CheckBlock() and the functions it calls only use state.IsValid(). The final assert is changed into Assume, with a LogError. Co-authored-by: <Ryan Ofsky <[email protected]>
1 parent 5757de4 commit 74690f4

File tree

4 files changed

+94
-51
lines changed

4 files changed

+94
-51
lines changed

src/node/miner.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,10 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
179179
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
180180
pblock->nNonce = 0;
181181

182-
BlockValidationState state;
183-
if (m_options.test_block_validity && !TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev,
184-
/*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
185-
throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString()));
182+
if (m_options.test_block_validity) {
183+
if (BlockValidationState state{TestBlockValidity(m_chainstate, *pblock, /*check_pow=*/false, /*check_merkle_root=*/false)}; !state.IsValid()) {
184+
throw std::runtime_error(strprintf("TestBlockValidity failed: %s", state.ToString()));
185+
}
186186
}
187187
const auto time_2{SteadyClock::now()};
188188

src/rpc/mining.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,7 @@ static RPCHelpMan generateblock()
388388
block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
389389
RegenerateCommitments(block, chainman);
390390

391-
BlockValidationState state;
392-
if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
391+
if (BlockValidationState state{TestBlockValidity(chainman.ActiveChainstate(), block, /*check_pow=*/false, /*check_merkle_root=*/false)}; !state.IsValid()) {
393392
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
394393
}
395394
}
@@ -745,13 +744,7 @@ static RPCHelpMan getblocktemplate()
745744
return "duplicate-inconclusive";
746745
}
747746

748-
// TestBlockValidity only supports blocks built on the current Tip
749-
if (block.hashPrevBlock != tip) {
750-
return "inconclusive-not-best-prevblk";
751-
}
752-
BlockValidationState state;
753-
TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/true);
754-
return BIP22ValidationResult(state);
747+
return BIP22ValidationResult(TestBlockValidity(chainman.ActiveChainstate(), block, /*check_pow=*/false, /*check_merkle_root=*/true));
755748
}
756749

757750
const UniValue& aClientRules = oparam.find_value("rules");

src/validation.cpp

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4600,42 +4600,78 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef&
46004600
return result;
46014601
}
46024602

4603-
bool TestBlockValidity(BlockValidationState& state,
4604-
const CChainParams& chainparams,
4605-
Chainstate& chainstate,
4606-
const CBlock& block,
4607-
CBlockIndex* pindexPrev,
4608-
bool fCheckPOW,
4609-
bool fCheckMerkleRoot)
4603+
4604+
BlockValidationState TestBlockValidity(
4605+
Chainstate& chainstate,
4606+
const CBlock& block,
4607+
const bool check_pow,
4608+
const bool check_merkle_root)
46104609
{
4611-
AssertLockHeld(cs_main);
4612-
assert(pindexPrev && pindexPrev == chainstate.m_chain.Tip());
4613-
CCoinsViewCache viewNew(&chainstate.CoinsTip());
4614-
uint256 block_hash(block.GetHash());
4615-
CBlockIndex indexDummy(block);
4616-
indexDummy.pprev = pindexPrev;
4617-
indexDummy.nHeight = pindexPrev->nHeight + 1;
4618-
indexDummy.phashBlock = &block_hash;
4619-
4620-
// NOTE: CheckBlockHeader is called by CheckBlock
4621-
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev)) {
4622-
LogError("%s: Consensus::ContextualCheckBlockHeader: %s\n", __func__, state.ToString());
4623-
return false;
4610+
// Lock must be held throughout this function for two reasons:
4611+
// 1. We don't want the tip to change during several of the validation steps
4612+
// 2. To prevent a CheckBlock() race condition for fChecked, see ProcessNewBlock()
4613+
AssertLockHeld(chainstate.m_chainman.GetMutex());
4614+
4615+
BlockValidationState state;
4616+
CBlockIndex* tip{Assert(chainstate.m_chain.Tip())};
4617+
4618+
if (block.hashPrevBlock != *Assert(tip->phashBlock)) {
4619+
state.Invalid({}, "inconclusive-not-best-prevblk");
4620+
return state;
46244621
}
4625-
if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) {
4626-
LogError("%s: Consensus::CheckBlock: %s\n", __func__, state.ToString());
4627-
return false;
4622+
4623+
// For signets CheckBlock() verifies the challenge iff fCheckPow is set.
4624+
if (!CheckBlock(block, state, chainstate.m_chainman.GetConsensus(), /*fCheckPow=*/check_pow, /*fCheckMerkleRoot=*/check_merkle_root)) {
4625+
// This should never happen, but belt-and-suspenders don't approve the
4626+
// block if it does.
4627+
if (state.IsValid()) NONFATAL_UNREACHABLE();
4628+
return state;
46284629
}
4629-
if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev)) {
4630-
LogError("%s: Consensus::ContextualCheckBlock: %s\n", __func__, state.ToString());
4631-
return false;
4630+
4631+
/**
4632+
* At this point ProcessNewBlock would call AcceptBlock(), but we
4633+
* don't want to store the block or its header. Run individual checks
4634+
* instead:
4635+
* - skip AcceptBlockHeader() because:
4636+
* - we don't want to update the block index
4637+
* - we do not care about duplicates
4638+
* - we already ran CheckBlockHeader() via CheckBlock()
4639+
* - we already checked for prev-blk-not-found
4640+
* - we know the tip is valid, so no need to check bad-prevblk
4641+
* - we already ran CheckBlock()
4642+
* - do run ContextualCheckBlockHeader()
4643+
* - do run ContextualCheckBlock()
4644+
*/
4645+
4646+
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, tip)) {
4647+
if (state.IsValid()) NONFATAL_UNREACHABLE();
4648+
return state;
46324649
}
4633-
if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) {
4634-
return false;
4650+
4651+
if (!ContextualCheckBlock(block, state, chainstate.m_chainman, tip)) {
4652+
if (state.IsValid()) NONFATAL_UNREACHABLE();
4653+
return state;
46354654
}
4636-
assert(state.IsValid());
46374655

4638-
return true;
4656+
// We don't want ConnectBlock to update the actual chainstate, so create
4657+
// a cache on top of it, along with a dummy block index.
4658+
CBlockIndex index_dummy{block};
4659+
uint256 block_hash(block.GetHash());
4660+
index_dummy.pprev = tip;
4661+
index_dummy.nHeight = tip->nHeight + 1;
4662+
index_dummy.phashBlock = &block_hash;
4663+
CCoinsViewCache view_dummy(&chainstate.CoinsTip());
4664+
4665+
// Set fJustCheck to true in order to update, and not clear, validation caches.
4666+
if(!chainstate.ConnectBlock(block, state, &index_dummy, view_dummy, /*fJustCheck=*/true)) {
4667+
if (state.IsValid()) NONFATAL_UNREACHABLE();
4668+
return state;
4669+
}
4670+
4671+
// Ensure no check returned successfully while also setting an invalid state.
4672+
if (!state.IsValid()) NONFATAL_UNREACHABLE();
4673+
4674+
return state;
46394675
}
46404676

46414677
/* This function is called from the RPC code for pruneblockchain */

src/validation.h

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -384,14 +384,28 @@ class ValidationCache
384384
/** Context-independent validity checks */
385385
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true);
386386

387-
/** Check a block is completely valid from start to finish (only works on top of our current best block) */
388-
bool TestBlockValidity(BlockValidationState& state,
389-
const CChainParams& chainparams,
390-
Chainstate& chainstate,
391-
const CBlock& block,
392-
CBlockIndex* pindexPrev,
393-
bool fCheckPOW = true,
394-
bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
387+
/**
388+
* Verify a block, including transactions.
389+
*
390+
* @param[in] block The block we want to process. Must connect to the
391+
* current tip.
392+
* @param[in] chainstate The chainstate to connect to.
393+
* @param[in] check_pow perform proof-of-work check, nBits in the header
394+
* is always checked
395+
* @param[in] check_merkle_root check the merkle root
396+
*
397+
* @return Valid or Invalid state. This doesn't currently return an Error state,
398+
* and shouldn't unless there is something wrong with the existing
399+
* chainstate. (This is different from functions like AcceptBlock which
400+
* can fail trying to save new data.)
401+
*
402+
* For signets the challenge verification is skipped when check_pow is false.
403+
*/
404+
BlockValidationState TestBlockValidity(
405+
Chainstate& chainstate,
406+
const CBlock& block,
407+
bool check_pow,
408+
bool check_merkle_root) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
395409

396410
/** Check with the proof of work on each blockheader matches the value in nBits */
397411
bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams);

0 commit comments

Comments
 (0)