Add checkBlock() to Mining interface #31981

pull Sjors wants to merge 4 commits into bitcoin:master from Sjors:2025/03/checkBlock changing 15 files +500 −126
  1. Sjors commented at 11:04 am on March 4, 2025: member

    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 and the getblocktemplate and generateblock RPC calls, so it has test coverage.

    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.1 This is not sufficient to ensure a proposed block is actually valid. In some schemes miners could take advantage of incomplete validation2.

    The Stratum Reference Implementation (SRI), currently the only Stratum v2 implementation, collects all missing mempool transactions, but does not yet fully verify the block.3. 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)

  2. DrahtBot commented at 11:04 am on March 4, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31981.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK ismaelsadeeq
    Stale ACK ryanofsky

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #32317 (kernel: Separate UTXO set access from validation functions by TheCharlatan)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. Sjors commented at 11:39 am on March 4, 2025: member
    I also salvaged the functional tests from 15d841e41bb61f435dd3e60e99fa4db47d34a3a6.
  4. laanwj added the label Mining on Mar 4, 2025
  5. DrahtBot added the label Needs rebase on Mar 12, 2025
  6. Sjors force-pushed on Mar 13, 2025
  7. Sjors commented at 8:30 am on March 13, 2025: member
    Rebased after #31283.
  8. DrahtBot removed the label Needs rebase on Mar 13, 2025
  9. DrahtBot added the label Needs rebase on Mar 14, 2025
  10. Sjors force-pushed on Mar 14, 2025
  11. Sjors commented at 9:03 am on March 14, 2025: member
    Rebased after #31974 (trivial conflict with 3b33fd9424a2b4edbdc1745339e3541ab4b90af5).
  12. DrahtBot removed the label Needs rebase on Mar 14, 2025
  13. ryanofsky requested review from Copilot on Apr 8, 2025
  14. Copilot commented at 8:40 pm on April 8, 2025: none

    Copilot reviewed 13 out of 15 changed files in this pull request and generated 1 comment.

    • src/ipc/capnp/mining.capnp: Language not supported
    • src/test/ipc_test.capnp: Language not supported

    src/node/interfaces.cpp:1098

    • Correct the comment formatting by removing the ‘=’ in ‘/=check_merkle_root=/’ to read ‘/check_merkle_root=/’ for clarity.
    0return chainman().TestBlockValidity(block, reason, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root);
    
  15. in src/node/types.h:91 in e6e170cf6c outdated
    85@@ -85,6 +86,17 @@ struct BlockWaitOptions {
    86     CAmount fee_threshold{MAX_MONEY};
    87 };
    88 
    89+struct BlockCheckOptions {
    90+    /**
    91+     * Set false to omit the merkle root heck
    


    Copilot commented at 8:40 pm on April 8, 2025:

    Typo in the comment: change ‘heck’ to ‘check’.

    0     * Set false to omit the merkle root check
    

    l0rinc commented at 8:42 pm on April 8, 2025:
    Can we turn this off?

    maflcko commented at 6:38 am on April 9, 2025:

    Can we turn this off?

    Probably best discussed in a separate brainstorming issue, but if this can find typos without false positives before real review and before merge, it seems acceptable. Obviously it can’t do any real review and this is the first time I’ve seen it used, so I am likely missing downsides.


    ryanofsky commented at 4:47 pm on April 9, 2025:

    re: #31981 (review)

    I actually requested the review since I was curious what it would do. It seems a bit afraid to be opinionated though and might be more useful if we added guidelines https://docs.github.com/en/enterprise-cloud@latest/copilot/using-github-copilot/code-review/configuring-coding-guidelines


    Sjors commented at 11:04 am on April 25, 2025:
    Fixed typo since I had to retouch anyway.
  16. in src/rpc/mining.cpp:744 in 1d029c23a1 outdated
    746-            }
    747-            BlockValidationState state;
    748-            TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/true);
    749-            return BIP22ValidationResult(state);
    750+            std::string reason;
    751+            bool res{chainman.TestBlockValidity(block, reason, /*check_pow=*/false, /*check_merkle_root=*/true)};
    


    ryanofsky commented at 4:01 pm on April 9, 2025:

    In commit “validation: refactor TestBlockValidity” (1d029c23a143f1b9110377967a7c4a20ee75058d)

    it seems like there could be change in behavior here since BIP22ValidationResult would previously throw an exception on error and now it will just return an error string.


    Sjors commented at 11:04 am on April 25, 2025:

    Good point. The original code would return a string in some cases and throw an error in others. Although it’s possible nobody uses the getblocktemplate RPC in proposal mode at all, there could be (unmaintained) proprietary software out there using it. So the safest option is to maintain the behavior.

    However it seems there’s no change in behavior: BIP22ValidationResult only throws for state.IsError(), not for state.IsInvalid(). I looked through CheckBlock and the functions it calls and none of those can set state.IsError().

    I expanded the commit message to explain this.

  17. in src/rpc/mining.cpp:391 in 1d029c23a1 outdated
    385@@ -386,9 +386,9 @@ static RPCHelpMan generateblock()
    386         block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
    387         RegenerateCommitments(block, chainman);
    388 
    389-        BlockValidationState state;
    390-        if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
    391-            throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
    392+        std::string reason;
    393+        if (!chainman.TestBlockValidity(block, reason, /*check_pow=*/false, /*check_merkle_root=*/false)) {
    394+            throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", reason));
    


    ryanofsky commented at 4:10 pm on April 9, 2025:

    In commit “validation: refactor TestBlockValidity” (1d029c23a143f1b9110377967a7c4a20ee75058d)

    There seems to be a change in behavior here if ValidationState::m_debug_message is set because now it would previous be included in ToString output but now will be dropped.


    Sjors commented at 12:14 pm on April 25, 2025:
    I changed TestBlockValidity to also take a debug argument.
  18. in src/validation.cpp:4644 in 1d029c23a1 outdated
    4646-                       Chainstate& chainstate,
    4647-                       const CBlock& block,
    4648-                       CBlockIndex* pindexPrev,
    4649-                       bool fCheckPOW,
    4650-                       bool fCheckMerkleRoot)
    4651+bool ChainstateManager::TestBlockValidity(const CBlock& block, std::string& reason, const bool check_pow, const bool check_merkle_root)
    


    ryanofsky commented at 4:20 pm on April 9, 2025:

    In commit “validation: refactor TestBlockValidity” (1d029c23a143f1b9110377967a7c4a20ee75058d)

    Most of the changes here seem good, but there are a few things I think could be improved:

    • It seems better to me if this would return a BlockValidationState object instead of a string and a bool, so the valid/invalid/error states could be clearly distinguished, debug information could be returned, and this would fit in better with other validation.h functions using BlockValidationState. Returning BlockValidationState would also let callers use the BIP22ValidationResult univalue conversion function so we could reduce inconsistencies in how RPCs return validation information.
    • Also think it would be good if this function still took a chainstate parameter, since it shouldn’t care about which chainstate is active and could work on any chainstate.
    • I think it would be better if this remained a standalone function instead of becoming a ChainstateManager method since it shouldn’t need access to private ChainstateManager state and we might want to move it out of validation.cpp at some point to someplace like node/miner.cpp since it is only used by mining code.
    • Probably also better for it not to LOCK(cs_main) to avoid unnecessary recursive mutex locking.
    • Using Assert() consistently here would seem better than using a mix of Assert and Assume. Or if Assume() should be used it would be better to check return value and return an error status, instead of the ignoring failures in release builds.

    Made a commit with all these changes which looks like: bf15692ab9b5f8d68cb84e925c067caebe03efba (branch)


    Sjors commented at 11:15 am on April 25, 2025:
    Making this return a BlockValidationState means we have to pass it over the interface, which means we can’t drop the special handling, see cca5993b26e6223af31fe1ef5bf8a319cb87cf93.

    Sjors commented at 1:27 pm on April 25, 2025:

    None of the Assume checks seem problematic in production. They would just be inconsistent with (my understanding of) previous behavior. I dropped them, since none of the tests and fuzzers tripped over them.

    I dropped the !block.fChecked Assert. The remaining Assert`s are where we would crash anyway.

    Finally I changed the pre-existing assert at the end into an Assume along with a LogError.


    ryanofsky commented at 5:37 pm on May 13, 2025:

    re: #31981 (review)

    Making this return a BlockValidationState means we have to pass it over the interface, which means we can’t drop the special handling

    Agree it’s not good to pass BlockValidationState over the IPC interface (and the earlier change I posted didn’t do that). I just think it it’s best if validation.h/cpp uses BlockValidationState to be internally consistent and return the most information to callers.

    None of the Assume checks seem problematic in production.

    Thanks, I should have been clear my issue was just with unchecked Assume statements, not Assume statements in general. If unexpected states occur in releases I think it’s best to report them or return them to callers, not ignore them.

    re: #31981 (comment)

    Probably also better for it not to LOCK(cs_main) to avoid unnecessary recursive mutex locking.

    Can you elaborate on this?

    Not too important but there’s an effort to replace recursive mutexes with nonrecursive ones in #19303. Suggestion is just to replace the lock with a lock annotation.


    I do think current code as of c1939c43c3addb17c4316d49580762a1e0ec4504 is ok but that it would be nice to keep using BlockValidationState in validation code and return more complete error information. Here are the changes I’d suggest:

      0--- a/src/node/interfaces.cpp
      1+++ b/src/node/interfaces.cpp
      2@@ -1112,7 +1112,11 @@ public:
      3 
      4     bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
      5     {
      6-        return TestBlockValidity(chainman().ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root, reason, debug);
      7+        LOCK(chainman().GetMutex());
      8+        BlockValidationState state{TestBlockValidity(chainman().ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root)};
      9+        reason = state.GetRejectReason();
     10+        debug = state.GetDebugMessage();
     11+        return state.IsValid();
     12     }
     13 
     14     NodeContext* context() override { return &m_node; }
     15--- a/src/node/miner.cpp
     16+++ b/src/node/miner.cpp
     17@@ -173,10 +173,10 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
     18     pblock->nBits          = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
     19     pblock->nNonce         = 0;
     20 
     21-    std::string reason;
     22-    std::string debug;
     23-    if (m_options.test_block_validity && !TestBlockValidity(m_chainstate, *pblock, /*check_pow=*/false, /*check_merkle_root=*/false, reason, debug)) {
     24-        throw std::runtime_error(strprintf("TestBlockValidity failed: %s - %s", reason, debug));
     25+    if (m_options.test_block_validity) {
     26+        if (BlockValidationState state{TestBlockValidity(m_chainstate, *pblock, /*check_pow=*/false, /*check_merkle_root=*/false)}; !state.IsValid()) {
     27+            throw std::runtime_error(strprintf("TestBlockValidity failed: %s", state.ToString()));
     28+        }
     29     }
     30     const auto time_2{SteadyClock::now()};
     31 
     32--- a/src/rpc/mining.cpp
     33+++ b/src/rpc/mining.cpp
     34@@ -387,10 +387,8 @@ static RPCHelpMan generateblock()
     35         block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
     36         RegenerateCommitments(block, chainman);
     37 
     38-        std::string reason;
     39-        std::string debug;
     40-        if (!miner.checkBlock(block, {.check_merkle_root = false, .check_pow = false}, reason, debug)) {
     41-            throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s - %s", reason, debug));
     42+        if (BlockValidationState state{TestBlockValidity(chainman.ActiveChainstate(), block, /*check_pow=*/false, /*check_merkle_root=*/false)}; !state.IsValid()) {
     43+            throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
     44         }
     45     }
     46 
     47@@ -742,12 +740,7 @@ static RPCHelpMan getblocktemplate()
     48                 return "duplicate-inconclusive";
     49             }
     50 
     51-            std::string reason;
     52-            std::string debug;
     53-            bool res{miner.checkBlock(block, {.check_pow = false}, reason, debug)};
     54-            if (res) return UniValue::VNULL;
     55-            LogDebug(BCLog::RPC, "Invalid block: %s", debug);
     56-            return UniValue{reason};
     57+            return BIP22ValidationResult(TestBlockValidity(chainman.ActiveChainstate(), block, /*check_pow=*/false, /*check_merkle_root=*/true));
     58         }
     59 
     60         const UniValue& aClientRules = oparam.find_value("rules");
     61--- a/src/validation.cpp
     62+++ b/src/validation.cpp
     63@@ -4648,31 +4648,30 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef&
     64     return result;
     65 }
     66 
     67-bool TestBlockValidity(Chainstate& chainstate,
     68-                       const CBlock& block,
     69-                       const bool check_pow,
     70-                       const bool check_merkle_root,
     71-                       std::string& reason,
     72-                       std::string& debug)
     73+
     74+BlockValidationState TestBlockValidity(
     75+    Chainstate& chainstate,
     76+    const CBlock& block,
     77+    const bool check_pow,
     78+    const bool check_merkle_root)
     79 {
     80     // Lock must be held throughout this function for two reasons:
     81     // 1. We don't want the tip to change during several of the validation steps
     82     // 2. To prevent a CheckBlock() race condition for fChecked, see ProcessNewBlock()
     83-    LOCK(chainstate.m_chainman.GetMutex());
     84+    AssertLockHeld(chainstate.m_chainman.GetMutex());
     85 
     86     BlockValidationState state;
     87     CBlockIndex* tip{Assert(chainstate.m_chain.Tip())};
     88 
     89     if (block.hashPrevBlock != *Assert(tip->phashBlock)) {
     90-        reason = "inconclusive-not-best-prevblk";
     91-        return false;
     92+        state.Invalid({}, "inconclusive-not-best-prevblk");
     93+        return state;
     94     }
     95 
     96     // For signets CheckBlock() verifies the challenge iff fCheckPow is set.
     97     if (!CheckBlock(block, state, chainstate.m_chainman.GetConsensus(), /*fCheckPow=*/check_pow, /*fCheckMerkleRoot=*/check_merkle_root)) {
     98-        reason = state.GetRejectReason();
     99-        debug = state.GetDebugMessage();
    100-        return false;
    101+        if (state.IsValid()) state.Error("CheckBlock failed");
    102+        return state;
    103     }
    104 
    105     /**
    106@@ -4691,15 +4690,13 @@ bool TestBlockValidity(Chainstate& chainstate,
    107      */
    108 
    109     if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, tip)) {
    110-        reason = state.GetRejectReason();
    111-        debug = state.GetDebugMessage();
    112-        return false;
    113+        if (state.IsValid()) state.Error("ContextualCheckBlockHeader failed");
    114+        return state;
    115     }
    116 
    117     if (!ContextualCheckBlock(block, state, chainstate.m_chainman, tip)) {
    118-        reason = state.GetRejectReason();
    119-        debug = state.GetDebugMessage();
    120-        return false;
    121+        if (state.IsValid()) state.Error("ContextualCheckBlock failed");
    122+        return state;
    123     }
    124 
    125     // We don't want ConnectBlock to update the actual chainstate, so create
    126@@ -4716,16 +4713,17 @@ bool TestBlockValidity(Chainstate& chainstate,
    127 
    128     // Set fJustCheck to true in order to update, and not clear, validation caches.
    129     if(!chainstate.ConnectBlock(block, state, &index_dummy, view, /*fJustCheck=*/true)) {
    130-        reason = state.GetRejectReason();
    131-        debug = state.GetDebugMessage();
    132-        return false;
    133-    }
    134-    if (!Assume(state.IsValid())) {
    135-        LogError("Unexpected invalid validation state");
    136-        return false;
    137+        if (state.IsValid()) state.Error("ConnectBlock failed");
    138+        return state;
    139     }
    140 
    141-    return true;
    142+    // Ensure no check returned successfully while also setting an invalid state.
    143+    if (!Assume(state.IsValid())) {
    144+        LogError("Unexpected invalid state in TestBlockValidity: %s", state.ToString());
    145+        state.Error("TestBlockValidity failed");
    146+    }
    147+
    148+    return state;
    149 }
    150 
    151 /* This function is called from the RPC code for pruneblockchain */
    152--- a/src/validation.h
    153+++ b/src/validation.h
    154@@ -388,21 +388,23 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu
    155  *
    156  * [@param](/bitcoin-bitcoin/contributor/param/)[in]   block       The block we want to process. Must connect to the
    157  *                          current tip.
    158- * [@param](/bitcoin-bitcoin/contributor/param/)[in]   chainstate    The chainstate to connect to.
    159- * [@param](/bitcoin-bitcoin/contributor/param/)[out]  reason      rejection reason (BIP22)
    160- * [@param](/bitcoin-bitcoin/contributor/param/)[out]  debug       more detailed rejection reason
    161+ * [@param](/bitcoin-bitcoin/contributor/param/)[in]   chainstate  The chainstate to connect to.
    162  * [@param](/bitcoin-bitcoin/contributor/param/)[in]   check_pow   perform proof-of-work check, nBits in the header
    163  *                          is always checked
    164  * [@param](/bitcoin-bitcoin/contributor/param/)[in]   check_merkle_root check the merkle root
    165  *
    166+ * [@return](/bitcoin-bitcoin/contributor/return/) Valid or Invalid state. This doesn't currently return an Error state,
    167+ *         and shouldn't unless there is something wrong with the existing
    168+ *         chainstate. (This is different from functions like AcceptBlock which
    169+ *         can fail trying to save new data.)
    170+ *
    171  * For signets the challenge verification is skipped when check_pow is false.
    172  */
    173-bool TestBlockValidity(Chainstate& chainstate,
    174-                       const CBlock& block,
    175-                       const bool check_pow,
    176-                       const bool check_merkle_root,
    177-                       std::string& reason,
    178-                       std::string& debug);
    179+BlockValidationState TestBlockValidity(
    180+    Chainstate& chainstate,
    181+    const CBlock& block,
    182+    bool check_pow,
    183+    bool check_merkle_root) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    184 
    185 /** Check with the proof of work on each blockheader matches the value in nBits */
    186 bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams);
    
  19. ryanofsky approved
  20. ryanofsky commented at 5:00 pm on April 9, 2025: contributor
    Code review ACK e6e170cf6c67a56b9c14cece66fdc4fab5f3ec6b. Change is nicely implemented and background in the PR description is very helpful. Am a little concerned about possible changes in RPC behavior though and left some comments below.
  21. Sjors commented at 8:22 am on April 22, 2025: member
    Note to self, figure out if the sigops check needs to be fixed here or in another PR: https://github.com/bitcoin/bitcoin/pull/31624/files#r2053597806
  22. Sjors force-pushed on Apr 25, 2025
  23. Sjors commented at 12:16 pm on April 25, 2025: member

    Rebased, fixed typo found by LLM, clarified non behavior change for proposal mode.

    I added a debug argument to pass along more detailed failure reasons.

    I’m a bit on the fence regarding this suggestion: #31981 (review)

    figure out if the sigops check needs to be fixed here

    No it doesn’t. TestBlockValidity first calls CheckBlock which performs the limited sigops check documented in #31624. Later it calls ConnectBlock() which does the thorough check.

  24. maflcko commented at 12:49 pm on April 25, 2025: member

    I’ve implemented a typo check in DrahtBot and there are more, see #31981 (comment)

    Also, the CI is failing.

  25. DrahtBot added the label CI failed on Apr 25, 2025
  26. DrahtBot commented at 12:50 pm on April 25, 2025: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/41155656918

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  27. Sjors force-pushed on Apr 25, 2025
  28. Sjors commented at 1:27 pm on April 25, 2025: member

    Forgot to adjust the capnp file. Fixed more typos.

    I moved TestBlockValidity out of ChainstateManager again, and pass in a chainstate parameter as suggested in #31981 (review).

    Probably also better for it not to LOCK(cs_main) to avoid unnecessary recursive mutex locking.

    Can you elaborate on this?


    I also moved the TestBlockValidity arguments around a bit to reduce churn.

  29. Sjors force-pushed on Apr 25, 2025
  30. DrahtBot removed the label CI failed on Apr 25, 2025
  31. in src/validation.cpp:4662 in a0a6dbbe75 outdated
    4732+    index_dummy.nHeight = tip->nHeight + 1;
    4733+    index_dummy.phashBlock = &block_hash;
    4734+    CCoinsViewCache tip_view(&chainstate.CoinsTip());
    4735+    CCoinsView blockCoins;
    4736+    CCoinsViewCache view(&blockCoins);
    4737+    view.SetBackend(tip_view);
    


    davidgumberg commented at 4:56 pm on April 30, 2025:
    0    CCoinsViewCache view_dummy(&chainstate.CoinsTip());
    

    Sjors commented at 2:33 pm on May 14, 2025:
    I’m confused about what you’re suggesting here.
  32. in src/validation.cpp:4610 in c1939c43c3 outdated
    4673-    // NOTE: CheckBlockHeader is called by CheckBlock
    4674-    if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev)) {
    4675-        LogError("%s: Consensus::ContextualCheckBlockHeader: %s\n", __func__, state.ToString());
    4676+    // Lock must be held throughout this function for two reasons:
    4677+    // 1. We don't want the tip to change during several of the validation steps
    4678+    // 2. To prevent a CheckBlock() race condition for fChecked, see ProcessNewBlock()
    


    stringintech commented at 6:12 pm on April 30, 2025:
    Since TestBlockValidity is typically called with a fresh block instance, I’m not seeing how the race condition with fChecked could occur. Could you elaborate on this please?

    Sjors commented at 2:25 pm on May 14, 2025:

    I think you’re right that this can’t happen in the current codebase. TestBlockValidity is only called by checkBlock(). When this is called via the IPC interface, it will have a fresh CBlock each time (unless we start supporting shared memory between processes). Simiarly when called by the generateblock RPC or getblocktemplate in proposal mode it will have a fresh instance.

    The code in miner_tests.cpp does reuse the same CBlock across multiple calls, though not in parallel.

    Still, it seems like a good precaution against (perhaps unlikely) future regressions.


    stringintech commented at 6:28 pm on May 15, 2025:
    Thank you for the clarification.
  33. ismaelsadeeq commented at 3:33 pm on May 13, 2025: member
  34. Sjors commented at 3:43 pm on May 13, 2025: member
    Thanks. I still need to address the questions by @stringintech.
  35. in test/functional/mining_template_verification.py:41 in c1939c43c3 outdated
    36+class MiningTemplateVerificationTest(BitcoinTestFramework):
    37+
    38+    def set_test_params(self):
    39+        self.num_nodes = 1
    40+
    41+    def run_test(self):
    


    ryanofsky commented at 7:35 pm on May 13, 2025:

    In commit “test: more template verification tests” (c1939c43c3addb17c4316d49580762a1e0ec4504)

    Might be nice to split this test up into different methods since most of the checks seem independent. (Could pass in any objects they are sharing in common as arguments)


    Sjors commented at 3:13 pm on May 14, 2025:
    Done (though I didn’t put too much effort into making them actually independent).
  36. ryanofsky approved
  37. ryanofsky commented at 7:40 pm on May 13, 2025: contributor

    Code review ACK c1939c43c3addb17c4316d49580762a1e0ec4504. Since last review changes were improving error handling adding many comments, and making TestBlockValidity a standalone method again accepting a chainstate.

    I left another suggestion (and a diff) below to make TestBlockValidity return a BlockValidationState instead of a bool and strings, which I think would be better, but feel free to keep current approach if you prefer.

    Might also be nice to link to the review club discussion in the description (https://bitcoincore.reviews/31981)

  38. DrahtBot requested review from ismaelsadeeq on May 13, 2025
  39. Sjors force-pushed on May 14, 2025
  40. Sjors commented at 3:12 pm on May 14, 2025: member

    I took @ryanofsky’s patch to keep BlockValidationState in the interface implementation and validation code.

    Also split the test into multiple functions.

  41. 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 <ryan@ofsky.org>
    de0831f2ff
  42. ipc: drop BlockValidationState special handling
    The Mining interface avoids using BlockValidationState.
    e190bcb6f4
  43. Add checkBlock to Mining interface
    And use it in miner_tests, getblocktemplate and generateblock.
    116a9d2dac
  44. test: more template verification tests c759ec1256
  45. Sjors force-pushed on May 14, 2025
  46. Sjors commented at 5:09 pm on May 14, 2025: member

    Rebased to include the new comment added in #31624.

    The tests I added don’t cover the difference in sigops counting. Suggestions are welcome, it could look for “CheckBlock failed” vs “ConnectBlock failed”. In any case this could be a followup.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-05-19 18:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me