LCOV - code coverage report
Current view: top level - src/test - headers_sync_chainwork_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 98.3 % 60 59
Test Date: 2026-03-16 05:20:51 Functions: 100.0 % 12 12
Branches: 50.9 % 542 276

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2022-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <chain.h>
       6                 :             : #include <chainparams.h>
       7                 :             : #include <consensus/params.h>
       8                 :             : #include <headerssync.h>
       9                 :             : #include <net_processing.h>
      10                 :             : #include <pow.h>
      11                 :             : #include <test/util/common.h>
      12                 :             : #include <test/util/setup_common.h>
      13                 :             : #include <validation.h>
      14                 :             : 
      15                 :             : #include <cstddef>
      16                 :             : #include <vector>
      17                 :             : 
      18                 :             : #include <boost/test/unit_test.hpp>
      19                 :             : 
      20                 :             : using State = HeadersSyncState::State;
      21                 :             : 
      22                 :             : // Standard set of checks common to all scenarios. Macro keeps failure lines at the call-site.
      23                 :             : #define CHECK_RESULT(result_expression, hss, exp_state, exp_success, exp_request_more,                   \
      24                 :             :                      exp_headers_size, exp_pow_validated_prev, exp_locator_hash)                         \
      25                 :             :     do {                                                                                                 \
      26                 :             :         const auto result{result_expression};                                                            \
      27                 :             :         BOOST_REQUIRE_EQUAL(hss.GetState(), exp_state);                                                  \
      28                 :             :         BOOST_CHECK_EQUAL(result.success, exp_success);                                                  \
      29                 :             :         BOOST_CHECK_EQUAL(result.request_more, exp_request_more);                                        \
      30                 :             :         BOOST_CHECK_EQUAL(result.pow_validated_headers.size(), exp_headers_size);                        \
      31                 :             :         const std::optional<uint256> pow_validated_prev_opt{exp_pow_validated_prev};                     \
      32                 :             :         if (pow_validated_prev_opt) {                                                                    \
      33                 :             :             BOOST_CHECK_EQUAL(result.pow_validated_headers.at(0).hashPrevBlock, pow_validated_prev_opt); \
      34                 :             :         } else {                                                                                         \
      35                 :             :             BOOST_CHECK_EQUAL(exp_headers_size, 0);                                                      \
      36                 :             :         }                                                                                                \
      37                 :             :         const std::optional<uint256> locator_hash_opt{exp_locator_hash};                                 \
      38                 :             :         if (locator_hash_opt) {                                                                          \
      39                 :             :             BOOST_CHECK_EQUAL(hss.NextHeadersRequestLocator().vHave.at(0), locator_hash_opt);            \
      40                 :             :         } else {                                                                                         \
      41                 :             :             BOOST_CHECK_EQUAL(exp_state, State::FINAL);                                                  \
      42                 :             :         }                                                                                                \
      43                 :             :     } while (false)
      44                 :             : 
      45                 :             : constexpr size_t TARGET_BLOCKS{15'000};
      46                 :             : constexpr arith_uint256 CHAIN_WORK{TARGET_BLOCKS * 2};
      47                 :             : 
      48                 :             : // Subtract MAX_HEADERS_RESULTS (2000 headers/message) + an arbitrary smaller
      49                 :             : // value (123) so our redownload buffer is well below the number of blocks
      50                 :             : // required to reach the CHAIN_WORK threshold, to behave similarly to mainnet.
      51                 :             : constexpr size_t REDOWNLOAD_BUFFER_SIZE{TARGET_BLOCKS - (MAX_HEADERS_RESULTS + 123)};
      52                 :             : constexpr size_t COMMITMENT_PERIOD{600}; // Somewhat close to mainnet.
      53                 :             : 
      54                 :           6 : struct HeadersGeneratorSetup : public RegTestingSetup {
      55                 :             :     const CBlock& genesis{Params().GenesisBlock()};
      56   [ +  -  +  -  :           6 :     CBlockIndex& chain_start{WITH_LOCK(::cs_main, return *Assert(m_node.chainman->m_blockman.LookupBlockIndex(genesis.GetHash())))};
             -  +  +  - ]
      57                 :             : 
      58                 :             :     // Generate headers for two different chains (using differing merkle roots
      59                 :             :     // to ensure the headers are different).
      60                 :           2 :     const std::vector<CBlockHeader>& FirstChain()
      61                 :             :     {
      62                 :             :         // Block header hash target is half of max uint256 (2**256 / 2), expressible
      63                 :             :         // roughly as the coefficient 0x7fffff with the exponent 0x20 (32 bytes).
      64                 :             :         // This implies around every 2nd hash attempt should succeed, which
      65                 :             :         // is why CHAIN_WORK == TARGET_BLOCKS * 2.
      66         [ -  + ]:           2 :         assert(genesis.nBits == 0x207fffff);
      67                 :             : 
      68                 :             :         // Subtract 1 since the genesis block also contributes work so we reach
      69                 :             :         // the CHAIN_WORK target.
      70                 :           2 :         static const auto first_chain{GenerateHeaders(/*count=*/TARGET_BLOCKS - 1, genesis.GetHash(),
      71   [ +  +  +  -  :           3 :                 genesis.nVersion, genesis.nTime, /*merkle_root=*/uint256::ZERO, genesis.nBits)};
             +  -  +  - ]
      72                 :           2 :         return first_chain;
      73                 :             :     }
      74                 :           2 :     const std::vector<CBlockHeader>& SecondChain()
      75                 :             :     {
      76                 :             :         // Subtract 2 to keep total work below the target.
      77                 :           2 :         static const auto second_chain{GenerateHeaders(/*count=*/TARGET_BLOCKS - 2, genesis.GetHash(),
      78   [ +  +  +  -  :           3 :                 genesis.nVersion, genesis.nTime, /*merkle_root=*/uint256::ONE, genesis.nBits)};
             +  -  +  - ]
      79                 :           2 :         return second_chain;
      80                 :             :     }
      81                 :             : 
      82                 :           4 :     HeadersSyncState CreateState()
      83                 :             :     {
      84                 :           4 :         return {/*id=*/0,
      85                 :           4 :                 Params().GetConsensus(),
      86                 :           4 :                 HeadersSyncParams{
      87                 :             :                     .commitment_period = COMMITMENT_PERIOD,
      88                 :             :                     .redownload_buffer_size = REDOWNLOAD_BUFFER_SIZE,
      89                 :             :                 },
      90                 :           4 :                 chain_start,
      91                 :           4 :                 /*minimum_required_work=*/CHAIN_WORK};
      92                 :             :     }
      93                 :             : 
      94                 :             : private:
      95                 :             :     /** Search for a nonce to meet (regtest) proof of work */
      96                 :             :     void FindProofOfWork(CBlockHeader& starting_header);
      97                 :             :     /**
      98                 :             :      * Generate headers in a chain that build off a given starting hash, using
      99                 :             :      * the given nVersion, advancing time by 1 second from the starting
     100                 :             :      * prev_time, and with a fixed merkle root hash.
     101                 :             :      */
     102                 :             :     std::vector<CBlockHeader> GenerateHeaders(size_t count,
     103                 :             :             uint256 prev_hash, int32_t nVersion, uint32_t prev_time,
     104                 :             :             const uint256& merkle_root, uint32_t nBits);
     105                 :             : };
     106                 :             : 
     107                 :       29997 : void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header)
     108                 :             : {
     109         [ +  + ]:       59957 :     while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) {
     110                 :       29960 :         ++starting_header.nNonce;
     111                 :             :     }
     112                 :       29997 : }
     113                 :             : 
     114                 :           2 : std::vector<CBlockHeader> HeadersGeneratorSetup::GenerateHeaders(
     115                 :             :         const size_t count, uint256 prev_hash, const int32_t nVersion,
     116                 :             :         uint32_t prev_time, const uint256& merkle_root, const uint32_t nBits)
     117                 :             : {
     118                 :           2 :     std::vector<CBlockHeader> headers(count);
     119         [ +  + ]:       29999 :     for (auto& next_header : headers) {
     120                 :       29997 :         next_header.nVersion = nVersion;
     121                 :       29997 :         next_header.hashPrevBlock = prev_hash;
     122                 :       29997 :         next_header.hashMerkleRoot = merkle_root;
     123                 :       29997 :         next_header.nTime = ++prev_time;
     124                 :       29997 :         next_header.nBits = nBits;
     125                 :             : 
     126         [ +  - ]:       29997 :         FindProofOfWork(next_header);
     127         [ +  - ]:       29997 :         prev_hash = next_header.GetHash();
     128                 :             :     }
     129                 :           2 :     return headers;
     130                 :           0 : }
     131                 :             : 
     132                 :             : // In this test, we construct two sets of headers from genesis, one with
     133                 :             : // sufficient proof of work and one without.
     134                 :             : // 1. We deliver the first set of headers and verify that the headers sync state
     135                 :             : //    updates to the REDOWNLOAD phase successfully.
     136                 :             : //    Then we deliver the second set of headers and verify that they fail
     137                 :             : //    processing (presumably due to commitments not matching).
     138                 :             : // 2. Verify that repeating with the first set of headers in both phases is
     139                 :             : //    successful.
     140                 :             : // 3. Repeat the second set of headers in both phases to demonstrate behavior
     141                 :             : //    when the chain a peer provides has too little work.
     142                 :             : BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup)
     143                 :             : 
     144   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(sneaky_redownload)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     145                 :             : {
     146                 :           1 :     const auto& first_chain{FirstChain()};
     147                 :           1 :     const auto& second_chain{SecondChain()};
     148                 :             : 
     149                 :             :     // Feed the first chain to HeadersSyncState, by delivering 1 header
     150                 :             :     // initially and then the rest.
     151                 :           1 :     HeadersSyncState hss{CreateState()};
     152                 :             : 
     153                 :             :     // Just feed one header and check state.
     154                 :             :     // Pretend the message is still "full", so we don't abort.
     155   [ +  -  +  -  :           2 :     CHECK_RESULT(hss.ProcessNextHeaders({{first_chain.front()}}, /*full_headers_message=*/true),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     156                 :             :         hss, /*exp_state=*/State::PRESYNC,
     157                 :             :         /*exp_success=*/true, /*exp_request_more=*/true,
     158                 :             :         /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     159                 :             :         /*exp_locator_hash=*/first_chain.front().GetHash());
     160                 :             : 
     161                 :             :     // This chain should look valid, and we should have met the proof-of-work
     162                 :             :     // requirement during PRESYNC and transitioned to REDOWNLOAD.
     163   [ -  +  +  -  :           2 :     CHECK_RESULT(hss.ProcessNextHeaders(std::span{first_chain}.subspan(1), true),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     164                 :             :         hss, /*exp_state=*/State::REDOWNLOAD,
     165                 :             :         /*exp_success=*/true, /*exp_request_more=*/true,
     166                 :             :         /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     167                 :             :         /*exp_locator_hash=*/genesis.GetHash());
     168                 :             : 
     169                 :             :     // Below is the number of commitment bits that must randomly match between
     170                 :             :     // the two chains for this test to spuriously fail. 1 / 2^25 =
     171                 :             :     // 1 in 33'554'432 (somewhat less due to HeadersSyncState::m_commit_offset).
     172                 :           1 :     static_assert(TARGET_BLOCKS / COMMITMENT_PERIOD == 25);
     173                 :             : 
     174                 :             :     // Try to sneakily feed back the second chain during REDOWNLOAD.
     175   [ -  +  +  -  :           1 :     CHECK_RESULT(hss.ProcessNextHeaders(second_chain, true),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
                      - ]
     176                 :             :         hss, /*exp_state=*/State::FINAL,
     177                 :             :         /*exp_success=*/false, // Foiled! We detected mismatching headers.
     178                 :             :         /*exp_request_more=*/false,
     179                 :             :         /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     180                 :             :         /*exp_locator_hash=*/std::nullopt);
     181                 :           1 : }
     182                 :             : 
     183   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(happy_path)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     184                 :             : {
     185                 :           1 :     const auto& first_chain{FirstChain()};
     186                 :             : 
     187                 :             :     // Headers message that moves us to the next state doesn't need to be full.
     188         [ +  + ]:           3 :     for (const bool full_headers_message : {false, true}) {
     189                 :             :         // This time we feed the first chain twice.
     190                 :           2 :         HeadersSyncState hss{CreateState()};
     191                 :             : 
     192                 :             :         // Sufficient work transitions us from PRESYNC to REDOWNLOAD:
     193         [ +  - ]:           2 :         const auto genesis_hash{genesis.GetHash()};
     194   [ -  +  +  -  :           4 :         CHECK_RESULT(hss.ProcessNextHeaders(first_chain, full_headers_message),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     195                 :             :             hss, /*exp_state=*/State::REDOWNLOAD,
     196                 :             :             /*exp_success=*/true, /*exp_request_more=*/true,
     197                 :             :             /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     198                 :             :             /*exp_locator_hash=*/genesis_hash);
     199                 :             : 
     200                 :             :         // Process only so that the internal threshold isn't exceeded, meaning
     201                 :             :         // validated headers shouldn't be returned yet:
     202   [ +  -  +  -  :           4 :         CHECK_RESULT(hss.ProcessNextHeaders({first_chain.begin(), REDOWNLOAD_BUFFER_SIZE}, true),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     203                 :             :             hss, /*exp_state=*/State::REDOWNLOAD,
     204                 :             :             /*exp_success=*/true, /*exp_request_more=*/true,
     205                 :             :             /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     206                 :             :             /*exp_locator_hash=*/first_chain[REDOWNLOAD_BUFFER_SIZE - 1].GetHash());
     207                 :             : 
     208                 :             :         // We start receiving headers for permanent storage before completing:
     209   [ +  -  +  -  :           4 :         CHECK_RESULT(hss.ProcessNextHeaders({{first_chain[REDOWNLOAD_BUFFER_SIZE]}}, true),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     210                 :             :             hss, /*exp_state=*/State::REDOWNLOAD,
     211                 :             :             /*exp_success=*/true, /*exp_request_more=*/true,
     212                 :             :             /*exp_headers_size=*/1, /*exp_pow_validated_prev=*/genesis_hash,
     213                 :             :             /*exp_locator_hash=*/first_chain[REDOWNLOAD_BUFFER_SIZE].GetHash());
     214                 :             : 
     215                 :             :         // Feed in remaining headers, meeting the work threshold again and
     216                 :             :         // completing the REDOWNLOAD phase:
     217   [ +  -  +  -  :           2 :         CHECK_RESULT(hss.ProcessNextHeaders({first_chain.begin() + REDOWNLOAD_BUFFER_SIZE + 1, first_chain.end()}, full_headers_message),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  -  
          +  +  -  +  -  
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     218                 :             :             hss, /*exp_state=*/State::FINAL,
     219                 :             :             /*exp_success=*/true, /*exp_request_more=*/false,
     220                 :             :             // All headers except the one already returned above:
     221                 :             :             /*exp_headers_size=*/first_chain.size() - 1, /*exp_pow_validated_prev=*/first_chain.front().GetHash(),
     222                 :             :             /*exp_locator_hash=*/std::nullopt);
     223                 :           2 :     }
     224                 :           1 : }
     225                 :             : 
     226   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(too_little_work)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     227                 :             : {
     228                 :           1 :     const auto& second_chain{SecondChain()};
     229                 :             : 
     230                 :             :     // Verify that just trying to process the second chain would not succeed
     231                 :             :     // (too little work).
     232                 :           1 :     HeadersSyncState hss{CreateState()};
     233   [ +  -  +  - ]:           1 :     BOOST_REQUIRE_EQUAL(hss.GetState(), State::PRESYNC);
     234                 :             : 
     235                 :             :     // Pretend just the first message is "full", so we don't abort.
     236   [ +  -  +  -  :           2 :     CHECK_RESULT(hss.ProcessNextHeaders({{second_chain.front()}}, true),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     237                 :             :         hss, /*exp_state=*/State::PRESYNC,
     238                 :             :         /*exp_success=*/true, /*exp_request_more=*/true,
     239                 :             :         /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     240                 :             :         /*exp_locator_hash=*/second_chain.front().GetHash());
     241                 :             : 
     242                 :             :     // Tell the sync logic that the headers message was not full, implying no
     243                 :             :     // more headers can be requested. For a low-work-chain, this should cause
     244                 :             :     // the sync to end with no headers for acceptance.
     245   [ -  +  +  -  :           1 :     CHECK_RESULT(hss.ProcessNextHeaders(std::span{second_chain}.subspan(1), false),
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
                      - ]
     246                 :             :         hss, /*exp_state=*/State::FINAL,
     247                 :             :         // Nevertheless, no validation errors should have been detected with the
     248                 :             :         // chain:
     249                 :             :         /*exp_success=*/true,
     250                 :             :         /*exp_request_more=*/false,
     251                 :             :         /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
     252                 :             :         /*exp_locator_hash=*/std::nullopt);
     253                 :           1 : }
     254                 :             : 
     255                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1