Rework validation logic for assumeutxo #27746

pull sdaftuar wants to merge 13 commits into bitcoin:master from sdaftuar:2023-05-assumeutxo-validation-improvements changing 14 files +378 −254
  1. sdaftuar commented at 7:57 pm on May 24, 2023: member

    This PR proposes a clean up of the relationship between block storage and the chainstate objects, by moving the decision of whether to store a block on disk to something that is not chainstate-specific. Philosophically, the decision of whether to store a block on disk is related to validation rules that do not require any UTXO state; for anti-DoS reasons we were using some chainstate-specific heuristics, and those have been reworked here to achieve the proposed separation.

    This PR also fixes a bug in how a chainstate’s setBlockIndexCandidates was being initialized; it should always have all the HAVE_DATA block index entries that have more work than the chain tip. During startup, we were not fully populating setBlockIndexCandidates in some scenarios involving multiple chainstates.

    Further, this PR establishes a concept that whenever we have 2 chainstates, that we always know the snapshotted chain’s base block and the base block’s hash must be an element of our block index. Given that, we can establish a new invariant that the background validation chainstate only needs to consider blocks leading to that snapshotted block entry as potential candidates for its tip. As a followup I would imagine that when writing net_processing logic to download blocks for the background chainstate, that we would use this concept to only download blocks towards the snapshotted entry as well.

  2. jamesob commented at 8:05 pm on May 24, 2023: member
    Great! Thanks for this. I’m active in these neighborhoods today as well, since adding some -stopatheight/restart logic to the functional tests in #27596 has turned up some issues with CheckBlockIndex(), which are maybe related to some of the changes here. So I’ll be looking at this shortly.
  3. DrahtBot added the label Needs rebase on May 24, 2023
  4. sdaftuar force-pushed on May 24, 2023
  5. DrahtBot removed the label Needs rebase on May 24, 2023
  6. DrahtBot commented at 6:05 am on May 25, 2023: contributor

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

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK achow101, jamesob, Sjors, ryanofsky
    Concept ACK dergoegge
    Stale ACK fjahr, mzumsande

    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:

    • #28052 (blockstorage: XOR blocksdir *.dat files by MarcoFalke)
    • #27866 (blockstorage: Return on fatal flush errors by TheCharlatan)
    • #27460 (rpc: Add importmempool RPC by MarcoFalke)

    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.

  7. Sjors commented at 4:41 pm on May 31, 2023: member

    The first two commits up to f50fa248f1d904be5f33a29c837f74a2cca8abc0 look good to me.

    How do you see the division of labor between ChainstateManager and BlockManager? The ChainState doc currently says:

    Anything that is contingent on the current tip of the chain is stored here, whereas block information and metadata independent of the current tip is kept in BlockManager.

    In any case moving AcceptBlock from Chainstate to ChainstateManager seems an improvement.

  8. in src/validation.h:969 in 534b6f99a3 outdated
    941+    /** Decreasing counter (used by subsequent preciousblock calls). */
    942+    int32_t nBlockReverseSequenceId = -1;
    943+    /** chainwork for the last block that preciousblock has been applied to. */
    944+    arith_uint256 nLastPreciousChainwork = 0;
    945+
    946+    void ResetBlockSequenceCounters() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    


    Sjors commented at 6:43 pm on May 31, 2023:
    534b6f99a3779465678f39ed2704da6049c6469d: this function seems to come out of nowhere and is only used in a test.

    sdaftuar commented at 5:06 pm on June 13, 2023:
    Split this out into its own commit.
  9. Sjors commented at 6:44 pm on May 31, 2023: member
    If you can split 534b6f99a3779465678f39ed2704da6049c6469d in ~half that would make it a bit easier to review. Maybe start with anything that’s trivial to move, and then do the more involved changes in the second commit.
  10. in src/validation.cpp:3419 in 534b6f99a3 outdated
    3446+    // that build on blocks for which we actually have all the data, while the
    3447+    // snapshot chainstate (if we have one) is able to build on blocks that are
    3448+    // missing but for which BLOCK_ASSUME_VALID is set.
    3449+    // So this is broken right now. XXX
    3450+    AssertLockHeld(cs_main);
    3451+    if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
    


    Sjors commented at 6:48 pm on May 31, 2023:
    534b6f99a3779465678f39ed2704da6049c6469d: maybe use this refactor as an opportunity to add a comment explaining the somewhat obtuse line above

    sdaftuar commented at 5:06 pm on June 13, 2023:
    I think this should be a bit better now?
  11. in src/validation.cpp:3449 in 534b6f99a3 outdated
    3444+{
    3445+    // FIXME: the background-validation chainstate should only add new entries
    3446+    // that build on blocks for which we actually have all the data, while the
    3447+    // snapshot chainstate (if we have one) is able to build on blocks that are
    3448+    // missing but for which BLOCK_ASSUME_VALID is set.
    3449+    // So this is broken right now. XXX
    


    Sjors commented at 6:50 pm on May 31, 2023:

    534b6f99a3779465678f39ed2704da6049c6469d if it’s broken in master, can we fix it before refactoring? Alternatively there could be a This will be fixed in the next commit comment.

    I’m also confused by the comment. TryAddBlockIndexCandidate is only called from ReceivedBlockTransactions after it sets BLOCK_VALID_TRANSACTIONS. So are you saying we’re currently too strict? Was there a regression somewhere?


    sdaftuar commented at 5:27 pm on June 13, 2023:

    Sorry that comment was confusing. I dropped it, and instead change the behavior around managing setBlockIndexCandidates in the last two commits.

    I think master has a couple bugs. When we start up, in LoadBlockIndex() we weren’t adding blocks that have been downloaded and have more work than the background chainstate’s tip to setBlockIndexCandidates, which prevents those blocks from being validated and connected on startup. Also, because AcceptBlock() is a member of Chainstate, it’s at best confusing to reason about whether a block that would advance the tip of a chainstate would always be added to a chainstate’s setBlockIndexCandidates (it depends on yet-more logic around where a block came from and guessing about which chainstate might need it).

    The new code tries to simplify things by establishing that the background chainstate is only trying to connect blocks towards the snapshot block, and that all blocks for which we HAVE_DATA and have more work than the background chainstate’s tip (and which are ancestors of the snapshot block) will be added to the background chainstate’s setBlockIndexCandidates. Also, the logic bug in LoadBlockIndex should be fixed in the last commit here.


    ryanofsky commented at 9:37 pm on June 16, 2023:

    re: #27746 (review)e

    I think master has a couple bugs.

    I’m curious if there is actually more than one bug. The one bug I know about is the bug introduced 0fd599a from #23174 where LoadBlockIndex is misinterpreting the BLOCK_ASSUMED_VALID flag, and setting first_assumed_valid_height to the height of the first assumed valid block, instead of the height of the first block after the last assumed valid block (i.e. the snapshot height). I think it’s pretty bad that we didn’t catch this bug in review, or push for a more realistic test that would have caught it, or followed up on the suggestion from Marco #23174 (review) to just add blocks to setBlockIndexCandidates linearly, which is what this PR does, and is much more straightforward.

    Are there other known bugs? Are the changes to AcceptBlock meant to fix a bug or more to prevent a potential bug? Does the change to ChainstateManager::ReceivedBlockTransactions fix a bug? It seems like maybe it could prevent inappropriate blocks being added as candidates to the background chainstate, but it’s not clear.


    sdaftuar commented at 1:46 am on June 17, 2023:

    I guess the fairest thing to say is that the changes to AcceptBlock() are to prevent a future bug – it’s possible that we could write code that somehow gets it right, but the design of selectively adding blocks to one chainstate’s block index, and not the other, is one that I found very confusing.

    As an example, if you took #24008 (which proposed logic for determining which chainstate to invoke AcceptBlock() on) and combined that with changing the logic in AcceptBlock() so that the background chainstate wouldn’t bother processing blocks that are past the snapshot base, then that would be problematic if we were to allow pruning the snapshot-based-chain while the background sync is running (because according to the logic in #24008, if the block is already on the active chain, then we give it to the background chainstate to process). That’s 2 hypotheticals so certainly this is speculative, but I think that those changes would all make sense on their own, so the fact that they fail to compose with the code in master is a sign to me that we should take a different design.

  12. in src/validation.cpp:3452 in 534b6f99a3 outdated
    3447+    // snapshot chainstate (if we have one) is able to build on blocks that are
    3448+    // missing but for which BLOCK_ASSUME_VALID is set.
    3449+    // So this is broken right now. XXX
    3450+    AssertLockHeld(cs_main);
    3451+    if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
    3452+        LogPrintf("TryAddBlockIndexCandidate: %s is now a candidate (%s)\n", pindex->GetBlockHash().ToString(), (this == &m_chainman.ActiveChainstate() ? "active" : "inactive"));
    


    Sjors commented at 6:54 pm on May 31, 2023:
    534b6f99a3779465678f39ed2704da6049c6469d: "active" :"background"?

    sdaftuar commented at 5:28 pm on June 13, 2023:
    This LogPrintf() disappeared during my edits, so this is gone now.
  13. in src/validation.cpp:4603 in 534b6f99a3 outdated
    4646@@ -4629,7 +4647,14 @@ void Chainstate::LoadExternalBlockFile(
    4647                 // Activate the genesis block so normal node progress can continue
    4648                 if (hash == params.GetConsensus().hashGenesisBlock) {
    4649                     BlockValidationState state;
    4650-                    if (!ActivateBestChain(state, nullptr)) {
    4651+                    bool genesis_activation_failure = false;
    4652+                    for (auto c : GetAll()) {
    


    Sjors commented at 7:06 pm on May 31, 2023:
    534b6f99a3779465678f39ed2704da6049c6469d Previously LoadExternalBlockFile was only called on the ActiveChainstate(), now you’re looping over both. This is ok? If possible, it would be good to move the change to LoadExternalBlockFile into a separate commit.

    sdaftuar commented at 5:33 pm on June 13, 2023:

    If you’re asking if it’s ok to call ActivateBestChain() on both chainstates, the answer to that is essentially always “yes”. ABC() doesn’t do anything if no new candidates for most-work chain have been added to our block index; but if any block that has more work than our tip is now available we want to try to connect it.

    When calling LoadExternalBlockFile(), we’re adding blocks that theoretically could be of interest to either chainstate; so I think it makes sense to separate the block storage and import from any particular chainstate.

    I’m not sure how easy this would be to separate out into its own commit, because this invokes AcceptBlock() as well… Hopefully the commit is more manageable now that I’ve split a bunch of other stuff out? Let me know if you see a good way to shrink this commit more and I can take a stab at it.


    ryanofsky commented at 9:23 pm on July 3, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896) in LoadExternalBlockFile:

    It seems inconsistent to call ActivateBestChain on all chainstates here for the genesis block, but only call ActivateBestChain on the active chainstate a few lines below (line 4631).

    I think I can understand the reasons from the discussion above (“we’re adding blocks that theoretically could be of interest to either chainstate; so I think it makes sense to separate the block storage and import from any particular chainstate”) and from pruning context below. But in both cases I think the code would be clearer if there were explicit comments saying why ActiveChainstate() is used and why GetAll() is used in each place.


    ryanofsky commented at 10:52 pm on July 17, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (c8f4a8702851c3097ebd038a814d974dce4dcc78)

    It seems inconsistent to call ActivateBestChain on all chainstates here for the genesis block, but only call ActivateBestChain on the active chainstate a few lines below (line 4631).

    This inconsistency is resolved now in commit “Move block-storage-related logic to ChainstateManager” (c8f4a8702851c3097ebd038a814d974dce4dcc78) by calling ActivateBestChain on all chains in both cases.

    Since the case below is a pruning case, and pruning already has problems when snapshot activated and a background chainstate is being loaded, skipping the ActivateBestChain for the background chainstate probably did not cause any issues. But if pruning is going to be supported in the future, probably better if the code starts off treating chainstates the same and doesn’t have unexplained inconsistencies.

  14. glozow added the label Validation on Jun 2, 2023
  15. sdaftuar commented at 5:23 pm on June 5, 2023: member

    How do you see the division of labor between ChainstateManager and BlockManager? The ChainState doc currently says:

    Anything that is contingent on the current tip of the chain is stored here, whereas block information and metadata independent of the current tip is kept in BlockManager.

    I think of BlockManager as being a generic backend database of block (and undo) data, while any validation-related logic should live in Chainstate (if it relies on chainstate-specific information) or somewhere else (either a static function in validation or in ChainstateManager as I propose here) if not.

    I think AcceptBlock could also be a static function in validation.cpp; I mostly just wanted to get it out of Chainstate because I think that having block storage be coupled to a particular chainstate is a confusing idea, but I can go either way on whether it belongs in ChainstateManager or not.

  16. sdaftuar force-pushed on Jun 6, 2023
  17. DrahtBot added the label CI failed on Jun 6, 2023
  18. sdaftuar force-pushed on Jun 10, 2023
  19. in src/test/validation_chainstatemanager_tests.cpp:108 in df3655c12c outdated
    107 }
    108 
    109 //! Test rebalancing the caches associated with each chainstate.
    110+// FIXME: Need to rewrite this test under the requirement that snapshots always
    111+// correspond to blocks in the block index.
    112+#if 0
    


    achow101 commented at 9:27 pm on June 12, 2023:

    In 99e28b1a58b9864e4f393a2a065ef63f12eb8b85 “Tighten requirements for adding elements to setBlockIndexCandidates”

    Is this test case expected to fail with the changes in this commit or is it just expected to be incorrect? I tried re-enabling it and it passes.


    sdaftuar commented at 5:33 pm on June 13, 2023:
    Ah, thanks for noticing this. I believe I had an earlier version of this branch with assert()’s added in to ensure that the snapshot base was never nullptr, and so this test had been failing. I guess I removed those asserts before pushing… I’ve re-enabled the test for now, but really this test should be re-written so that snapshots are always at blocks in the block index.

    achow101 commented at 3:57 pm on June 14, 2023:

    this test should be re-written so that snapshots are always at blocks in the block index.

    Does it? AFAICT, it doesn’t test anything within the block index at all, and the only part that does is LoadGenesisBlock.


    sdaftuar commented at 4:03 pm on June 14, 2023:

    See https://github.com/bitcoin/bitcoin/pull/27746/files#diff-dbada1fe3a3d0af884304dd28be8c9df74b592401dec2c6400f6b491aefe6c9bL141

    0Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
    

    Trying to activate a snapshot for a block that is not in the block index ought to be an illegal operation.


    achow101 commented at 4:42 pm on June 14, 2023:

    Ah I see. Seems like this could be easily resolved by using TestChain100Setup

     0diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
     1index 2d0e1705fd..3d90bdcc26 100644
     2--- a/src/test/validation_chainstatemanager_tests.cpp
     3+++ b/src/test/validation_chainstatemanager_tests.cpp
     4@@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
     5 }
     6 
     7 //! Test rebalancing the caches associated with each chainstate.
     8-BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
     9+BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
    10 {
    11     ChainstateManager& manager = *m_node.chainman;
    12     CTxMemPool& mempool = *m_node.mempool;
    13@@ -118,7 +118,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
    14 
    15     // Create a legacy (IBD) chainstate.
    16     //
    17-    Chainstate& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool));
    18+    Chainstate& c1 = manager.ActiveChainstate();
    19     chainstates.push_back(&c1);
    20     c1.InitCoinsDB(
    21         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
    22@@ -126,8 +126,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
    23     {
    24         LOCK(::cs_main);
    25         c1.InitCoinsCache(1 << 23);
    26-        BOOST_REQUIRE(c1.LoadGenesisBlock());
    27-        c1.CoinsTip().SetBestBlock(InsecureRand256());
    28         manager.MaybeRebalanceCaches();
    29     }
    30 
    31@@ -136,7 +134,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
    32 
    33     // Create a snapshot-based chainstate.
    34     //
    35-    Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
    36+    CBlockIndex* snapshot_base{WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()[manager.ActiveChain().Height() / 2])};
    37+    Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, *snapshot_base->phashBlock));
    38     chainstates.push_back(&c2);
    39     c2.InitCoinsDB(
    40         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
    41@@ -144,8 +143,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
    42     {
    43         LOCK(::cs_main);
    44         c2.InitCoinsCache(1 << 23);
    45-        BOOST_REQUIRE(c2.LoadGenesisBlock());
    46-        c2.CoinsTip().SetBestBlock(InsecureRand256());
    47         manager.MaybeRebalanceCaches();
    48     }
    49 
    

    sdaftuar commented at 6:22 pm on June 15, 2023:
    Thank you! I took this and updated the commit where this test was changed.
  20. in src/test/validation_chainstatemanager_tests.cpp:354 in df3655c12c outdated
    350@@ -349,7 +351,7 @@ struct SnapshotTestSetup : TestChain100Setup {
    351         }
    352 
    353         // Snapshot should refuse to load after one has already loaded.
    354-        BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
    355+        //BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
    


    achow101 commented at 9:28 pm on June 12, 2023:

    In 99e28b1a58b9864e4f393a2a065ef63f12eb8b85 “Tighten requirements for adding elements to setBlockIndexCandidates”

    Is this check expected to fail? I tried uncommenting it and the test passes.


    sdaftuar commented at 5:34 pm on June 13, 2023:
    I have no idea why I commented this out – I’ve re-added as well.
  21. in src/test/validation_chainstatemanager_tests.cpp:416 in df3655c12c outdated
    412@@ -411,6 +413,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
    413 //!   chainstate only contains fully validated blocks and the other chainstate contains all blocks,
    414 //!   even those assumed-valid.
    415 //!
    416+#if 0
    


    achow101 commented at 9:31 pm on June 12, 2023:

    In 99e28b1a58b9864e4f393a2a065ef63f12eb8b85 “Tighten requirements for adding elements to setBlockIndexCandidates”

    This diff makes the test work, assuming that TryAddBlockIndexCandidate is working as it is supposed to be.

     0diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
     1index 8fab53c5c5..b9fe054a64 100644
     2--- a/src/test/validation_chainstatemanager_tests.cpp
     3+++ b/src/test/validation_chainstatemanager_tests.cpp
     4@@ -413,7 +411,6 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
     5 //!   chainstate only contains fully validated blocks and the other chainstate contains all blocks,
     6 //!   even those assumed-valid.
     7 //!
     8-#if 0
     9 BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
    10 {
    11     ChainstateManager& chainman = *Assert(m_node.chainman);
    12@@ -427,6 +424,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
    13     const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
    14 
    15     CBlockIndex* validated_tip{nullptr};
    16+    CBlockIndex* assumed_base{nullptr};
    17     CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
    18 
    19     auto reload_all_block_indexes = [&]() {
    20@@ -440,10 +438,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
    21         WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
    22     };
    23 
    24-    // Ensure that without any assumed-valid BlockIndex entries, all entries are considered
    25-    // tip candidates.
    26+    // Ensure that without any assumed-valid BlockIndex entries, only the current tip is
    27+    // considered as a candidate.
    28     reload_all_block_indexes();
    29-    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), cs1.m_chain.Height() + 1);
    30+    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
    31 
    32     // Mark some region of the chain assumed-valid.
    33     for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
    34@@ -462,27 +460,33 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
    35             validated_tip = index;
    36             BOOST_CHECK(!index->IsAssumedValid());
    37         }
    38+        // Note the block after the last assumed valid block as the snapshot base
    39+        if (i == last_assumed_valid_idx) {
    40+            assumed_base = index;
    41+            BOOST_CHECK(!index->IsAssumedValid());
    42+        }
    43     }
    44 
    45     BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
    46 
    47     Chainstate& cs2 = WITH_LOCK(::cs_main,
    48-        return chainman.ActivateExistingSnapshot(&mempool, GetRandHash()));
    49+        return chainman.ActivateExistingSnapshot(&mempool, *assumed_base->phashBlock));
    50+
    51+    // Set tip of the fully validated chain to be the validated tip
    52+    cs1.m_chain.SetTip(*validated_tip);
    53 
    54     reload_all_block_indexes();
    55 
    56-    // The fully validated chain only has candidates up to the start of the assumed-valid
    57-    // blocks.
    58+    // The fully validated chain should have the current validated tip, the assumed valid
    59+    // blocks, and the assumed valid base as candidates.
    60     BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
    61-    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_tip), 0);
    62-    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), assumed_valid_start_idx);
    63+    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), expected_assumed_valid + 2);
    64 
    65     // The assumed-valid tolerant chain has all blocks as candidates.
    66     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
    67     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
    68     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes);
    69 }
    70-#endif
    71 
    72 //! Ensure that snapshot chainstates initialize properly when found on disk.
    73 BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
    

    sdaftuar commented at 5:51 pm on June 13, 2023:
    Thanks, I took these changes and re-enabled the test.
  22. sdaftuar force-pushed on Jun 13, 2023
  23. sdaftuar commented at 5:54 pm on June 13, 2023: member
    @sjors @achow101 Thanks for the review! I split up the big commit as @sjors suggested, and with the test fixes I think this is ready to be moved out of Draft status.
  24. sdaftuar marked this as ready for review on Jun 13, 2023
  25. sdaftuar force-pushed on Jun 13, 2023
  26. sdaftuar renamed this:
    Draft: rework validation logic for assumeutxo
    Rework validation logic for assumeutxo
    on Jun 13, 2023
  27. in src/validation.h:1158 in ac3220a63a outdated
    1140+     * @param[out]  ppindex     Optional return parameter to get the
    1141+     *                          CBlockIndex pointer for this block.
    1142+     * @param[out]  fNewBlock   Optional return parameter to indicate if the
    1143+     *                          block is new to our storage.
    1144+     *
    1145+     * @returns   False is the block or header is invalid; true otherwise.
    


    mzumsande commented at 9:21 pm on June 13, 2023:
    typo: is -> if Also, another way for AcceptBlock() to return false is if the block/header are valid, but SaveBlockToDisk() failed for some reason (e.g. missing disk space).

    sdaftuar commented at 6:22 pm on June 15, 2023:
    Fixed.
  28. sdaftuar force-pushed on Jun 14, 2023
  29. DrahtBot removed the label CI failed on Jun 14, 2023
  30. in src/validation.cpp:3412 in e08dc8c14f outdated
    3413@@ -3414,7 +3414,17 @@ void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
    3414     AssertLockHeld(cs_main);
    3415     // If the block has more work than our tip, then it should be a candidate for most-work-chain.
    3416     if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
    3417-        setBlockIndexCandidates.insert(pindex);
    3418+        // The active chainstate should always add entries that have more work than the tip.
    3419+        // For the background chainstate, we only consider connecting blocks
    3420+        // towards the snapshot base.
    3421+        bool is_bg_chainstate = m_chainman.IsSnapshotActive() && m_chainman.IsBackgroundChainstate(this) && !m_disabled;
    


    mzumsande commented at 7:42 pm on June 14, 2023:
    how about inserting and returning here in the !is_bg_chainstate case, so that we can skip the GetAncestor() call when dealing with the snapshot chainstate?

    sdaftuar commented at 6:22 pm on June 15, 2023:
    Agreed, good idea.

    jamesob commented at 7:24 pm on June 21, 2023:

    https://github.com/bitcoin/bitcoin/pull/27746/commits/55e27ee995944d188d7453c6e78a6538f6739d64

    If you retouch, I think you can simply say bool is_bg_chainstate = this != &ActiveChainstate();


    sdaftuar commented at 1:22 pm on June 27, 2023:
    Good idea – and it looks like my code here is wrong anyway, because if we’re a disabled background chainstate, then we’ll be adding blocks to setBlockIndexCandidates as long as they have more work than the tip!
  31. in src/validation.cpp:5662 in 1880b5412e outdated
    5658@@ -5644,6 +5659,7 @@ Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uin
    5659         std::make_unique<Chainstate>(mempool, m_blockman, *this, base_blockhash);
    5660     LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString());
    5661     m_active_chainstate = m_snapshot_chainstate.get();
    5662+    m_ibd_chainstate->m_snapshot_entry = m_snapshot_chainstate->m_snapshot_entry;
    


    mzumsande commented at 9:58 pm on June 14, 2023:

    I don’t think this line does anything. It’s called during init from LoadChainstate() -> DetectSnapshotChainstate(), at which point m_snapshot_chainstate->m_snapshot_entry isn’t set yet. That only happens right after, in CompleteChainstateInitialization() -> LoadBlockIndex().

    The ctor of Chainstate also wouldn’t have set m_snapshot_entry for the same reason (the block index is loaded after the chainstate).


    sdaftuar commented at 6:23 pm on June 15, 2023:
    Good catch, removed this and left a comment there instead.
  32. mzumsande commented at 9:59 pm on June 14, 2023: contributor
    Concept ACK
  33. sdaftuar force-pushed on Jun 15, 2023
  34. bitcoinfinancier approved
  35. in src/node/blockstorage.h:129 in 6cc09f4759 outdated
    125@@ -126,6 +126,12 @@ class BlockManager
    126     RecursiveMutex cs_LastBlockFile;
    127     std::vector<CBlockFileInfo> m_blockfile_info;
    128     int m_last_blockfile = 0;
    129+    // Track the largest height block whose undo data is in m_last_blockfile.
    


    ryanofsky commented at 11:07 am on June 16, 2023:

    In commit “Explicitly track maximum block height stored in undo files” (6cc09f4759444218d1b493a1bcf44bb63f3160d2)

    When I first read this comment, I misinterpreted “whose undo data is in m_last_blockfile” to mean “whose undo data belongs in m_last_blockfile” as opposed to “whose undo data has already been written to m_last_blockfile”. So I was confused about how it was working. Would suggest changing this sentence to something like “Track the height of the highest block in m_last_blockfile whose undo data has been written.”

    I also don’t think if would hurt if there could be more explanation after this like: “Block data is written to block files in download order, but is written to undo files in validation order, which is usually in order by height. To avoid wasting disk space, undo files will be will trimmed whenever the corresponding block file is finalized and the height of the highest block written to the block file equals the height of the highest block written to the undo file. This is a heuristic and can sometimes preemptively trim undo files that will write more data later, and sometimes fail to trim undo files that can’t have more data written later.”


    sdaftuar commented at 1:22 am on June 17, 2023:
    Thanks – this is much better, took your language in daeba143a718c95b11432aed909781affe33efea.
  36. in src/validation.cpp:4373 in 2ebdfa8529 outdated
    4369@@ -4370,7 +4370,7 @@ bool Chainstate::NeedsRedownload() const
    4370 void Chainstate::UnloadBlockIndex()
    4371 {
    4372     AssertLockHeld(::cs_main);
    4373-    nBlockSequenceId = 1;
    4374+    m_chainman.nBlockSequenceId = 1;
    


    ryanofsky commented at 1:17 pm on June 16, 2023:

    In commit “Move block-arrival information / preciousblock counters to ChainstateManager” (2ebdfa8529b222de0db6d7114e9e5a1622fecfd0)

    Just IMO, but I think it would good to delete the m_chainman.nBlockSequenceId = 1 line in this commit instead of next commit “Add wrapper for adding entries to a chainstates block index candidates” (df6576d48e4daff2b9787fe7d2d254ffce2eb998). I also think it would also be good to rename UnloadBlockIndex to ClearBlockIndexCandidates here. Doing these things would leave the code in a less confusing state after this commit, even if it makes this diff a little bigger.


    sdaftuar commented at 1:29 pm on June 17, 2023:
    Done in latest push.
  37. in src/test/validation_chainstatemanager_tests.cpp:142 in b0b48b3e98 outdated
    133@@ -138,16 +134,15 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
    134 
    135     // Create a snapshot-based chainstate.
    136     //
    137-    Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
    


    ryanofsky commented at 4:39 pm on June 16, 2023:

    In commit “Tighten requirements for adding elements to setBlockIndexCandidates” (b0b48b3e986319fe8cd5a80052099070279ce5c0)

    Note for reviewers: A real hash is being passed instead of GetRandHash() now, because of discussion #27746 (review), because ActivateExistingSnapshot should not be expected to work when called with an invalid blockhash even if it does work now.

  38. in src/validation.cpp:4731 in bb47ea918c outdated
    4710@@ -4711,12 +4711,14 @@ void Chainstate::CheckBlockIndex()
    4711     CBlockIndex* pindexFirstNotTransactionsValid = nullptr; // Oldest ancestor of pindex which does not have BLOCK_VALID_TRANSACTIONS (regardless of being valid or not).
    4712     CBlockIndex* pindexFirstNotChainValid = nullptr; // Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN (regardless of being valid or not).
    4713     CBlockIndex* pindexFirstNotScriptsValid = nullptr; // Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS (regardless of being valid or not).
    4714+    CBlockIndex* pindexFirstAssumeValid = nullptr; // Oldest ancestor of pindex which has BLOCK_ASSUMED_VALID
    4715     while (pindex != nullptr) {
    4716         nNodes++;
    4717+        if (pindexFirstAssumeValid == nullptr && pindex->nStatus & BLOCK_ASSUMED_VALID) pindexFirstAssumeValid = pindex;
    


    ryanofsky commented at 6:08 pm on June 16, 2023:

    In commit “Update CheckBlockIndex invariants for snapshotted chains” (bb47ea918c19cd7063f529c0087bd156af18b499):

    I think this also needs to add if (pindex == pindexFirstAssumeValid) pindexFirstAssumeValid = nullptr; at line 4881 to reset pindexFirstAssumeValid when moving to a sibling.


    sdaftuar commented at 1:22 am on June 17, 2023:
    Fixed in 95680a6e90bbf27183dcf09b5c450494d356ebe5. Good catch, thanks. It seems unfortunate that this wasn’t caught by any unit tests; perhaps we should come up with a test case that will more fully exercise all of the CheckBlockIndex() logic?
  39. in src/validation.cpp:4725 in bb47ea918c outdated
    4716         nNodes++;
    4717+        if (pindexFirstAssumeValid == nullptr && pindex->nStatus & BLOCK_ASSUMED_VALID) pindexFirstAssumeValid = pindex;
    4718         if (pindexFirstInvalid == nullptr && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
    4719         // Assumed-valid index entries will not have data since we haven't downloaded the
    4720         // full block yet.
    4721-        if (pindexFirstMissing == nullptr && !(pindex->nStatus & BLOCK_HAVE_DATA) && !pindex->IsAssumedValid()) {
    


    ryanofsky commented at 8:03 pm on June 16, 2023:

    In commit “Update CheckBlockIndex invariants for snapshotted chains” (bb47ea918c19cd7063f529c0087bd156af18b499)

    Note: This change is partially reverting 8f5710f from #21526

  40. in src/validation.cpp:4719 in bb47ea918c outdated
    4710@@ -4711,12 +4711,14 @@ void Chainstate::CheckBlockIndex()
    4711     CBlockIndex* pindexFirstNotTransactionsValid = nullptr; // Oldest ancestor of pindex which does not have BLOCK_VALID_TRANSACTIONS (regardless of being valid or not).
    4712     CBlockIndex* pindexFirstNotChainValid = nullptr; // Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN (regardless of being valid or not).
    4713     CBlockIndex* pindexFirstNotScriptsValid = nullptr; // Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS (regardless of being valid or not).
    4714+    CBlockIndex* pindexFirstAssumeValid = nullptr; // Oldest ancestor of pindex which has BLOCK_ASSUMED_VALID
    4715     while (pindex != nullptr) {
    4716         nNodes++;
    4717+        if (pindexFirstAssumeValid == nullptr && pindex->nStatus & BLOCK_ASSUMED_VALID) pindexFirstAssumeValid = pindex;
    4718         if (pindexFirstInvalid == nullptr && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
    4719         // Assumed-valid index entries will not have data since we haven't downloaded the
    


    ryanofsky commented at 8:14 pm on June 16, 2023:

    In commit “Update CheckBlockIndex invariants for snapshotted chains” (bb47ea918c19cd7063f529c0087bd156af18b499)

    Should also delete this “Assumed-valid index entries will not have data” comment, I think, since it doesn’t really explain anything and no longer seems relevant here


    sdaftuar commented at 1:23 am on June 17, 2023:
    Good catch, that comment was incorrect (blocks retain their assumevalid status until they are validated, which can be some time after they are downloaded). Removed this comment in 95680a6e90bbf27183dcf09b5c450494d356ebe5.
  41. in src/validation.cpp:4849 in bb47ea918c outdated
    4844@@ -4837,7 +4845,12 @@ void Chainstate::CheckBlockIndex()
    4845             // So if this block is itself better than m_chain.Tip() and it wasn't in
    4846             // setBlockIndexCandidates, then it must be in m_blocks_unlinked.
    4847             if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) && setBlockIndexCandidates.count(pindex) == 0) {
    4848-                if (pindexFirstInvalid == nullptr) {
    4849+                if (pindexFirstInvalid == nullptr && pindexFirstAssumeValid == nullptr) {
    4850+                    // If this is a snapshotted chain, then this block could
    


    ryanofsky commented at 8:25 pm on June 16, 2023:

    In commit “Update CheckBlockIndex invariants for snapshotted chains” (bb47ea918c19cd7063f529c0087bd156af18b499)

    This is very pedantic but the phrase “snapshotted chain” here and in the commit description doesn’t really parse for me. It sounds like a chain a snapshot was created from, not a chain created from a snapshot. Could maybe replace “snapshotted chain” with “chain based on assumeutxo snapshot”


    sdaftuar commented at 1:23 am on June 17, 2023:

    Good point, fixed in this place in the code in 95680a6e90bbf27183dcf09b5c450494d356ebe5. Will try to fix up the commit descriptions when I squash/rebase.

    Edit: this is now fixed in the commit messages as well.

  42. in src/validation.cpp:4419 in f02125f1c7 outdated
    4442@@ -4446,9 +4443,6 @@ bool ChainstateManager::LoadBlockIndex()
    4443                 assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); }));
    4444                 assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); }));
    4445 
    4446-                first_assumed_valid_height = block->nHeight;
    


    ryanofsky commented at 8:54 pm on June 16, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f02125f1c7ba1c77c6d4ddb028999090fe5298eb)

    I think it would be good to drop this whole for-loop. The loop was originally added in 0fd599a from #23174 to compute first_assumed_valid_height. Adding the asserts there was really an afterthought, so keeping this loop just to make assertions about ChainStateManager doesn’t seem worth it. Dropping the loop and asserts would also allow dropping the reliesOnAssumedValid method, which would be nice to get rid of.


    sdaftuar commented at 1:27 am on June 17, 2023:

    Ah, ok – I’ve removed this in 1aebb99a10f9977e1d1bf4b923e2a2e984c76160.

    So, one thing I haven’t fixed yet is the reference to pindex->IsAssumedValid() just below this for loop:

     0
     1        for (CBlockIndex* pindex : vSortedByHeight) {
     2            if (ShutdownRequested()) return false;
     3            if (pindex->IsAssumedValid() ||
     4                    (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
     5                     (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
     6
     7                for (Chainstate* chainstate : GetAll()) {
     8                    chainstate->TryAddBlockIndexCandidate(pindex);
     9                }
    10            }
    

    Really, the assumed-valid status bit has nothing to do with whether a block index should be a most-work-chain candidate, except that right when a snapshot is activated, we need the base block to be added to setBlockIndexCandidates for the chain that is based on that snapshot. Probably I should fix this in this PR as well?

  43. in src/validation.cpp:3428 in b0b48b3e98 outdated
    3424+            // For the background chainstate, we only consider connecting blocks
    3425+            // towards the snapshot base (which can't be nullptr or else we'll
    3426+            // never make progress).
    3427+            assert(m_snapshot_entry != nullptr);
    3428+            bool ancestor_of_snapshot_base = (m_snapshot_entry->GetAncestor(pindex->nHeight) == pindex);
    3429+            if (ancestor_of_snapshot_base) {
    


    achow101 commented at 8:55 pm on June 16, 2023:

    In b0b48b3e986319fe8cd5a80052099070279ce5c0 “Tighten requirements for adding elements to setBlockIndexCandidates”

    nit: can inline

    0            if (m_snapshot_entry->GetAncestor(pindex->nHeight) == pindex) {
    

    sdaftuar commented at 1:21 am on June 17, 2023:
    Done in 0274b79285f781c3374678208dc3e00607907c7f
  44. achow101 commented at 9:16 pm on June 16, 2023: member
    ACK f02125f1c7ba1c77c6d4ddb028999090fe5298eb
  45. in src/validation.h:497 in d2ccbd3541 outdated
    490@@ -491,6 +491,13 @@ class Chainstate
    491     //! is set to true on the snapshot chainstate.
    492     bool m_disabled GUARDED_BY(::cs_main) {false};
    493 
    494+    void UpdateSnapshotBaseEntry(const CBlockIndex *entry) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    495+        { m_snapshot_entry = entry; }
    496+
    497+    //! Track the snapshot entry
    


    ryanofsky commented at 9:25 pm on June 16, 2023:

    In commit “Explicitly track snapshot block header when using multiple chainstates” (d2ccbd3541622147ab0d4b808a1b9388ddcd7948)

    I don’t think adding this Chainstate::m_snapshot_entry variable is a good idea. I don’t like how it has different meanings for different chainstates (starting point of validation for the snapshot chainstate, and ending point for the background chainstate), and the lack of documentation, and the complicated initialization which happens in the Chainstate constructor for the snapshot chainstate, but also in ActivateSnapshot for both chainstates, and then again in LoadBlockIndex for both chainstates, and pointedly not in ActivateExistingSnapshot according to a new comment there saying it will be initialized later… It just seems like a lot of complexity for new variable which is redundant with the existing m_from_snapshot_blockhash variable and is only used one place, in Chainstate::TryAddBlockIndexCandidate.

    I think it would be better if Chainstate::TryAddBlockIndexCandidate just used snapshot chainstate’s m_from_snapshot_blockhash value directly. Or if that is too inefficient, we could add a m_from_snapshot_blockindex member that mirrors m_from_snapshot_blockhash and is const and could be initialized straightforwardly in the ChainState constructor.


    jamesob commented at 11:06 pm on June 16, 2023:
    I think I concur with @ryanofsky here. I’ll have to double check, but in actual usage in #27596, I think I found that the one usage of this had another (existing) way of getting at this data. I’m afk for a few days, but will verify when I get back.

    sdaftuar commented at 1:19 am on June 17, 2023:

    I agree with your criticisms that the way this is written is pretty confusing (it took me a while to get it right in the first place, and I found this hard to reason about).

    I wanted to capture the idea that the hash m_from_snapshot_blockhash must always be in our block index, so that we never have to worry when we look it up in the block index that it might be missing, and we have to deal with a nullptr case.

    However, I couldn’t see how to make it a const that is straightforwardly initialized in Chainstate, because there are two different cases to consider (activating a snapshot for the first time, versus detecting that we have a snapshot on restart).

    Also, while this PR introduces one place where this CBlockIndex is used, I expect that we’ll use it again in the net_processing code which will download blocks toward the snapshot base.

    Here are a few options I can think of to improve this:

    • just look the hash up every time we need to use it (both in TryAddBlockIndexCandidate and wherever it is needed in net_processing, likely one additional place), avoiding the storage of redundant information
    • lazily fill it in so that we only look it up once, and then use the looked-up value for future requests
    • move the knowledge of the snapshot base to ChainstateManager, so that we don’t store redundant information in both chainstates (this could be done along with either of the first two ideas, I think?)

    EDIT: Gah, I just noticed ChainstateManager::GetSnapshotBaseBlock(), didn’t realize we already had that. I think I know what to do now to fix this!


    ryanofsky commented at 3:16 pm on June 17, 2023:

    re: #27746 (review)

    EDIT: Gah, I just noticed ChainstateManager::GetSnapshotBaseBlock(), didn’t realize we already had that. I think I know what to do now to fix this!

    Thanks, I didn’t realize how clunky ChainstateManager initialization was when I made that suggestion. Particularly how it allowed construction with that snapshot block that wasn’t actually in the index. I think initialization can definitely be simplified later. But even without doing that I experimented a little and came up with a change that adds CS::SnapshotBase() and CSM::ActiveSnapshotBase() methods that I think should provide a good interface for external code to use, while allowing us to clean up the internal representation and init process later:

      0--- a/src/validation.h
      1+++ b/src/validation.h
      2@@ -528,12 +528,8 @@ protected:
      3     //! is set to true on the snapshot chainstate.
      4     bool m_disabled GUARDED_BY(::cs_main) {false};
      5 
      6-    void UpdateSnapshotBaseEntry(const CBlockIndex *entry) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
      7-        { m_snapshot_entry = entry; }
      8-
      9-    //! Track the snapshot entry
     10-    const CBlockIndex* m_snapshot_entry GUARDED_BY(::cs_main) {nullptr};
     11-
     12+    //! Cached result of LookupBlockIndex(*m_from_snapshot_blockhash)
     13+    const CBlockIndex* m_cached_snapshot_base GUARDED_BY(::cs_main) {nullptr};
     14 
     15 public:
     16     //! Reference to a BlockManager instance which itself is shared across all
     17@@ -586,6 +582,13 @@ public:
     18      */
     19     const std::optional<uint256> m_from_snapshot_blockhash;
     20 
     21+    /**
     22+     * The base of the snapshot this chainstate was created from.
     23+     *
     24+     * nullptr if this chainstate was not created from a snapshot.
     25+     */
     26+    const CBlockIndex* SnapshotBase() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
     27+
     28     //! Return true if this chainstate relies on blocks that are assumed-valid. In
     29     //! practice this means it was created based on a UTXO snapshot.
     30     bool reliesOnAssumedValid() { return m_from_snapshot_blockhash.has_value(); }
     31@@ -1093,6 +1096,11 @@ public:
     32         return m_snapshot_chainstate && m_ibd_chainstate && m_ibd_chainstate->m_disabled;
     33     }
     34 
     35+    const CBlockIndex* ActiveSnapshotBase() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
     36+    {
     37+        return m_active_chainstate ? m_active_chainstate->SnapshotBase() : nullptr;
     38+    }
     39+
     40     /**
     41      * Import blocks from an external file
     42      *
     43--- a/src/validation.cpp
     44+++ b/src/validation.cpp
     45@@ -1579,9 +1579,13 @@ Chainstate::Chainstate(
     46       m_chainman(chainman),
     47       m_from_snapshot_blockhash(from_snapshot_blockhash)
     48 {
     49-    if (m_from_snapshot_blockhash) {
     50-        m_snapshot_entry = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(*m_from_snapshot_blockhash));
     51-    }
     52+}
     53+
     54+const CBlockIndex* Chainstate::SnapshotBase()
     55+{
     56+    if (!m_from_snapshot_blockhash) return nullptr;
     57+    if (!m_cached_snapshot_base) m_cached_snapshot_base = Assert(m_chainman.m_blockman.LookupBlockIndex(*m_from_snapshot_blockhash));
     58+    return m_cached_snapshot_base;
     59 }
     60 
     61 void Chainstate::InitCoinsDB(
     62@@ -3423,8 +3427,9 @@ void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
     63             // For the background chainstate, we only consider connecting blocks
     64             // towards the snapshot base (which can't be nullptr or else we'll
     65             // never make progress).
     66-            assert(m_snapshot_entry != nullptr);
     67-            if (m_snapshot_entry->GetAncestor(pindex->nHeight) == pindex) {
     68+            const CBlockIndex* snapshot_base{m_chainman.ActiveSnapshotBase()};
     69+            assert(snapshot_base != nullptr);
     70+            if (snapshot_base->GetAncestor(pindex->nHeight) == pindex) {
     71                 setBlockIndexCandidates.insert(pindex);
     72             }
     73         }
     74@@ -4416,14 +4421,6 @@ bool ChainstateManager::LoadBlockIndex()
     75         bool ret{m_blockman.LoadBlockIndexDB()};
     76         if (!ret) return false;
     77 
     78-        // If we already have 2 chainstates, then we need to update the
     79-        // snapshot base to point to the correct block entry.
     80-        if (IsSnapshotActive()) {
     81-            CBlockIndex *entry = m_blockman.LookupBlockIndex(*SnapshotBlockhash());
     82-            m_ibd_chainstate->UpdateSnapshotBaseEntry(entry);
     83-            m_snapshot_chainstate->UpdateSnapshotBaseEntry(entry);
     84-        }
     85-
     86         m_blockman.ScanAndUnlinkAlreadyPrunedFiles();
     87 
     88         std::vector<CBlockIndex*> vSortedByHeight{m_blockman.GetAllBlockIndices()};
     89@@ -5142,8 +5139,6 @@ bool ChainstateManager::ActivateSnapshot(
     90             m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
     91 
     92         this->MaybeRebalanceCaches();
     93-
     94-        m_ibd_chainstate->m_snapshot_entry = m_snapshot_chainstate->m_snapshot_entry;
     95     }
     96     return true;
     97 }
     98@@ -5623,8 +5618,6 @@ Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uin
     99         std::make_unique<Chainstate>(mempool, m_blockman, *this, base_blockhash);
    100     LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString());
    101     m_active_chainstate = m_snapshot_chainstate.get();
    102-    // Note: m_snapshot_entry will be populated after the block index is
    103-    // loaded; see ChainstateManager::LoadBlockIndex.
    104     return *m_snapshot_chainstate;
    105 }
    106 
    
  46. ryanofsky approved
  47. ryanofsky commented at 11:01 pm on June 16, 2023: contributor

    Partial code review ACK f02125f1c7ba1c77c6d4ddb028999090fe5298eb. I’ve looked at most of this, but not the whole thing yet, and all the changes seem very nice. This should help avoid bugs and make assumeutxo code more maintainable. I think it would be good to clean up documentation as part of this too. Here are documentation changes I would suggest:

     0--- a/doc/design/assumeutxo.md
     1+++ b/doc/design/assumeutxo.md
     2@@ -17,10 +17,9 @@ respectively generate and load UTXO snapshots. The utility script
     3 
     4 - A new block index `nStatus` flag is introduced, `BLOCK_ASSUMED_VALID`, to mark block
     5   index entries that are required to be assumed-valid by a chainstate created
     6-  from a UTXO snapshot. This flag is mostly used as a way to modify certain
     7+  from a UTXO snapshot. This flag is used as a way to modify certain
     8   CheckBlockIndex() logic to account for index entries that are pending validation by a
     9-  chainstate running asynchronously in the background. We also use this flag to control
    10-  which index entries are added to setBlockIndexCandidates during LoadBlockIndex().
    11+  chainstate running asynchronously in the background.
    12 
    13 - The concept of UTXO snapshots is treated as an implementation detail that lives
    14   behind the ChainstateManager interface. The external presentation of the changes
    15diff --git a/src/chain.h b/src/chain.h
    16index f5dd0fd31514..cb8bc7f04792 100644
    17--- a/src/chain.h
    18+++ b/src/chain.h
    19@@ -113,10 +113,10 @@ enum BlockStatus : uint32_t {
    20     BLOCK_VALID_TRANSACTIONS =    3,
    21 
    22     //! Outputs do not overspend inputs, no double spends, coinbase output ok, no immature coinbase spends, BIP30.
    23-    //! Implies all parents are also at least CHAIN.
    24+    //! Implies all parents are either at least VALID_CHAIN, or are ASSUMED_VALID
    25     BLOCK_VALID_CHAIN        =    4,
    26 
    27-    //! Scripts & signatures ok. Implies all parents are also at least SCRIPTS.
    28+    //! Scripts & signatures ok. Implies all parents are either at least VALID_SCRIPTS, or are ASSUMED_VALID.
    29     BLOCK_VALID_SCRIPTS      =    5,
    30 
    31     //! All validity bits.
    32@@ -134,10 +134,18 @@ enum BlockStatus : uint32_t {
    33     BLOCK_OPT_WITNESS        =   128, //!< block data in blk*.dat was received with a witness-enforcing client
    34 
    35     /**
    36-     * If set, this indicates that the block index entry is assumed-valid.
    37-     * Certain diagnostics will be skipped in e.g. CheckBlockIndex().
    38-     * It almost certainly means that the block's full validation is pending
    39-     * on a background chainstate. See `doc/design/assumeutxo.md`.
    40+     * If ASSUMED_VALID is set, it means that this block has not been validated
    41+     * and has validity status less than VALID_SCRIPTS. Also that it has
    42+     * descendant blocks with VALID_SCRIPTS set, because they were validated
    43+     * based on an assumeutxo snapshot.
    44+     *
    45+     * When an assumeutxo snapshot is loaded, the ASSUMED_VALID flag is added to
    46+     * unvalidated blocks below the snapshot height. Then, as the background
    47+     * validation progresses, and these blocks are validated, the ASSUMED_VALID
    48+     * flags are removed. See `doc/design/assumeutxo.md` for details.
    49+     *
    50+     * This flag is only used to implement checks in CheckBlockIndex() and
    51+     * should not be used elsewhere.
    52      */
    53     BLOCK_ASSUMED_VALID      =   256,
    54 };
    

    re: PR description #27746#issue-1724651759

    base block is: the block hash

    Grammar here seems off, maybe replace with “base block, and the base block hash”


    re: PR description #27746#issue-1724651759

    This code needs a lot of cleanup and test fixes and so I’ve marked it as a draft – help from reviewers would be welcome!

    Could drop this part since PR seems to be a good state to review and merge.


    re: #27746 (comment)

    I think AcceptBlock could also be a static function in validation.cpp

    IMO, would be nice make it a standalone function (static or not). Unless a function is really tied to one particular class or needs access to its private state, better for it just be standalone and use the public interface.

  48. sdaftuar force-pushed on Jun 17, 2023
  49. sdaftuar commented at 1:19 pm on June 17, 2023: member

    Thanks @ryanofsky for the detailed review. I’ve addressed many of the comments in fixup commits, which are tagged here for easier review: 27746.1. I’ve now squashed those fixups into place and pushed. Still outstanding for me to look at:

    • Move AcceptBlock to not be a member of ChainstateManager. I think you’re right that this is better if we pull it out of the class. EDIT: took a stab at this in 14e56b9, if this looks good I can integrate this earlier in the commit history.
    • Clean up the commits mentioned here: #27746 (review)
    • Resolve the issue mentioned here: #27746 (review)
    • Fix the usage of pindex->IsAssumedValid() that I mentioned here: #27746 (review) (done in 78fcc8fbdc510c03dd021e6c34a10e6b35fc2c83)
    • See if I can add some kind of test that would have caught the bug you mentioned in CheckBlockIndex here: #27746 (review)
  50. sdaftuar force-pushed on Jun 17, 2023
  51. sdaftuar force-pushed on Jun 17, 2023
  52. in src/test/blockmanager_tests.cpp:34 in 59b085f4d8 outdated
    27@@ -28,20 +28,20 @@ BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
    28     BlockManager blockman{blockman_opts};
    29     CChain chain {};
    30     // simulate adding a genesis block normally
    31-    BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
    32+    BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
    


    darosior commented at 1:05 pm on June 18, 2023:
    nit: CChain chain {}; above is now unused.

    sdaftuar commented at 4:41 pm on June 18, 2023:
    Fixed in 4a699011048e88099545a612075b5483ff55f533.
  53. sdaftuar commented at 4:48 pm on June 18, 2023: member

    I added a commit that moves CheckBlockIndex() from Chainstate to ChainstateManager and tightens up the sanity checks around setBlockIndexCandidates and mapBlocksUnlinked, to better match what is implemented in this PR, and reduce the places in CheckBlockIndex where we skip tests in the presence of assumed-valid blocks.

    There’s probably still room for improvement on the tests to better exercise all this logic (https://github.com/bitcoin/bitcoin/pull/27746#discussion_r1232922177), and I haven’t yet picked up the suggestion from @ryanofsky in #27746 (review), which would give us a performance optimization by caching lookups into the block index when adding blocks to the background chainstate’s setBlockIndexCandidates. I could use explicit feedback on: (a) whether the performance optimization is important enough that we should do it; (b) whether the movement of AcceptBlock out of ChainstateManager is an improvement; (c) whether additional tests should be added prior to merge (if so, I can work on it, but would also welcome help to write more tests!); and (d) whether moving CheckBlockIndex() into ChainstateManager should be done in this PR. With feedback on those items, I’ll go back and squash the fixup commits into place to clean up the branch.

  54. in src/validation.cpp:3419 in 55e27ee995 outdated
    3408@@ -3409,7 +3409,21 @@ void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
    3409     AssertLockHeld(cs_main);
    3410     // If the block has more work than our tip, then it should be a candidate for most-work-chain.
    3411     if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
    


    fjahr commented at 7:49 pm on June 18, 2023:
    Thanks for adding the comment. I would much prefer it if this line was a guard clause. Currently, it wraps the entire function body. I find guard clauses improve readability significantly in that case.

    sdaftuar commented at 4:23 pm on June 27, 2023:
    Can you explain a bit more what you have in mind for rewriting this function? Not sure I follow but happy to try to make this more readable/less error-prone.

    fjahr commented at 8:42 pm on June 27, 2023:

    sdaftuar commented at 7:21 pm on July 1, 2023:
    Thanks! I’ve incorporated that here.
  55. bitcoinfinancier approved
  56. in src/validation.cpp:3456 in 3110dbcee2 outdated
    3452@@ -3453,21 +3453,21 @@ void ChainstateManager::ReceivedBlockTransactions(const CBlock& block, CBlockInd
    3453             CBlockIndex *pindex = queue.front();
    3454             queue.pop_front();
    3455             pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
    3456-            pindex->nSequenceId = nBlockSequenceId++;
    3457-            for (Chainstate *c : GetAll()) {
    3458+            pindex->nSequenceId = chainman.nBlockSequenceId++;
    


    dergoegge commented at 10:06 am on June 19, 2023:
    nBlockSequenceId should only ever increase, so I would suggest wrapping this in a ChainstateManger::GetNewBlockSequenceId (or similar) and making nBlockSequenceId private.
  57. in src/validation.h:375 in 3110dbcee2 outdated
    370+ *
    371+ * @returns   False if the block or header is invalid, or if saving to disk fails (likely a fatal error); true otherwise.
    372+ */
    373+bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, ChainstateManager& chainman, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    374+
    375+void ReceivedBlockTransactions(const CBlock& block, ChainstateManager& chainman, CBlockIndex* pindexNew, const FlatFilePos& pos) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    


    dergoegge commented at 10:24 am on June 19, 2023:
    turbo nit: All params except chainman are related to the block you are passing in, so having the chainman in the middle feels a bit out of order. Would suggest placing it first or last.
  58. in src/validation.cpp:3819 in 3110dbcee2 outdated
    3815@@ -3816,13 +3816,13 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
    3816             // hasn't been validated up to BLOCK_VALID_SCRIPTS. This is a performance
    3817             // optimization, in the common case of adding a new block to the tip,
    3818             // we don't need to iterate over the failed blocks list.
    3819-            for (const CBlockIndex* failedit : m_failed_blocks) {
    3820+            for (const CBlockIndex* failedit : chainman.m_failed_blocks) {
    


    dergoegge commented at 10:28 am on June 19, 2023:
    Accessing m_failed_blocks directly doesn’t seem like a clean interface. I think it would be nicer if this entire for loop would be wrapped in a method on chainman.
  59. dergoegge commented at 11:02 am on June 19, 2023: member

    Concept ACK

    (b) whether the movement of AcceptBlock out of ChainstateManager is an improvement;

    I think this would make sense but if we are gonna pass ChainstateManager to static functions, then I think we should avoid reaching into its internals and use the available interfaces or define new ones. I left a couple comments about this inline.

    It might also be better to do this in a separate PR and stick to the assumeutxo related improvements in this one. There are likely a lot more interface related cleanups/improvements to be made around Chainstate and ChainstateManager that would benefit from isolated review.

  60. in src/node/blockstorage.cpp:618 in 59b085f4d8 outdated
    603@@ -604,7 +604,7 @@ fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
    604     return BlockFileSeq().FileName(pos);
    


    jamesob commented at 6:10 pm on June 21, 2023:

    (General note on https://github.com/bitcoin/bitcoin/pull/27746/commits/59b085f4d8bb5120c14b055ce394c06fe129dc60)

    Reviewers may be interested to take a look at a related future commit in #27596, https://github.com/bitcoin/bitcoin/pull/27596/commits/906ae54c11d873cfc1803078b9ce7822fc00bfd4, where I propose adding chainstate_role as a simple enum parameter to FindBlockPos() so that we may segment block storage by chainstate type.

  61. in src/node/chainstate.cpp:224 in 96cd3b0635 outdated
    220@@ -221,7 +221,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
    221 
    222         // A reload of the block index is required to recompute setBlockIndexCandidates
    223         // for the fully validated chainstate.
    224-        chainman.ActiveChainstate().UnloadBlockIndex();
    225+        chainman.ActiveChainstate().ClearBlockIndexCandidates();
    


    jamesob commented at 6:18 pm on June 21, 2023:
    No need to call ResetBlockSequenceCounters() here to preserve existing behavior?

    sdaftuar commented at 1:06 pm on June 27, 2023:

    It doesn’t seem necessary to me, because the block arrival information is something that seems unrelated to setting up a chainstate (and I don’t see why it would need to be reset in the event of a chainstate activation).

    I think if we need to reset the counters it would be for tests, which is the only place outside of init.cpp where we use this… I thought I addressed adding ResetBlockSequenceCounters() where it was necessary, but I should look at that again to see if I missed a case.


    ryanofsky commented at 11:36 pm on July 17, 2023:

    In commit “Move block-arrival information / preciousblock counters to ChainstateManager” (471da5f6e74bac71aeffe2ebc5faff145a6cbcea)

    The “reload of the block index” comment now seems out of date. And actually I don’t see why this ClearBlockIndexCandidates call is necessary here. After the InitializeChainstate call above, the active chainstate should be newly created and setBlockIndexCandidates should already be empty. So I think it would be good to drop this ClearBlockIndexCandidates call, or add a new comment to explain what it’s doing.


    sdaftuar commented at 2:04 pm on July 21, 2023:
    I agree that this seems redundant; the original call to UnloadBlockIndex() was added in #25740, and if I’m reading correctly I don’t think it should have been necessary then either. @jamesob Wondering if you agree, or if we’re overlooking something?
  62. in src/validation.cpp:3925 in 943bd665e2 outdated
    3921@@ -3920,13 +3922,13 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
    3922     // process an unrequested block if it's new and has enough work to
    3923     // advance our tip, and isn't too many blocks ahead.
    3924     bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
    3925-    bool fHasMoreOrSameWork = (m_chain.Tip() ? pindex->nChainWork >= m_chain.Tip()->nChainWork : true);
    3926+    bool fHasMoreOrSameWork = (ActiveChain().Tip() ? pindex->nChainWork >= ActiveChain().Tip()->nChainWork : true);
    


    jamesob commented at 7:04 pm on June 21, 2023:

    https://github.com/bitcoin/bitcoin/pull/27746/commits/943bd665e2523e5cd483aa2a77a511b967dee509

    If you happen to retouch, could ActiveChain().Tip() -> ActiveTip().


    sdaftuar commented at 4:21 pm on June 27, 2023:
    Agreed, done.
  63. in src/validation.cpp:3931 in 943bd665e2 outdated
    3928     // pruning, because pruning will not delete block files that contain any
    3929     // blocks which are too close in height to the tip.  Apply this test
    3930     // regardless of whether pruning is enabled; it should generally be safe to
    3931     // not process unrequested blocks.
    3932-    bool fTooFarAhead{pindex->nHeight > m_chain.Height() + int(MIN_BLOCKS_TO_KEEP)};
    3933+    bool fTooFarAhead{pindex->nHeight > ActiveChain().Height() + int(MIN_BLOCKS_TO_KEEP)};
    


    jamesob commented at 7:04 pm on June 21, 2023:

    sdaftuar commented at 4:21 pm on June 27, 2023:
    Done.
  64. in src/validation.cpp:3992 in 943bd665e2 outdated
    3989+    // chainstate (particularly if we haven't implemented pruning with
    3990+    // background validation yet).
    3991+    ActiveChainstate().FlushStateToDisk(state, FlushStateMode::NONE);
    3992 
    3993-    CheckBlockIndex();
    3994+    ActiveChainstate().CheckBlockIndex();
    


    jamesob commented at 7:07 pm on June 21, 2023:

    https://github.com/bitcoin/bitcoin/pull/27746/commits/943bd665e2523e5cd483aa2a77a511b967dee509

    Probably no need to use ActiveChainstate() for these (vs. this->) given the TODO above, but seems fine.


    ryanofsky commented at 2:34 am on July 18, 2023:

    re: #27746 (review)

    Probably no need to use ActiveChainstate() for these (vs. this->) given the TODO above, but seems fine.

    Marking resolved since later commit “Move CheckBlockIndex() from Chainstate to ChainstateManager” (f921887c1afdcd333cf71ca25e5efbf4991f806e) drops this.

  65. in src/validation.cpp:4637 in 943bd665e2 outdated
    4634                         break;
    4635                     }
    4636                 }
    4637 
    4638-                NotifyHeaderTip(*this);
    4639+                NotifyHeaderTip(ActiveChainstate());
    


    jamesob commented at 7:15 pm on June 21, 2023:
    Note: it seems like we can default to the active chainstate here because the underlying notification that this sends (KernelNotifications::headerTip()) isn’t really particular to any chainstate.

    sdaftuar commented at 1:46 pm on June 27, 2023:
    Yeah my thought was that this notification wasn’t something that made sense for the background chain – the existing UI notifications for should be oblivious to background validation (and if we want to change that then we likely need new functionality/a new interface for whatever notifications we want to set up).

    ryanofsky commented at 9:30 pm on July 3, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    It seems like NotifyHeaderTip is a “block-storage-related” function just like other functions that are moving from Chainstate to ChainstateManager so it would be cleaner if NotifyHeaderTip took a ChainstateManager argument instead of a Chainstate one, and if it called ActiveChainstate() internally so NotifyHeaderTip callers don’t have to consider how the UI reflects background validation.


    ryanofsky commented at 11:06 pm on July 17, 2023:

    re: #27746 (comment), #27746 (review)

    I wasn’t sure if that was something to do in this PR or a future one?

    I think it would be a good as a followup. Doing it wouldn’t simplify this PR so it’s probably good not to add it here.

  66. in src/validation.cpp:3422 in 55e27ee995 outdated
    3418+        } else {
    3419+            // For the background chainstate, we only consider connecting blocks
    3420+            // towards the snapshot base (which can't be nullptr or else we'll
    3421+            // never make progress).
    3422+            const CBlockIndex* snapshot_entry = m_chainman.GetSnapshotBaseBlock();
    3423+            assert(snapshot_entry != nullptr);
    


    jamesob commented at 7:26 pm on June 21, 2023:

    https://github.com/bitcoin/bitcoin/pull/27746/commits/55e27ee995944d188d7453c6e78a6538f6739d64

    If you retrouch, I think you could remove this line and just do ... snapshot_entry = Assert(m_chainman.GetSnapshotBaseBlock());

    Also could move the snapshot_entry initialization out of the loop if this gets slow for some reason.


    sdaftuar commented at 4:23 pm on June 27, 2023:
    Done (combined with taking @ryanofsky’s approach for caching this value).
  67. in src/validation.h:1054 in 55e27ee995 outdated
    1046@@ -1047,6 +1047,12 @@ class ChainstateManager
    1047     //!          that a background validation chainstate is also in use.
    1048     bool IsSnapshotActive() const;
    1049 
    1050+    //! @returns true if the chainstate is the background chainstate
    1051+    bool IsBackgroundChainstate(const Chainstate *chainstate) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    1052+    {
    1053+        return chainstate == m_ibd_chainstate.get();
    1054+    }
    


    jamesob commented at 7:29 pm on June 21, 2023:

    https://github.com/bitcoin/bitcoin/pull/27746/commits/55e27ee995944d188d7453c6e78a6538f6739d64

    Maybe not necessary in lieu of this != &ActiveChainstate()? Not hurting anything though.


    sdaftuar commented at 4:23 pm on June 27, 2023:
    Agreed, gone now.
  68. in src/chain.h:148 in 2d20f75b0b outdated
    147+     * unvalidated blocks below the snapshot height. Then, as the background
    148+     * validation progresses, and these blocks are validated, the ASSUMED_VALID
    149+     * flags are removed. See `doc/design/assumeutxo.md` for details.
    150+     *
    151+     * This flag is only used to implement checks in CheckBlockIndex() and
    152+     * should not be used elsewhere.
    


    jamesob commented at 7:34 pm on June 21, 2023:

    https://github.com/bitcoin/bitcoin/pull/27746/commits/2d20f75b0bad0b680cd45fc057c54704ac2a9d66

    Not entirely true since we have a small usage (by way of IsAssumedValid()) in LoadBlockIndex().

    Edit: oops nevermind, not as of the fixup commit: https://github.com/bitcoin/bitcoin/pull/27746/commits/78fcc8fbdc510c03dd021e6c34a10e6b35fc2c83

  69. jamesob commented at 7:53 pm on June 21, 2023: member

    ACK 4a699011048e88099545a612075b5483ff55f533 (jamesob/ackr/27746.1.sdaftuar.rework_validation_logic)

    Reviewed locally and built each commit.

    This change is a nice tightening up of the block index management code; the CheckBlockIndex() clarifications are welcome, not to mention fixing the buggy setBlockIndexCandidates initialization.

    The latent bugs I introduced in the original code here say a lot to me about the wisdom (or lack thereof) of introducing unused code littered over a number of years and PRs. I also wish I were smarter.


    The fixups need to be squashed in and I have a tiny question about the UnloadBlockIndex() change, but otherwise this is looking really good.

  70. DrahtBot requested review from achow101 on Jun 21, 2023
  71. DrahtBot requested review from ryanofsky on Jun 21, 2023
  72. in src/node/blockstorage.h:134 in 3fe223c36f outdated
    125@@ -126,6 +126,19 @@ class BlockManager
    126     RecursiveMutex cs_LastBlockFile;
    127     std::vector<CBlockFileInfo> m_blockfile_info;
    128     int m_last_blockfile = 0;
    129+
    130+    // Track the height of the highest block in m_last_blockfile whose undo
    131+    // data has been written. Block data is written to block files in download
    132+    // order, but is written to undo files in validation order, which is
    133+    // usually in order by height. To avoid wasting disk space, undo files will
    134+    // be will trimmed whenever the corresponding block file is finalized and
    


    fjahr commented at 3:46 pm on June 22, 2023:
    nit: be will => be

    sdaftuar commented at 4:24 pm on June 27, 2023:
    Fixed, thanks.
  73. in src/test/validation_chainstate_tests.cpp:80 in 55e27ee995 outdated
    76@@ -77,6 +77,13 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
    77     // After adding some blocks to the tip, best block should have changed.
    78     BOOST_CHECK(::g_best_block != curr_tip);
    79 
    80+    // Grab block 1 from disk; we'll add it back to the background chain later.
    


    fjahr commented at 9:11 pm on June 22, 2023:
    I think “back” isn’t technically correct here, it wasn’t in there before

    sdaftuar commented at 4:24 pm on June 27, 2023:
    Fixed.
  74. in src/validation.h:349 in 3110dbcee2 outdated
    344+ * block index (permanent memory storage), indicating that the header is
    345+ * known to be part of a sufficiently high-work chain (anti-dos check).
    346+ */
    347+bool AcceptBlockHeader(
    348+    const CBlockHeader& block,
    349+    ChainstateManager& chaiman,
    


    fjahr commented at 9:24 pm on June 22, 2023:
    chainman

    sdaftuar commented at 4:26 pm on June 27, 2023:
    No longer relevant now that AcceptBlock/AcceptBlockHeader are staying in CSM.
  75. in src/validation.h:357 in 3110dbcee2 outdated
    352+    bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    353+
    354+/**
    355+ * Sufficiently validate a block for disk storage (and store on disk).
    356+ *
    357+ * @param[in]   pblock          The block we want to process.
    


    fjahr commented at 9:25 pm on June 22, 2023:
    chainman is missing from the list of params

    sdaftuar commented at 4:26 pm on June 27, 2023:
    No longer relevant now that AcceptBlock is staying in CSM.
  76. fjahr commented at 10:01 pm on June 22, 2023: contributor

    Re your requests for feedback: a) Looks simple enough for me to be included here b) Overall I think it’s good but I agree with @dergoegge that the interface can be improved. But let’s keep that for a follow-up along with further cleanup that will probably follow when assumeutxo is merged. c) Will think about what could be adding value d) It seems to have already received some review here so I would vote to keep it in as well

    I will try to do some manual testing with this and the assumeutxo PR in the next days as well.

  77. luke-jr commented at 1:14 am on June 24, 2023: member
    Would prefer to keep refactoring and bugfixes in separate PRs, if there’s no reason they have to be together here?
  78. ryanofsky commented at 3:09 am on June 24, 2023: contributor

    Would prefer to keep refactoring and bugfixes in separate PRs, if there’s no reason they have to be together here?

    It’s an internal bug and the only way to trigger it is to use #27596 and restart the node during background snapshot validation. The bug was introduced by code in 0fd599a51a700c028d6f7ed8067d2d9f6e6a04a5 which was basically untested and broken, and the bug is fixed by c21624c14b83e30f32d5f8c653b3e64235625780 which depends on the earlier refactoring. Some more details about it are in #27746 (review)e

    There’s no good way to fix the bug without doing refactoring first, and probably no benefit to moving the bugfix to separate PR based on a refactoring PR, especially since the bug is internal and can’t actually be triggered without modifying current code.

  79. sdaftuar force-pushed on Jun 27, 2023
  80. sdaftuar force-pushed on Jun 27, 2023
  81. sdaftuar commented at 4:25 pm on June 27, 2023: member

    Thanks all for the additional review. I’ve updated based on feedback and cleaned up the commit history.

    would make sense but if we are gonna pass ChainstateManager to static functions, then I think we should avoid reaching into its internals and use the available interfaces or define new ones. I left a couple comments about this inline.

    It might also be better to do this in a separate PR and stick to the assumeutxo related improvements in this one. There are likely a lot more interface related cleanups/improvements to be made around Chainstate and ChainstateManager that would benefit from isolated review. @dergoegge I think I agree that this code movement would make sense in its own PR, so I decided to drop the movement of AcceptBlock out of ChainstateManager (and so therefore also ended up not taking the other changes you suggested about cleaning up the CSM interface, though I agree with you that those changes would make sense as well once AcceptBlock is moved out). @jamesob I think your comment here (https://github.com/bitcoin/bitcoin/pull/27746#discussion_r1237579876) highlighted a bug in the code; if the background chainstate was disabled then we’d add entries to setBlockIndexCandidates that we shouldn’t. I think this is fixed now! @fjahr Thanks for the review, I took a couple of the fixes, and just had a question about one of the suggestions.

  82. in src/validation.h:969 in 0dbc5805ca outdated
    979+    /** Decreasing counter (used by subsequent preciousblock calls). */
    980+    int32_t nBlockReverseSequenceId = -1;
    981+    /** chainwork for the last block that preciousblock has been applied to. */
    982+    arith_uint256 nLastPreciousChainwork = 0;
    983+
    984+    void ResetBlockSequenceCounters() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    


    ryanofsky commented at 2:56 pm on June 29, 2023:

    In commit “Move block-arrival information / preciousblock counters to ChainstateManager” (0dbc5805ca4371b7621bee0b7a553dac1a481614)

    It would be nice to have a documentation comment for this function saying what it is for, especially since it is only called in a test. If the counter values are basically arbitrary and only used for ordering, I’m not sure why if they actually ever need to be reset, or if that’s just a nice thing to do?


    sdaftuar commented at 9:09 pm on July 14, 2023:
    I think it’s just a nice thing to do to ensure that all state is reset when the block index is cleared out and re-initialized; it might be surprising if state were leftover? But I agree that it doesn’t seem strictly necessary. Added a comment to clarify.
  83. in src/validation.cpp:4385 in 0dbc5805ca outdated
    4366@@ -4367,10 +4367,9 @@ bool Chainstate::NeedsRedownload() const
    4367     return false;
    4368 }
    4369 
    4370-void Chainstate::UnloadBlockIndex()
    4371+void Chainstate::ClearBlockIndexCandidates()
    4372 {
    4373     AssertLockHeld(::cs_main);
    4374-    nBlockSequenceId = 1;
    


    ryanofsky commented at 3:06 pm on June 29, 2023:

    In commit “Move block-arrival information / preciousblock counters to ChainstateManager” (0dbc5805ca4371b7621bee0b7a553dac1a481614)

    IIUC removing this line is not a change in behavior because the nBlockSequenceId variable was never used again after the UnloadBlockIndex call, outside of tests. Now there is new ResetBlockSequenceCounters function that tests can use to reset it separately. EDIT: Another review comment #27746 (review) also seems to say resetting nBlockSequenceId is unnecessary outside of tests

    It might be good to say explicitly in commit message that this commit is supposed to be a refactoring which does not change behavior.


    sdaftuar commented at 9:10 pm on July 14, 2023:
    Updated the commit message.
  84. in src/test/util/chainstate.h:88 in e8168cd56c outdated
    83@@ -83,6 +84,18 @@ CreateAndActivateUTXOSnapshot(
    84             chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
    85             chain.LoadChainTip();
    86             node.chainman->MaybeRebalanceCaches();
    87+
    88+            // Reset the HAVE_DATA flags below the snapshot height, simulating
    


    ryanofsky commented at 3:40 pm on June 29, 2023:

    In commit “test: Clear block index flags when testing snapshots” (e8168cd56c490609f59e1dc89add58b1be18b516)

    This seems like a good change that makes test setup more realistic, but the could the commit message be updated to say what is motivating this change? Is it just for realism, or will a future change, or a certain kind of test depend on it?


    sdaftuar commented at 9:10 pm on July 14, 2023:

    I’d say mostly for realism. My intuition for how this is designed is that if you don’t reset the HAVE_DATA flags, then you’d immediately expect the background chain to process and validate all the blocks for which we HAVE_DATA and thus arrive at the original tip, as soon as you call ActivateBestChain().

    Updated commit message.

  85. in src/test/util/chainstate.h:100 in e8168cd56c outdated
    91+            // these blocks instead
    92+            CBlockIndex *pindex = orig_tip;
    93+            while (pindex && pindex != chain.m_chain.Tip()) {
    94+                pindex->nStatus &= ~BLOCK_HAVE_DATA;
    95+                pindex->nStatus &= ~BLOCK_HAVE_UNDO;
    96+                pindex->nStatus |= BLOCK_ASSUMED_VALID;
    


    ryanofsky commented at 3:46 pm on June 29, 2023:

    In commit “test: Clear block index flags when testing snapshots” (e8168cd56c490609f59e1dc89add58b1be18b516)

    Adding BLOCK_ASSUMED_VALID here seems surprising since I would expect ActivateSnapshot below to be setting BLOCK_ASSUMED_VALID, and comment above doesn’t mention this flag. I think it would be good to drop this line or add a comment explaining why it is necessary.


    sdaftuar commented at 9:11 pm on July 14, 2023:
    We need this due to the CheckBlockIndex() invariant that you can’t have a block index entry with nTx > 0 and without HAVE_DATA unless you’ve either pruned or used ASSUMED_VALID. Added a comment to clarify.
  86. in src/test/validation_chainstatemanager_tests.cpp:437 in 0dbc5805ca outdated
    430@@ -431,9 +431,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
    431     CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
    432 
    433     auto reload_all_block_indexes = [&]() {
    434+        WITH_LOCK(::cs_main, return chainman.ResetBlockSequenceCounters());
    


    ryanofsky commented at 3:54 pm on June 29, 2023:

    In commit “Move block-arrival information / preciousblock counters to ChainstateManager” (0dbc5805ca4371b7621bee0b7a553dac1a481614)

    The test seems to pass without this line, at least in this commit.

    If this is needed for a future change, would be good to say that in commit message. Or if this is just done for completeness would be good to say that in a code comment.


    sdaftuar commented at 9:11 pm on July 14, 2023:
    Added a comment.
  87. in src/validation.cpp:3478 in 034f920d17 outdated
    3438@@ -3439,8 +3439,10 @@ void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pin
    3439             CBlockIndex *pindex = queue.front();
    3440             queue.pop_front();
    3441             pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
    3442-            pindex->nSequenceId = m_chainman.nBlockSequenceId++;
    3443-            TryAddBlockIndexCandidate(pindex);
    3444+            pindex->nSequenceId = nBlockSequenceId++;
    3445+            for (Chainstate *c : GetAll()) {
    


    ryanofsky commented at 4:00 pm on June 29, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    Notes: It seems logical to call GetAll here for all chainstates and let the TryAddBlockIndexCandidate and FindMostWorkChain functions figure out how to use the block and which chain to attach it to.

    There is a change in behavior here if there are multiple chainstates, because previously this would only add blocks to setBlockIndexCandidates for the active chain, and now it will add all blocks with enough work to setBlockIndexCandidates for active and background chains.

    I think this means it will now add blocks above the snapshot height to the background chainstate, which ChainstateManager::LoadBlockIndex was trying to avoid. This is inefficient, but not really a problem because Chainstate::FindMostWorkChain will ignore and remove the blocks when it finds their ancestors have missing data.

    Followup commit f18ab9ee7deb92c25bdc68a5c4bef973f5e995f0 “Tighten requirements for adding elements to setBlockIndexCandidates” will make this more efficient by not adding these blocks to setBlockIndexCandidates

  88. in src/validation.cpp:3916 in 034f920d17 outdated
    3911@@ -3910,8 +3912,8 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
    3912     CBlockIndex *pindexDummy = nullptr;
    3913     CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy;
    3914 
    3915-    bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, min_pow_checked)};
    3916-    CheckBlockIndex();
    3917+    bool accepted_header{AcceptBlockHeader(block, state, &pindex, min_pow_checked)};
    3918+    ActiveChainstate().CheckBlockIndex();
    


    ryanofsky commented at 4:02 pm on June 29, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    Note: This change to the CheckBlockIndex() call is temporary and will revert back in later commit fb9c405506d6cfb6896e21b79789d7b6e638f6f2 “Move CheckBlockIndex() from Chainstate to ChainstateManager” when it becomes a ChainstateManager method.

    There is not a change in behavior here in this commit, because in both places where Chainstate::AcceptBlock was called previously (in ProcessNewBlock and LoadExternalBlockFile), it was only called on the active chainstate.

  89. in src/validation.cpp:4003 in 034f920d17 outdated
    3963@@ -3962,7 +3964,7 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
    3964 
    3965     // Header is valid/has work, merkle tree and segwit merkle tree are good...RELAY NOW
    3966     // (but if it does not build on our best tip, let the SendMessages loop relay it)
    3967-    if (!IsInitialBlockDownload() && m_chain.Tip() == pindex->pprev)
    3968+    if (!ActiveChainstate().IsInitialBlockDownload() && ActiveTip() == pindex->pprev)
    


    ryanofsky commented at 4:25 pm on June 29, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    Do you think it would be good if the IsInitialBlockDownload method were moved to ChainstateManager. Is there any place it should be actually called on the background chainstate rather than the active chainstate? Could add a TODO comment if IsInitialBlockDownload method should move like other methods are moving.


    sdaftuar commented at 2:27 pm on July 17, 2023:

    I haven’t done a careful review of all the places we use IsInitialBlockDownload() to confirm this, but my intuition would be that if we’re using an assumeutxo snapshot, what we care about is whether our snapshot-based chainstate is caught up or not – basically by definition, the background chainstate will always be “in initial block download” until it finishes and is no longer in use, so it wouldn’t make sense to condition anything on whether it’s behind or not.

    So yeah I think moving this to ChainstateManager would probably make sense…


    ryanofsky commented at 9:41 pm on July 17, 2023:

    re: #27746 (review)

    Thanks, it makes sense that the background chainstate only has one state because it is deleted as soon as it finishes syncing.

    Would be great if a followup PR moved IsInitialBlockDownload from Chainstate class to ChainstateManager so the IsInitialBlockDownload implementation could refer directly to the active chainstate, and IsInitialBlockDownload callers wouldn’t need to figure out which chainstate to call it on.

  90. in src/validation.cpp:4450 in c2bc5cfd99 outdated
    4440-        }
    4441-
    4442         for (CBlockIndex* pindex : vSortedByHeight) {
    4443             if (ShutdownRequested()) return false;
    4444-            if (pindex->IsAssumedValid() ||
    4445+            if (pindex == GetSnapshotBaseBlock() ||
    


    ryanofsky commented at 4:57 pm on June 29, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (c2bc5cfd9977838af334844675d77090fe9c3c74)

    Can add a comment here explaining the logic? I guess the pindex == GetSnapshotBaseBlock() case needs to be a special case because the snapshot block may not have transactions?

    Maybe also would be more efficient to call ActiveSnapshotBase instead of GetSnapshotBaseBlock here. I think I would move the commit introducing the ActiveSnapshotBase method earlier, so it’s also not necessary to replace GetSnapshotBaseBlock with ActiveSnapshotBase in later commit “Tighten requirements for adding elements to setBlockIndexCandidates” (ddb77d838c4cd7a4776c5ec70d96331fc8137900)


    sdaftuar commented at 2:44 pm on July 17, 2023:

    We may not have received the snapshot base block, and it’s only when we call ReceivedBlockTransactions on a block that we set VALID_TRANSACTIONS on the block index entry. So the snapshot base block may not otherwise be considered a block index candidate, but the snapshot base block could be the tip of the snapshot chain so we need to be able to call TryAddBlockIndexCandidate().

    I’ll push an update that adds a comment explaining this.

  91. sdaftuar force-pushed on Jul 1, 2023
  92. DrahtBot added the label CI failed on Jul 1, 2023
  93. in src/test/validation_chainstatemanager_tests.cpp:476 in 4a9f0dae7f outdated
    465+        // Note the last assumed valid block as the snapshot base
    466+        if (i == last_assumed_valid_idx - 1) {
    467             assumed_base = index;
    468-            BOOST_CHECK(!index->IsAssumedValid());
    469+            BOOST_CHECK(index->IsAssumedValid());
    470         }
    


    jamesob commented at 7:33 pm on July 3, 2023:

    4a9f0dae7ff64ba2c2f1eb1b44d0ca563726b776

    Nit: feel like it may have been okay to leave in the previous assertion too (although this is sorted of indirectly tested by the expected_assumed_valid comparison). No need to change.

     0diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
     1index a936a96577..f0ed54a521 100644
     2--- a/src/test/validation_chainstatemanager_tests.cpp
     3+++ b/src/test/validation_chainstatemanager_tests.cpp
     4@@ -464,6 +464,8 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
     5         if (i == last_assumed_valid_idx - 1) {
     6             assumed_base = index;
     7             BOOST_CHECK(index->IsAssumedValid());
     8+        } else if (i == last_assumed_valid_idx) {
     9+            BOOST_CHECK(!index->IsAssumedValid());
    10         }
    11     }
    

    sdaftuar commented at 9:09 pm on July 14, 2023:
    Done.
  94. jamesob approved
  95. jamesob commented at 8:34 pm on July 3, 2023: member

    ACK 03dfe04075668aa3b99a5d6d68adfcecffca0593 (jamesob/ackr/27746.3.sdaftuar.rework_validation_logic)

    Reexamined commits locally. Built, tested each (or most). Examined diff since last review.

    Changes include

    • removing BlockManager::AddDirty()
    • comment typo fixes
    • AcceptBlock() under ChainMan (vs. standalone function)
    • add Chainstate::SnapshotBase()
    • rework/additional commentary for Chainstate::TryAddBlockIndexCandidate
      • minor bugfix for disabled chainstates
    • put ReceivedBlockTransactions under ChainstateManager (vs. standalone)
    • leave AcceptBlockHeader() in place
    • Active*() rephrasings
    • move AcceptBlock() under ChainstateManager

    Not sure what’s up with the arm failure, but tests pass for me locally.

  96. in src/validation.h:1074 in 03dfe04075 outdated
    1064@@ -1055,6 +1065,11 @@ class ChainstateManager
    1065         return m_snapshot_chainstate && m_ibd_chainstate && m_ibd_chainstate->m_disabled;
    1066     }
    1067 
    1068+    const CBlockIndex* ActiveSnapshotBase() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    


    mzumsande commented at 8:47 pm on July 3, 2023:
    Now ChainstateManager has ActiveSnapshotBase() and GetSnapshotBaseBlock() which, as far as I understand it, do the same thing except for the caching. Would it have been possible to add the caching to the existing GetSnapshotBaseBlock() instead?

    jamesob commented at 2:37 pm on July 6, 2023:
    @mzumsande Yep, I don’t see any reason that couldn’t be done.

    sdaftuar commented at 9:09 pm on July 14, 2023:
    Done, thanks!
  97. in src/validation.cpp:3961 in 034f920d17 outdated
    3921@@ -3920,13 +3922,13 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
    3922     // process an unrequested block if it's new and has enough work to
    3923     // advance our tip, and isn't too many blocks ahead.
    3924     bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
    3925-    bool fHasMoreOrSameWork = (m_chain.Tip() ? pindex->nChainWork >= m_chain.Tip()->nChainWork : true);
    3926+    bool fHasMoreOrSameWork = (ActiveTip() ? pindex->nChainWork >= ActiveTip()->nChainWork : true);
    


    ryanofsky commented at 8:47 pm on July 3, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    It seems like fHasMoreOrSameWork will always be false for blocks that were downloaded to be added to the background chain, because they will have less work than the active snapshot chain.

    Is that intentional? It would helpful to have a comment here either that either explains why this ok, or adds a TODO indicating that there will be some change later.

    I do understand there is not a change in behavior here in this commit because in both places where Chainstate::AcceptBlock was called previously (in ProcessNewBlock and LoadExternalBlockFile), it was only called on the active chainstate, so it is still only looking at the active chainstate now.


    sdaftuar commented at 3:08 pm on July 17, 2023:

    The idea here is that this check is part of a heuristic that we use when we receive a block that was not requested. For anti-DoS reasons, back in #5875 we introduced logic to generally not process unrequested blocks, but we left in an optimization that was intended to not throw away blocks that would immediately advance our tip, in case we had some peer (like Matt’s old fast-relay-network, if I remember right) that was sending such blocks (see eg comment here #5875 (comment)).

    My view is that we don’t need to process unrequested blocks in general, and that if we do so it’s just an optimization to make block relay at the tip work better. We could probably add special-case logic to allow processing of unrequested blocks that we haven’t yet validated that are ancestors of the ancestor block, but this doesn’t strike me as something that is at all necessary (and perhaps not desirable?).


    ryanofsky commented at 10:43 pm on July 17, 2023:

    re: https://github.com/bitcoin/bitcoin/pull/27746/commits/c8f4a8702851c3097ebd038a814d974dce4dcc78#r1251271941

    Thanks, that makes sense, and thanks for linking to #5875.

    To make this less confusing, I think “Try to process all requested blocks” code comment above could be updated to mention the active chain and better match the code.

    Would suggest: “// Check all requested blocks that we do not already have for validity and save them to disk. Skip processing of unrequested blocks as an anti-Dos measure, unless the blocks have more work than the active chain tip, and aren’t too far ahead of it, so are likely to be attached soon.”


    Sjors commented at 3:49 pm on July 29, 2023:
    d0d40ea9a6478d81d7531b7cfc52a8bdaa0883d6 Maybe add: Background validation always ignores unrequested blocks.
  98. in src/validation.cpp:4517 in 034f920d17 outdated
    4517+void ChainstateManager::LoadExternalBlockFile(
    4518     FILE* fileIn,
    4519     FlatFilePos* dbp,
    4520     std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent)
    4521 {
    4522-    AssertLockNotHeld(m_chainstate_mutex);
    


    ryanofsky commented at 9:08 pm on July 3, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    Note: It seems fine to drop this assert, because the only code that is using this mutex is the ActivateBestChain function called below, and ActivateBestChain already has this same assert at the top of the function.

  99. in src/validation.cpp:4605 in 034f920d17 outdated
    4606@@ -4600,7 +4607,14 @@ void Chainstate::LoadExternalBlockFile(
    4607                 // Activate the genesis block so normal node progress can continue
    4608                 if (hash == params.GetConsensus().hashGenesisBlock) {
    4609                     BlockValidationState state;
    4610-                    if (!ActivateBestChain(state, nullptr)) {
    4611+                    bool genesis_activation_failure = false;
    4612+                    for (auto c : GetAll()) {
    4613+                        if (!c->ActivateBestChain(state, nullptr)) {
    


    ryanofsky commented at 9:11 pm on July 3, 2023:

    In commit “Move block-storage-related logic to ChainstateManager” (034f920d1755cff752d46b089ad37bdd703bf896)

    I think it would be a little clearer to move the BlockValidationState state; variable declaration down into the for loop so it is obvious the variable is ignored and not used to share state between ActivateBestChain calls

  100. mzumsande commented at 9:23 pm on July 3, 2023: contributor

    Code Review ACK 03dfe04075668aa3b99a5d6d68adfcecffca0593

    I reviewed the code again (didn’t look that deep into the test changes), and did some manual testing.

    Not sure what’s up with the arm failure, but tests pass for me locally.

    Pretty sure it’s unrelated, same thing just happened on master: https://github.com/bitcoin/bitcoin/runs/14733904119 (some discussion in #27988)

  101. ryanofsky approved
  102. ryanofsky commented at 9:51 pm on July 3, 2023: contributor
    Partial code review ACK 03dfe04075668aa3b99a5d6d68adfcecffca0593. I’ve looked in detail at almost all of this except for tests. It seems like a big improvement and I haven’t found any real issues at all. Am planning to review more soon.
  103. in src/node/blockstorage.cpp:753 in ad8b21dfe3 outdated
    734@@ -734,8 +735,9 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
    735         // the FindBlockPos function
    736         if (_pos.nFile < m_last_blockfile && static_cast<uint32_t>(block.nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
    737             FlushUndoFile(_pos.nFile, true);
    738+        } else if (_pos.nFile == m_last_blockfile && static_cast<uint32_t>(block.nHeight) > m_undo_height_in_last_blockfile) {
    


    fjahr commented at 2:05 pm on July 4, 2023:
    I must either be missing something about this line or it might be unnecessary. If I understand correctly this is updating m_undo_height_in_last_blockfile when the next block is connected after the flush happened. However, why not update it right away when the flush happens? I tried that by removing this line it seems like all tests are still passing without it.

    ryanofsky commented at 8:17 pm on July 4, 2023:

    I must either be missing something about this line or it might be unnecessary. If I understand correctly this is updating m_undo_height_in_last_blockfile when the next block is connected after the flush happened. However, why not update it right away when the flush happens? I tried that by removing this line it seems like all tests are still passing without it.

    It’s not surprising that there’s no test coverage for this. But to explain: the goal of the code is to flush the undo file when the height of the highest block in the undo file equals the height of the highest block in the corresponding block file. There are two cases where this can happen. The first case is on line 633 above, where the undo file and block file are flushed together because the undo data is not far behind the block data. The second case is on line 737 above when undo data is delayed and undo file gets flushed after the block file. The m_undo_height_in_last_blockfile needs to be set here so the m_blockfile_info[nFile].nHeightLast == m_undo_height_in_last_blockfile check in the first case line 633 can work.


    sdaftuar commented at 3:28 pm on July 17, 2023:
    For context, the issue was originally reported in #17890 and was fixed in #17994. It took me a while to understand exactly what this logic was for and how it worked, so a test to ensure we don’t introduce a regression would be helpful!
  104. fjahr commented at 4:19 pm on July 4, 2023: contributor

    Code review ACK 03dfe04075668aa3b99a5d6d68adfcecffca0593

    (modulo my question about the rev file management)

    I will also sync a node with this code and report if I see anything unusual in the coming days.

  105. DrahtBot added the label Needs rebase on Jul 6, 2023
  106. DrahtBot removed the label CI failed on Jul 7, 2023
  107. Explicitly track maximum block height stored in undo files
    When writing a new block to disk, if we have filled up the current block file,
    then we flush and truncate that block file (to free allocated but unused
    space) before advancing to the next one. When this happens, we have to
    determine whether to also flush and truncate the corresponding undo file.
    
    Undo data is only written when blocks are connected, not when blocks are
    received. Thus it's possible that the corresponding undo file already has all
    the data it will ever have, and we should flush/truncate it as we advance
    files; or it's possible that there is more data we expect to write, and should
    therefore defer flush/truncation until undo data is later written.
    
    Prior to this commit, we made the determination of whether the undo file was
    full of all requisite data by comparing against the chain tip. This patch
    replaces that dependence on validation data structures by instead just tracking
    the highest height of any block written in the undo file as we go.
    fe86a7cd48
  108. Remove CChain dependency in node/blockstorage 1cfc887d00
  109. sdaftuar force-pushed on Jul 14, 2023
  110. DrahtBot removed the label Needs rebase on Jul 14, 2023
  111. Move block-arrival information / preciousblock counters to ChainstateManager
    Block arrival information (and the preciousblock RPC, a related concept) are
    both chainstate-agnostic, so these are moved to ChainstateManager. This should
    just be a refactor, without any observable behavior changes.
    471da5f6e7
  112. Add wrapper for adding entries to a chainstate's block index candidates 10c05710ce
  113. Update CheckBlockIndex invariants for chains based on an assumeutxo snapshot 272fbc370c
  114. sdaftuar force-pushed on Jul 14, 2023
  115. test: Clear block index flags when testing snapshots
    When simulating a snapshot, remove the HAVE_DATA status for blocks below the
    snapshot height, to simulate never having downloaded them at all. This makes
    tests more realistic (and more closely match what will happen when using
    assumeutxo).
    3cfc75366e
  116. sdaftuar force-pushed on Jul 14, 2023
  117. jonatack commented at 1:07 am on July 15, 2023: contributor
    Initial commit-by-commit build/review looks good, will do a full review.
  118. sdaftuar force-pushed on Jul 17, 2023
  119. sdaftuar commented at 3:25 pm on July 17, 2023: member

    Just pushed a new branch incorporating feedback from all of @ryanofsky’s prior review. Regarding #27746 (review), I decided that it made the most sense to invoke ActivateBestChain() on both chainstates in LoadExternalBlockFile if pruning was enabled, even though I think it’ll take some more work to figure out how pruning should function in the event that we’re in the middle of assumeutxo-initiated background validation.

    Regarding the comment about NotifyHeaderTip() (https://github.com/bitcoin/bitcoin/pull/27746#discussion_r1251292140), I wasn’t sure if that was something to do in this PR or a future one?

  120. in src/test/validation_chainstate_tests.cpp:124 in eb8ce95a25 outdated
    120@@ -118,18 +121,18 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
    121     // once it is changed to support multiple chainstates.
    122     {
    123         LOCK(::cs_main);
    124-        bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
    125+        bool checked = CheckBlock(*pblockone, state, chainparams.GetConsensus());
    


    ryanofsky commented at 3:18 am on July 18, 2023:

    In commit “Tighten requirements for adding elements to setBlockIndexCandidates” (eb8ce95a2551fe521d00a5901ea773514395255b)

    Note: test here is changed to add the real block 1, instead of a random block, so the block will not be ignored by the new snapshot_base->GetAncestor(pindex->nHeight) == pindex check in TryAddBlockIndexCandidate

  121. in src/validation.cpp:3436 in eb8ce95a25 outdated
    3433         setBlockIndexCandidates.insert(pindex);
    3434+    } else if (!m_disabled) {
    3435+        // For the background chainstate, we only consider connecting blocks
    3436+        // towards the snapshot base (which can't be nullptr or else we'll
    3437+        // never make progress).
    3438+        const CBlockIndex* snapshot_base = Assert(m_chainman.GetSnapshotBaseBlock());
    


    ryanofsky commented at 3:24 am on July 18, 2023:

    In commit “Tighten requirements for adding elements to setBlockIndexCandidates” (eb8ce95a2551fe521d00a5901ea773514395255b)

    Is this comment in the commit message still accurate?

    Note that this introduces the new invariant that we can only have an assumeutxo snapshot where the snapshotted blockhash is in our block index. As a followup, unit tests that mock creating a snapshot ought to be updated to reflect this.

    The only way I can see that it relates to code changes in this commit is the new Assert on this line, and it’s confusing because I would have not have thought loading a snapshot with an unknown block hash worked before this code change, either. Also I’m not sure what unit tests “ought to be updated” means if tests are already updated this commit.

    I think it would be good to drop this comment, or add more specifics about what’s actually changing here and what followup needs to be done later.

    EDIT: Rereading this, I guess the commit message is just referring to an internal change that affects unit tests, not a bigger change that affects what snapshots are able to be loaded. Maybe a a clearer commit message would be: “After this change, ActivateExistingSnapshot needs to be passed a valid block hash to avoid assert errors when accepting new blocks.” Maybe it would be good to add an assert to ActivateExistingSnapshot to catch potential problems with this earlier, too.


    sdaftuar commented at 2:08 pm on July 21, 2023:

    I agree that it would be good to add an assert to catch this earlier, but we discussed before that due to the different ways that chainstates can be constructed and the order in which snapshot block hashes can be passed in (relative to when the block index is populated on startup), that there wasn’t a clear way to do this at an earlier point in the code.

    I’ll update the code comment though to make it more clear what will break if an invalid hash is passed in.

  122. in src/test/validation_chainstatemanager_tests.cpp:52 in eb8ce95a25 outdated
    48@@ -49,6 +49,9 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
    49     c1.InitCoinsDB(
    50         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
    51     WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
    52+    c1.LoadGenesisBlock();
    


    ryanofsky commented at 3:30 am on July 18, 2023:

    In commit “Tighten requirements for adding elements to setBlockIndexCandidates” (eb8ce95a2551fe521d00a5901ea773514395255b)

    Note: IIUC, it’s necessary to load the genesis block here so the ActivateExistingSnapshot call below can be passed a known block hash of a block in m_block_index, not a random block.

  123. in src/test/validation_chainstatemanager_tests.cpp:104 in eb8ce95a25 outdated
    100@@ -99,16 +101,12 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
    101     auto exp_tip2 = c2.m_chain.Tip();
    102     BOOST_CHECK_EQUAL(active_tip2, exp_tip2);
    103 
    104-    // Ensure that these pointers actually correspond to different
    105-    // CCoinsViewCache instances.
    106-    BOOST_CHECK(exp_tip != exp_tip2);
    


    ryanofsky commented at 3:34 am on July 18, 2023:

    In commit “Tighten requirements for adding elements to setBlockIndexCandidates” (eb8ce95a2551fe521d00a5901ea773514395255b)

    Seems like it might be useful to check BOOST_CHECK_EQUAL(exp_tip, exp_tip2); now to verify ActivateBestChain actually did arrive at the expected CBlockIndex*, not just the right block block hash.


    sdaftuar commented at 8:29 pm on July 24, 2023:
    Done.
  124. in src/test/validation_chainstatemanager_tests.cpp:150 in eb8ce95a25 outdated
    143         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
    144 
    145     {
    146         LOCK(::cs_main);
    147         c2.InitCoinsCache(1 << 23);
    148-        BOOST_REQUIRE(c2.LoadGenesisBlock());
    


    ryanofsky commented at 3:40 am on July 18, 2023:

    In commit “Tighten requirements for adding elements to setBlockIndexCandidates” (b0b48b3e986319fe8cd5a80052099070279ce5c0)

    Note: It seems like this LoadGenesis/SetBestBlock step was never necessary, so good to drop now. Dropping it maybe makes the test a little less realistic, but simplifies things and keeps the test more focused on verifying cache sizes.

  125. ryanofsky approved
  126. ryanofsky commented at 4:03 am on July 18, 2023: contributor

    This looks good, and thanks for all the clarifications. I’m just going over the commits another time in case I missed anything. Feel free to ignore all my suggestions, none are very important. Here’s my progress so far:

    • fe86a7cd480b32463da900db764d2d11a2bea095 Explicitly track maximum block height stored in undo files (1/12)
    • 1cfc887d00c5d1d4281107e3b3ff4641c6c34631 Remove CChain dependency in node/blockstorage (2/12)
    • 471da5f6e74bac71aeffe2ebc5faff145a6cbcea Move block-arrival information / preciousblock counters to ChainstateManager (3/12)
    • 10c05710ce1602d932037f72dc6c4bbc3f6f34ba Add wrapper for adding entries to a chainstate’s block index candidates (4/12)
    • 272fbc370c4e133d31d9f1d34e327cc265c5fad2 Update CheckBlockIndex invariants for chains based on an assumeutxo snapshot (5/12)
    • 3cfc75366e6596942cbc84f354f42dfd7fc5c073 test: Clear block index flags when testing snapshots (6/12)
    • c8f4a8702851c3097ebd038a814d974dce4dcc78 Move block-storage-related logic to ChainstateManager (7/12)
    • eb8ce95a2551fe521d00a5901ea773514395255b Tighten requirements for adding elements to setBlockIndexCandidates (8/12)
    • f41cef2ba1e1f033356691e520e39d9d75d5eb4f Fix initialization of setBlockIndexCandidates when working with multiple chainstates (9/12)
    • bef5acee93e128857d20137ea0c83f281a780211 Documentation improvements for assumeutxo (10/12)
    • f921887c1afdcd333cf71ca25e5efbf4991f806e Move CheckBlockIndex() from Chainstate to ChainstateManager (11/12)
    • 137762f34a845e491b80f9cea07efc4427cb38bf Cache block index entry corresponding
  127. in src/validation.cpp:4456 in f41cef2ba1 outdated
    4487-                // Instead of this height-based approach, an earlier attempt was made at
    4488-                // detecting "holistically" whether the block index under consideration
    4489-                // relied on an assumed-valid ancestor, but this proved to be too slow to
    4490-                // be practical.
    4491                 for (Chainstate* chainstate : GetAll()) {
    4492-                    if (chainstate->reliesOnAssumedValid() ||
    


    ryanofsky commented at 6:50 pm on July 18, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f41cef2ba1e1f033356691e520e39d9d75d5eb4f)

    It would be good to remove the reliesOnAssumedValid() function since it is no longer used after this.

  128. in src/test/validation_chainstatemanager_tests.cpp:448 in f41cef2ba1 outdated
    441@@ -442,16 +442,17 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
    442         WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
    443     };
    444 
    445-    // Ensure that without any assumed-valid BlockIndex entries, all entries are considered
    446-    // tip candidates.
    447+    // Ensure that without any assumed-valid BlockIndex entries, only the current tip is
    448+    // considered as a candidate.
    449     reload_all_block_indexes();
    450-    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), cs1.m_chain.Height() + 1);
    


    ryanofsky commented at 6:55 pm on July 18, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f41cef2ba1e1f033356691e520e39d9d75d5eb4f)

    Note: the reason why all entries were added previously was because pindex->nHeight < first_assumed_valid_height check was always true, because first_assumed_valid_height was std::numeric_limits<int>::max()

  129. in src/test/validation_chainstatemanager_tests.cpp:452 in f41cef2ba1 outdated
    449     reload_all_block_indexes();
    450-    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), cs1.m_chain.Height() + 1);
    451+    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
    452 
    453-    // Mark some region of the chain assumed-valid.
    454+    // Mark some region of the chain assumed-valid, and remove the HAVE_DATA flag.
    


    ryanofsky commented at 6:59 pm on July 18, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f41cef2ba1e1f033356691e520e39d9d75d5eb4f)

    HAVE_DATA flag doesn’t actualy seem to be removed here. Should it be? And since the test is already passing withtout this would the reason just removing HAVE_DATA flag be to satisfy CheckBlockIndex checks?


    achow101 commented at 4:18 pm on July 24, 2023:
    The blocks being marked ASSUMED_VALID have their nStatus completely reset to the expected values for assumed valid, which includes the omission of HAVE_DATA.
  130. in src/test/validation_chainstatemanager_tests.cpp:481 in f41cef2ba1 outdated
    476         }
    477     }
    478 
    479     BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
    480 
    481+    // Note: cs2's tip is not set when ActivateExistingSnapshot is called.
    


    ryanofsky commented at 7:17 pm on July 18, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f41cef2ba1e1f033356691e520e39d9d75d5eb4f)

    This is true, but it seems like it might make the test less confusing and more realistic if we just added a cs2.m_chain.SetTip(* assumed_base) call below the cs1.m_chain.SetTip(*validated_tip); call. I think if this was done the cs2.setBlockIndexCandidates would just contain blocks after the snapshot.

  131. in src/test/validation_chainstatemanager_tests.cpp:499 in f41cef2ba1 outdated
    500+    // candidate, but otherwise has none of the assumed-valid (which do not
    501+    // HAVE_DATA) blocks as candidates.
    502     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
    503     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
    504-    BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes);
    505+    BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - num_assumed_valid + 1);
    


    ryanofsky commented at 7:20 pm on July 18, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f41cef2ba1e1f033356691e520e39d9d75d5eb4f)

    Command at the top of the test saying it checks cs2 “contains all blocks, even those assumed-valid” is out of date. Should be “contains all blocks except those assumed-valid, because they don’t have data”

  132. in src/test/validation_chainstatemanager_tests.cpp:501 in f41cef2ba1 outdated
    496+    BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_base), 1);
    497 
    498-    // The assumed-valid tolerant chain has all blocks as candidates.
    499+    // The assumed-valid tolerant chain has the assumed valid base as a
    500+    // candidate, but otherwise has none of the assumed-valid (which do not
    501+    // HAVE_DATA) blocks as candidates.
    


    ryanofsky commented at 7:22 pm on July 18, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (f41cef2ba1e1f033356691e520e39d9d75d5eb4f)

    I’m actually confused about how this is working because the HAVE_DATA flag doesn’t seem to be removed above.


    sdaftuar commented at 7:58 pm on July 24, 2023:
  133. in src/chain.h:139 in bef5acee93 outdated
    138-     * Certain diagnostics will be skipped in e.g. CheckBlockIndex().
    139-     * It almost certainly means that the block's full validation is pending
    140-     * on a background chainstate. See `doc/design/assumeutxo.md`.
    141+     * If ASSUMED_VALID is set, it means that this block has not been validated
    142+     * and has validity status less than VALID_SCRIPTS. Also that it has
    143+     * descendant blocks with VALID_SCRIPTS set, because they were validated
    


    ryanofsky commented at 7:28 pm on July 18, 2023:

    In commit “Documentation improvements for assumeutxo” (bef5acee93e128857d20137ea0c83f281a780211)

    “it has” should be “it may have”

  134. in src/chain.h:143 in bef5acee93 outdated
    142+     * and has validity status less than VALID_SCRIPTS. Also that it has
    143+     * descendant blocks with VALID_SCRIPTS set, because they were validated
    144+     * based on an assumeutxo snapshot.
    145+     *
    146+     * When an assumeutxo snapshot is loaded, the ASSUMED_VALID flag is added to
    147+     * unvalidated blocks below the snapshot height. Then, as the background
    


    ryanofsky commented at 7:30 pm on July 18, 2023:

    In commit “Documentation improvements for assumeutxo” (bef5acee93e128857d20137ea0c83f281a780211)

    “below the snapshot height” should be “at the snapshot height and below”

  135. in src/validation.cpp:4755 in f921887c1a outdated
    4750@@ -4750,8 +4751,8 @@ void Chainstate::CheckBlockIndex()
    4751         // Begin: actual consistency checks.
    4752         if (pindex->pprev == nullptr) {
    4753             // Genesis block checks.
    4754-            assert(pindex->GetBlockHash() == m_chainman.GetConsensus().hashGenesisBlock); // Genesis block's hash must match.
    4755-            assert(pindex == m_chain.Genesis()); // The current active chain's genesis block must be this block.
    4756+            assert(pindex->GetBlockHash() == GetConsensus().hashGenesisBlock); // Genesis block's hash must match.
    4757+            assert(pindex == ActiveChain().Genesis()); // The current active chain's genesis block must be this block.
    


    ryanofsky commented at 7:33 pm on July 18, 2023:

    In commit “Move CheckBlockIndex() from Chainstate to ChainstateManager” (f921887c1afdcd333cf71ca25e5efbf4991f806e)

    I think it would be a little better to use GetAll instead of ActiveChain() here. All the chains should have the same genesis block.

  136. ryanofsky approved
  137. ryanofsky commented at 7:54 pm on July 18, 2023: contributor

    Code review ACK 137762f34a845e491b80f9cea07efc4427cb38bf. Left some more suggestions, but this looks good in its current form.

    Just so I don’t forget, I want to note that that it might make sense to move IsInitialBlockDownload and NotifyHeaderTip functions from the Chainstate class to ChainstateManager as a followups to this PR (https://github.com/bitcoin/bitcoin/pull/27746#discussion_r1246868905, #27746 (review))

  138. DrahtBot requested review from fjahr on Jul 18, 2023
  139. DrahtBot requested review from jamesob on Jul 18, 2023
  140. DrahtBot requested review from mzumsande on Jul 18, 2023
  141. Move block-storage-related logic to ChainstateManager
    Separate the notion of which blocks are stored on disk, and what data is in our
    block index, from what tip a chainstate might be able to get to. We can use
    chainstate-agnostic data to determine when to store a block on disk (primarily,
    an anti-DoS set of criteria) and let the chainstates figure out for themselves
    when a block is of interest for being a candidate tip.
    
    Note: some of the invariants in CheckBlockIndex are modified, but more work is
    needed (ie to move CheckBlockIndex to ChainstateManager, as most of what
    CheckBlockIndex is doing is checking the consistency of the block index, which
    is outside of Chainstate).
    d0d40ea9a6
  142. achow101 commented at 4:21 pm on July 24, 2023: member
    ACK 137762f34a845e491b80f9cea07efc4427cb38bf
  143. DrahtBot removed review request from achow101 on Jul 24, 2023
  144. mzumsande commented at 7:14 pm on July 24, 2023: contributor

    Code Review ACK 137762f34a845e491b80f9cea07efc4427cb38bf

    I reviewed all commits again and did a bit of testing with snapshots on signet.

  145. Tighten requirements for adding elements to setBlockIndexCandidates
    When using assumeutxo, we only need the background chainstate to consider
    blocks that are on the chain leading to the snapshotted block.
    
    Note that this introduces the new invariant that we can only have an assumeutxo
    snapshot where the snapshotted blockhash is in our block index. Unknown block
    hashes that are somehow passed in will cause assertion failures when processing
    new blocks.
    
    Includes test fixes and improvements by Andrew Chow and Fabian Jahr.
    d43a1f1a2f
  146. Fix initialization of setBlockIndexCandidates when working with multiple chainstates
    When using assumeutxo and multiple chainstates are active, the background
    chainstate should consider all HAVE_DATA blocks that are ancestors of the
    snapshotted block and that have more work than the tip as potential candidates.
    768690b7ce
  147. Documentation improvements for assumeutxo 0ce805b632
  148. Move CheckBlockIndex() from Chainstate to ChainstateManager
    Also rewrite CheckBlockIndex() to perform tests on all chainstates.
    
    This increases sanity-check coverage, as any place in our code where we were
    invoke CheckBlockIndex() on a single chainstate will now invoke the sanity
    checks on all chainstates.
    
    This change also tightens up the checks on setBlockIndexCandidates and
    mapBlocksUnlinked, to more precisely match what we aim for even in the presence
    of assumed-valid blocks.
    3556b85022
  149. Cache block index entry corresponding to assumeutxo snapshot base blockhash
    This is to (a) avoid repeated lookups into the block index for an entry that
    should never change and (b) emphasize that the snapshot base should always
    exist when set and not change during the runtime of the program.
    
    Thanks to Russ Yanofsky for suggesting this approach.
    d4a11abb19
  150. sdaftuar force-pushed on Jul 24, 2023
  151. sdaftuar commented at 8:26 pm on July 24, 2023: member

    Thanks all for the additional review. I pushed a branch that I think addresses all of @ryanofsky’s latest feedback (previous branch is tagged here for reference).

    Oh one open question is here: #27746 (review)

  152. Remove unused function `reliesOnAssumedValid` a733dd79e2
  153. sdaftuar force-pushed on Jul 24, 2023
  154. DrahtBot added the label CI failed on Jul 24, 2023
  155. DrahtBot removed the label CI failed on Jul 25, 2023
  156. achow101 commented at 8:05 pm on July 26, 2023: member

    ACK a733dd79e29068ad1e0532ac42a45188a040a7b9

    Changes since last are mainly responding to review comments.

  157. DrahtBot requested review from mzumsande on Jul 26, 2023
  158. DrahtBot requested review from ryanofsky on Jul 26, 2023
  159. in src/chain.h:138 in 0ce805b632 outdated
    133@@ -134,10 +134,18 @@ enum BlockStatus : uint32_t {
    134     BLOCK_OPT_WITNESS        =   128, //!< block data in blk*.dat was received with a witness-enforcing client
    135 
    136     /**
    137-     * If set, this indicates that the block index entry is assumed-valid.
    138-     * Certain diagnostics will be skipped in e.g. CheckBlockIndex().
    139-     * It almost certainly means that the block's full validation is pending
    140-     * on a background chainstate. See `doc/design/assumeutxo.md`.
    141+     * If ASSUMED_VALID is set, it means that this block has not been validated
    142+     * and has validity status less than VALID_SCRIPTS. Also that it may have
    


    Sjors commented at 1:21 pm on July 28, 2023:

    0ce805b632dcb98944a931f758f76f530f5ce5f2: why not less than BLOCK_VALID_TRANSACTIONS? Since the minimum requirement to download an assumed-valid block is that we have its headers, i.e. we’re at BLOCK_VALID_TREE or above.

    Same question for RaiseValidity, which this PR doesn’t touch. Contrast that to CheckBlockIndex() which skips checks for BLOCK_VALID_TRANSACTIONS, BLOCK_VALID_CHAIN and BLOCK_VALID_SCRIPTS when assume valid is set.

    I tried changing BLOCK_VALID_SCRIPTS to BLOCK_VALID_TRANSACTIONS in PopulateAndValidateSnapshot:

    0        // Mark unvalidated block index entries beneath the snapshot base block as assumed-valid.
    1        if (!index->IsValid(BLOCK_VALID_SCRIPTS)) {
    

    That didn’t break a test and makes more intuitive sense to me. It also seems more consistent with setBlockIndexCandidates.


    mzumsande commented at 2:42 pm on July 28, 2023:

    why not less than BLOCK_VALID_TRANSACTIONS?

    I asked myself the same question recently and came to the conclusion that it’s because when we finally end up downloading the assumed-valid blocks, there will be a period in time where we’ve saved the block (and therefore raised its validity to BLOCK_VALID_TRANSACTIONS) but haven’t connected it yet to the background chainstate (so it’s still ASSUMED_VALID).

  160. in src/chain.h:113 in 0ce805b632 outdated
    112@@ -113,10 +113,10 @@ enum BlockStatus : uint32_t {
    113     BLOCK_VALID_TRANSACTIONS =    3,
    


    Sjors commented at 1:38 pm on July 28, 2023:
    0ce805b632dcb98944a931f758f76f530f5ce5f2: why isn’t BLOCK_VALID_TRANSACTIONS also alternatively ASSUMED_VALID?

    ryanofsky commented at 7:49 pm on July 31, 2023:

    re: #27746 (review)

    0ce805b: why isn’t BLOCK_VALID_TRANSACTIONS also alternatively ASSUMED_VALID?

    I’m not sure what this review comment is asking. The code comment above still seems accurate to me, but it maybe it could be improved.


    Sjors commented at 8:02 am on August 1, 2023:

    It’s related to #27746 (review)

    (it suggests a further refactor, not changing the comment)

  161. in src/chain.h:122 in 0ce805b632 outdated
    119 
    120-    //! Scripts & signatures ok. Implies all parents are also at least SCRIPTS.
    121+    //! Scripts & signatures ok. Implies all parents are either at least VALID_SCRIPTS, or are ASSUMED_VALID.
    122     BLOCK_VALID_SCRIPTS      =    5,
    123 
    124     //! All validity bits.
    


    Sjors commented at 1:39 pm on July 28, 2023:

    0ce805b632dcb98944a931f758f76f530f5ce5f2: maybe make this:

    0//! All (non-assumed) validity bits.
    

    Which makes functions that use this more readable:

  162. Sjors commented at 2:25 pm on July 28, 2023: member
    The documentation commit made me wonder if we can use BLOCK_VALID_TRANSACTIONS (the first check beyond headers) instead of BLOCK_VALID_SCRIPTS to set BLOCK_ASSUMED_VALID.
  163. jamesob approved
  164. jamesob commented at 5:53 pm on July 28, 2023: member

    reACK a733dd79e29068ad1e0532ac42a45188a040a7b9 (jamesob/ackr/27746.5.sdaftuar.rework_validation_logic)

    Changes incorporate feedback discussed above.

     0-----BEGIN PGP SIGNED MESSAGE-----
     1Hash: SHA512
     2
     3reACK a733dd79e29068ad1e0532ac42a45188a040a7b9 ([`jamesob/ackr/27746.5.sdaftuar.rework_validation_logic`](https://github.com/jamesob/bitcoin/tree/ackr/27746.5.sdaftuar.rework_validation_logic))
     4
     5Changes incorporate feedback discussed above.
     6
     7-----BEGIN PGP SIGNATURE-----
     8
     9iQIzBAEBCgAdFiEEGNRVI1NPYuZCSIrGepNdrbLETwUFAmTEAMQACgkQepNdrbLE
    10TwWP5g//REGb6Fc8/xAok0xHNy81xhAF8B/egKZzUJOHMON/4k6R4rXH0XGYr8Cg
    11sUH7dHsCFRCHIghFYMLJArKwIOa1mmd/XPuztzl8E4Ki1FscylUkPxadPsH4D7Rz
    12tFqmAUdiLprrmDV9CauI//QHvI0feoxDS3YMneUUiqtCzPGKvfY9TAXK0UmkIQbY
    134wNKWrfpT8dLzWuiEosEHErJxcMpzK8OYY0IPtU4CmZ4OOXuMHL9hHqjtppPIpVJ
    14yoOy10KEBbdWG6ydmFgexAc9tNC1diF+Q2TmJTPdP/9bJXfaMi+8IwPTUbJXwQLZ
    15UMdXHnAeOMk++/PCtlY3PVG/T65nCdEUrzVMRf243iwiJCEK/DF0d8xcsVs6rgf/
    16EfHQ2Kz2CAxLVwIr8myeig4LTy1EErkvs4Duk69jjpIOjIvkrHUkIGm/pTB3qGD7
    17ylxkfffQBtM0CVQt5tIWqHB00rdN1xVZNna8AzB1++WxxOJHsLmk6PsJrRFc2qAV
    18ZxO9FnJr+GVqZ9hwFmjIcexS7Nz9mCZOiCUG2efilHyxkWkCXZ3ZQxUXPmD8Vi8W
    19ppePxNNKlq/8aursxx9Em2Thru5HdhPNiO6ta/r+wfvGLqvp8hTXtpB5Z1gXLyuO
    20NWSI19+tYx4pp1DjpsuOaDCmNWuLtmPdQMSscv0xD1lLpVU87/U=
    21=xs8R
    22-----END PGP SIGNATURE-----
    
    0Tested on Linux-6.4.6-arch1-1-x86_64-with-glibc2.37
    1
    2Configured with ./configure 'BDB_LIBS=-L/home/james/src/bitcoin/db4/lib -ldb_cxx-4.8' BDB_CFLAGS=-I/home/james/src/bitcoin/db4/include 'CXXFLAGS=-fPIE -pipe -O2 -g -Wthread-safety-analysis -Wall -Werror=sign-compare ' --disable-gui-tests --disable-fuzz-binary --enable-wallet --enable-debug --with-daemon --enable-natpmp-default CXX=/usr/bin/clang++ CC=/usr/bin/clang --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --disable-module-ecdh --no-create --no-recursion
    3
    4Compiled with /usr/bin/ccache /usr/bin/clang++ -std=c++17 -mavx -mavx2 -mpclmul -fPIE -pipe -O2 -g -Wthread-safety-analysis -Wall -Werror=sign-compare  -O0 -g3 -ftrapv -fdebug-prefix-map=$(abs_top_srcdir)=.  -Wstack-protector -fstack-protector-all -fcf-protection=full -fstack-clash-protection -msse4.1 -msse4.2 -msse4 -msha  i
    5
    6Compiler version: clang version 15.0.7
    
  165. in src/validation.cpp:4809 in 3556b85022 outdated
    4818-                if (is_active && (pindexFirstMissing == nullptr || pindex == m_chain.Tip())) {
    4819-                    assert(setBlockIndexCandidates.count(pindex));
    4820+        // Chainstate-specific checks on setBlockIndexCandidates
    4821+        for (auto c : GetAll()) {
    4822+            if (c->m_chain.Tip() == nullptr) continue;
    4823+            if (!CBlockIndexWorkComparator()(pindex, c->m_chain.Tip()) && pindexFirstNeverProcessed == nullptr) {
    


    Sjors commented at 7:49 pm on July 28, 2023:
    3556b850221bc0e597d7dd749d4d47ab58dc8083: if you have to retouch, can you add a comment that !CBlockIndexWorkComparator()(pindex, c->m_chain.Tip()) means “pindex has more work than the tip or was received earlier”
  166. in src/validation.cpp:4829 in 3556b85022 outdated
    4841+                    // In this case it must be in m_blocks_unlinked -- see test below.
    4842                 }
    4843-                // If some parent is missing, then it could be that this block was in
    4844-                // setBlockIndexCandidates but had to be removed because of the missing data.
    4845-                // In this case it must be in m_blocks_unlinked -- see test below.
    4846+            } else { // If this block sorts worse than the current tip or some ancestor's block has never been seen, it cannot be in setBlockIndexCandidates.
    


    Sjors commented at 8:00 pm on July 28, 2023:
    3556b850221bc0e597d7dd749d4d47ab58dc8083: there’s a tie breaker in the comparator for if two blocks with equal work were loaded from disk. Presumably that only matters if you restart right during a reorg in some weird way.
  167. Sjors commented at 8:15 pm on July 28, 2023: member
    Also reviewed a733dd79e29068ad1e0532ac42a45188a040a7b9, d4a11abb1972b54f0babdccfbb2fde97ab885933, 3556b850221bc0e597d7dd749d4d47ab58dc8083 and read through all of CheckBlockIndex(). Will continue later (going mostly in reverse order).
  168. in src/validation.cpp:4440 in 768690b7ce outdated
    4458             if (m_interrupt) return false;
    4459-            if (pindex->IsAssumedValid() ||
    4460+            // If we have an assumeutxo-based chainstate, then the snapshot
    4461+            // block will be a candidate for the tip, but it may not be
    4462+            // VALID_TRANSACTIONS (eg if we haven't yet downloaded the block),
    4463+            // so we special-case the snapshot block as a potential candidate
    


    Sjors commented at 2:48 pm on July 29, 2023:
    768690b7ce551cd403f8e2a099372915f6022ad4: Maybe clarify that initially this only applies to the snapshot chainstate, which uses the base block as its starting point. For the background chainstate, as well an active chainstate not created from a snapshot, GetSnapshotBaseBlock() is nullptr so the special-case won’t apply. But the background state will consider it a candidate once it’s been downloaded.

    ryanofsky commented at 6:36 pm on July 31, 2023:

    re: #27746 (review)

    GetSnapshotBaseBlock() is nullptr so the special-case won’t apply

    I may be misunderstanding, but I don’t think it’s true that GetSnapshotBaseBlock is null for the background chainstate. This is a ChainstateManager method, so GetSnapshotBaseBlock is going to be non-null if any snapshot is loaded, and this code will try to add the snapshot block to both chainstates, even though it’s not needed for the background chainstate. It might be clearer if the code narrowed the special case, and only added the snapshot block to the snapshot chainstate where it was actually needed, not to both chainstates. But that might make the code more complicated.

    I do think it might be better if the comment said “assumeutxo snapshot chainstate” instead of “assumeutxo-based chainstate” to be more specific about when this special case is needed, but I don’t think it need to go into detail about other cases where the check is harmless and not needed.

  169. in src/test/validation_chainstatemanager_tests.cpp:413 in 768690b7ce outdated
    411@@ -412,7 +412,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
    412 //! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
    413 //!   that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
    


    Sjors commented at 2:54 pm on July 29, 2023:

    768690b7ce551cd403f8e2a099372915f6022ad4

    0//!
    1//! - Run LoadBlockIndex() and ensure that setBlockIndexCandidates of the first ...
    
  170. in src/test/validation_chainstatemanager_tests.cpp:503 in 768690b7ce outdated
    506-    BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
    507+    // The assumed-valid tolerant chain has the assumed valid base as a
    508+    // candidate, but otherwise has none of the assumed-valid (which do not
    509+    // HAVE_DATA) blocks as candidates.
    510+    BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0);
    511     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
    


    Sjors commented at 3:10 pm on July 29, 2023:
    768690b7ce551cd403f8e2a099372915f6022ad4: this had me confused, but it’s because the test considers a scenario where the snapshot base block has been downloaded. It could be nice to include the moment before it’s downloaded where this should be 0.

    ryanofsky commented at 7:20 pm on July 31, 2023:

    re: #27746 (review)

    768690b: this had me confused, but it’s because the test considers a scenario where the snapshot base block has been downloaded. It could be nice to include the moment before it’s downloaded where this should be 0.

    This review comment does not match my understanding. The snapshot block is assumed_base at height 39 and it does not have data. This line is checking assumed_tip block at height 100 which does have data.

    I think all the variable names in this test are pretty confusing, and it would probably be clearer if blocks were referred to directly by height, but that’s the was the test style, so it’s not changing.

  171. Sjors commented at 3:11 pm on July 29, 2023: member

    Comments around 768690b7ce551cd403f8e2a099372915f6022ad4.

    It would be nice if the documentation for setBlockIndexCandidates and m_blocks_unlinked explained their purpose (as opposed to merely describing what’s in them).

     0/** 
     1* The set of all CBlockIndex entries that are - or recently
     2* were - candidates for a new tip. These are as good as our
     3* current tip or better, and must either have BLOCK_VALID_TRANSACTIONS
     4* (for itself and all ancestors) *or* BLOCK_ASSUMED_VALID (if using
     5* background chainstates). Entries may be failed, though, and
     6* pruning nodes may be missing the data for the block.
     7*/
     8 setBlockIndexCandidates
     9
    10/**
    11* CBlockIndex entries removed from setBlockIndexCandidates due
    12* to pruned data for themselves or their ancestors.
    13*/
    14 m_blocks_unlinked`
    

    (The above still doesn’t satisfyingly explain why we hold them in m_blocks_unlinked rather than treat them as any other block index entry. I assume it’s because we use the list to proactively aks for these blocks?)

    Suggested documentation for TryAddBlockIndexCandidate:

    0/**
    1* Add CBlockIndex entry to setBlockIndexCandidates if it has more
    2* work than the current tip. For the background chainstate, we only
    3* consider connecting blocks towards the snapshot base 
    4*/
    
  172. in src/validation.cpp:3979 in d0d40ea9a6 outdated
    3976@@ -3974,7 +3977,7 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
    3977 
    3978     // Header is valid/has work, merkle tree and segwit merkle tree are good...RELAY NOW
    3979     // (but if it does not build on our best tip, let the SendMessages loop relay it)
    


    Sjors commented at 3:53 pm on July 29, 2023:
    d0d40ea9a6478d81d7531b7cfc52a8bdaa0883d6: // We don't relay background validation blocks.
  173. Sjors approved
  174. Sjors commented at 4:54 pm on July 29, 2023: member

    Code review ACK a733dd79e29068ad1e0532ac42a45188a040a7b9.

    My comments above can all wait for a followup, if any.

    I’d like to take this for a spin in a rebased #27596 with -checkblockindex to see if we didn’t miss anything (without pruning, since that has a clear TODO).

  175. ryanofsky commented at 1:14 pm on July 31, 2023: contributor

    re: #27746#pullrequestreview-1553292155

    (The above still doesn’t satisfyingly explain why we hold them in m_blocks_unlinked rather than treat them as any other block index entry. I assume it’s because we use the list to proactively aks for these blocks?)

    I also found this pretty confusing when I was reviewing it and found a helpful explanation here: https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_6):_The_Blockchain. The description of m_blocks_unlinked (which used to be mapBlocksUnlinked) is:

    Multimap containing “all pairs A->B, where A (or one if its ancestors) misses transactions, but B has transactions.” (comment at main.cpp:125).

    The purpose of mapBlocksUnlinked is to quickly attach blocks we’ve already received to the blockchain when we receive a missing, intermediate block.

    The alternative would be to search the entire mapBlockIndex; however, it is more efficient to keep track of unlinked blocks in a separate data structure.

    So I think the map just exists for efficiency, not to proactively do anything. It would be great to improve documentation in the actual code, though.

  176. in src/test/validation_chainstatemanager_tests.cpp:502 in 768690b7ce outdated
    505-    // The assumed-valid tolerant chain has all blocks as candidates.
    506-    BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
    507+    // The assumed-valid tolerant chain has the assumed valid base as a
    508+    // candidate, but otherwise has none of the assumed-valid (which do not
    509+    // HAVE_DATA) blocks as candidates.
    510+    BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0);
    


    ryanofsky commented at 7:12 pm on July 31, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (768690b7ce551cd403f8e2a099372915f6022ad4)

    Would be good to directly verify the snapshot block is included:

    0// block before the snapshot is excluded (as well as earlier blocks)
    1BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base->pprev), 0); 
    2// snapshot block is included (as well as later blocks)
    3BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base), 1);
    
  177. in src/test/validation_chainstatemanager_tests.cpp:415 in 768690b7ce outdated
    411@@ -412,7 +412,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
    412 //! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
    413 //!   that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
    414 //!   chainstate only contains fully validated blocks and the other chainstate contains all blocks,
    415-//!   even those assumed-valid.
    416+//!   except those marked assume-valid, because those entries don't HAVE_DATA.
    


    ryanofsky commented at 7:35 pm on July 31, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (768690b7ce551cd403f8e2a099372915f6022ad4)

    I think it’s a little misleading to say the other chainstate contains all block except those marked assume-valid because:

    • It actually does contain one block marked assumed-valid, which is the snapshot base (block 39)
    • It also excludes other blocks which are not assumed-valid because they don’t have more work than the tip (blocks 1-19)

    I think this comment could incorporate sjors suggestion above and just say “Run LoadBlockIndex() and ensure that setBlockIndexCandidates of the first chainstate only contains blocks leading to the snapshot base, and that setBlockIndexCandidates of the second chainstate only contains blocks following the snapshot base.”

    Or could leave out the details and say “Run LoadBlockIndex() and check setBlockIndexCandidates of both chainstates.”

  178. ryanofsky approved
  179. ryanofsky commented at 8:07 pm on July 31, 2023: contributor
    Code review ACK a733dd79e29068ad1e0532ac42a45188a040a7b9. Just suggested changes since the last review. There are various small things that could be followed up on, but I think this is ready for merge.
  180. ryanofsky merged this on Jul 31, 2023
  181. ryanofsky closed this on Jul 31, 2023

  182. TheCharlatan commented at 8:22 am on August 1, 2023: contributor

    Post-merge ACK a733dd79e29068ad1e0532ac42a45188a040a7b9

    Very happy with the separation of concerns between blockstorage and validation.

  183. fjahr commented at 7:59 pm on August 1, 2023: contributor

    Post-merge code review ACK a733dd79e29068ad1e0532ac42a45188a040a7b9

    I also synced and kept a node running for a few days with the status of my previous ACK and didn’t see anything unusual.

  184. ryanofsky referenced this in commit 509a967cbc on Aug 4, 2023
  185. ryanofsky referenced this in commit 3d70aa3c60 on Aug 4, 2023
  186. ryanofsky referenced this in commit bda557e5eb on Aug 8, 2023
  187. sidhujag referenced this in commit bdd3a75e2d on Aug 9, 2023
  188. ryanofsky referenced this in commit 7e207be212 on Aug 18, 2023
  189. ryanofsky referenced this in commit 00a2e4362f on Aug 18, 2023
  190. ryanofsky referenced this in commit 94a98fbd1d on Aug 18, 2023
  191. fanquake referenced this in commit 723f1c669f on Aug 21, 2023
  192. janus referenced this in commit 622fd40c9b on Sep 11, 2023
  193. in src/validation.cpp:4442 in 768690b7ce outdated
    4460+            // If we have an assumeutxo-based chainstate, then the snapshot
    4461+            // block will be a candidate for the tip, but it may not be
    4462+            // VALID_TRANSACTIONS (eg if we haven't yet downloaded the block),
    4463+            // so we special-case the snapshot block as a potential candidate
    4464+            // here.
    4465+            if (pindex == GetSnapshotBaseBlock() ||
    


    ryanofsky commented at 1:40 am on September 11, 2023:

    In commit “Fix initialization of setBlockIndexCandidates when working with multiple chainstates” (768690b7ce551cd403f8e2a099372915f6022ad4)

    It looks like there may be a bug in this code because this pindex == GetSnapshotBaseBlock() special case will add the snapshot block to setBlockIndexCandidates whether or not it has data, but there is also an assert in FindMostWorkChain which asserts that blocks in setBlockIndexCandidates do have data:

    https://github.com/bitcoin/bitcoin/blob/c5a63ea56f8347139bd84e1669b378ecfb234c3c/src/validation.cpp#L2914

    I think it’s not possible to hit this assert for the snapshot chainstate, but it may be possible to hit this assert for the background chainstate.

    For the snapshot chainstate, I think the assert won’t be reached because after the snapshot is loaded the Chainstate::LoadChainTip function should immediately set the chain tip to the snapshot block index:

    https://github.com/bitcoin/bitcoin/blob/c5a63ea56f8347139bd84e1669b378ecfb234c3c/src/validation.cpp#L4153

    so the m_chain.Contains(pindexTest) check before the assert will be false and the assert will not run:

    https://github.com/bitcoin/bitcoin/blob/c5a63ea56f8347139bd84e1669b378ecfb234c3c/src/validation.cpp#L2913-L2914

    But for the background chainstate, it seems possible assert could be hit if FindMostWorkChain is called before the snapshot block is downloaded.

    In any case, this special pindex == GetSnapshotBaseBlock() case seems overbroad. It seems like it should only apply to the snapshot chainstate, not the background chainstate, and I’m not actually sure why it is necessary to have at all if Chainstate::LoadChainTip is already going to set the snapshot block as the snapshot chainstate tip.

    I think the interaction between these checks and the checks inside the TryAddBlockIndexCandidate are also unnecessarily confusing, and it would be nice whether there is a bug or not to move all the move all the checks inside TryAddBlockIndexCandidate and call it unconditionally. This should make it easeir to avoid adding the snapshot block the background chainstate candidate set before it is downloaded. It would also be great to drop the snapshot block special case entirely if it is not needed.


    jamesob commented at 4:41 pm on September 11, 2023:

    It looks like there may be a bug in this code because this pindex == GetSnapshotBaseBlock() special case will add the snapshot block to setBlockIndexCandidates whether or not it has data, but there is also an assert in FindMostWorkChain which asserts that blocks in setBlockIndexCandidates do have data:

    bitcoin/src/validation.cpp Line 2914 in c5a63ea

    0assert(pindexTest->HaveTxsDownloaded() || pindexTest->nHeight == 0); 
    

    I think it’s not possible to hit this assert for the snapshot chainstate, but it may be possible to hit this assert for the background chainstate.

    If I’m reading FindMostWorkChain() correctly, this assert won’t be hit for a background chainstate, because the second clause of this condition

    0while (pindexTest && !m_chain.Contains(pindexTest)) {
    

    will exclude the snapshot base block from consideration without data (since it is not in the background chainstate’s m_chain until it is actually downloaded, and so has data). So as far as I can tell, there’s no bug here - but I’m glad you’re double checking this; the setBlockIndexCandidates code is very confusing indeed, and I’m amenable to any way we can make it more easily understandable.


    ryanofsky commented at 5:05 pm on September 11, 2023:

    re: #27746 (review)

    The way I’m reading e code:

    https://github.com/bitcoin/bitcoin/blob/c5a63ea56f8347139bd84e1669b378ecfb234c3c/src/validation.cpp#L2913-L2914

    If a snapshot is loaded and pindexTest points to the snapshot block and the snapshot block is not yet downloaded, and m_chain is the background chain, then m_chain.Contains(pindexTest) will be false, so the while loop condition will be true, and the assert could be be hit.

    You seem to be saying that m_chain.Contains(pindexTest) will be false, so the while condition won’t be hit?


    jamesob commented at 5:09 pm on September 11, 2023:

    I’m sorry, my previous comment was more or less incorrect. Another reason why the assert you mention doesn’t pose a problem is because, confusingly, HaveTxsDownloaded() is just an alias for nChainTx != 0. Since we manually set nChainTx for the snapshot base block (https://github.com/bitcoin/bitcoin/pull/27596/commits/315586f25b9ec0ed0c807d8c4455e6a73a3c4990) this check (again, very confusingly) passes. At some point long ago I made a note somewhere about fixing that method’s misleading name…

    So the long story short is that FindMostWorkChain() eventually works for the background chainstate because it detects, correctly, that there are blocks with missing data between the IBD tip and the snapshot base, and correctly rewinds m_chain back to the actual IBD tip. I think when @sdaftuar first proposed this change I was confused about why the LoadBlockIndex() simplification could actually work, and this is the reason.


    jamesob commented at 5:26 pm on September 11, 2023:

    So the long story short is that FindMostWorkChain() eventually works for the background chainstate because it detects, correctly, that there are blocks with missing data between the IBD tip and the snapshot base, and correctly rewinds m_chain back to the actual IBD tip.

    Again I’m wrong: snapshot base block never makes it into background chainstate’s m_chain because LoadChainTip() (when called from the init code in src/node/chainstate.cpp) sets the background chainstate’s tip to the correct IBD tip (on the basis of the coinscache GetBestBlock()), which is before the snapshot base block.


    ryanofsky commented at 5:40 pm on September 11, 2023:

    Thanks, I missed that HaveTxsDownloaded was checking nChainTx not nTx.

    I guess the main thing I’m confused about now is why this pindex == GetSnapshotBaseBlock() || special case is needed at all? If LoadChainTip() already sets the snapshot block to be the snapshot tip, why does the snapshot block need to be added to the candidate set?


    jamesob commented at 5:54 pm on September 11, 2023:

    I guess the main thing I’m confused about now is why this pindex == GetSnapshotBaseBlock() || special case is needed at all? If LoadChainTip() already sets the snapshot block to be the snapshot tip, why does the snapshot block need to be added to the candidate set?

    I’m not sure if there’s a more fundamental reason, but various assertions fail if this case is stripped out of LoadBlockIndex(); e.g. the !setBlockIndexCandidates.empty() assertion fails during PruneBlockIndexCandidates() if this special case is not included.

    The true purpose of setBlockIndexCandidates has always been somewhat mysterious to me, but the most essential use seems to be in FindMostWorkChain(), to supply candidates for activation in ActivateBestChain().


    jamesob commented at 5:56 pm on September 11, 2023:
    I’ve added some documentation for HaveTxsDownloaded() (https://github.com/bitcoin/bitcoin/pull/27596/commits/9f9ffb655e4236f8d567b961102f53c5933e8aa8), and I’m happy to add more for the confusing process discussed above.

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: 2024-07-01 10:13 UTC

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