Bitcoin Core  31.0.0
P2P Digital Currency
headers_sync_chainwork_tests.cpp
Go to the documentation of this file.
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 
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 struct HeadersGeneratorSetup : public RegTestingSetup {
55  const CBlock& genesis{Params().GenesisBlock()};
56  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  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  assert(genesis.nBits == 0x207fffff);
67 
68  // Subtract 1 since the genesis block also contributes work so we reach
69  // the CHAIN_WORK target.
70  static const auto first_chain{GenerateHeaders(/*count=*/TARGET_BLOCKS - 1, genesis.GetHash(),
71  genesis.nVersion, genesis.nTime, /*merkle_root=*/uint256::ZERO, genesis.nBits)};
72  return first_chain;
73  }
74  const std::vector<CBlockHeader>& SecondChain()
75  {
76  // Subtract 2 to keep total work below the target.
77  static const auto second_chain{GenerateHeaders(/*count=*/TARGET_BLOCKS - 2, genesis.GetHash(),
78  genesis.nVersion, genesis.nTime, /*merkle_root=*/uint256::ONE, genesis.nBits)};
79  return second_chain;
80  }
81 
82  HeadersSyncState CreateState()
83  {
84  return {/*id=*/0,
85  Params().GetConsensus(),
86  HeadersSyncParams{
87  .commitment_period = COMMITMENT_PERIOD,
88  .redownload_buffer_size = REDOWNLOAD_BUFFER_SIZE,
89  },
90  chain_start,
91  /*minimum_required_work=*/CHAIN_WORK};
92  }
93 
94 private:
96  void FindProofOfWork(CBlockHeader& starting_header);
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 void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header)
108 {
109  while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) {
110  ++starting_header.nNonce;
111  }
112 }
113 
114 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  std::vector<CBlockHeader> headers(count);
119  for (auto& next_header : headers) {
120  next_header.nVersion = nVersion;
121  next_header.hashPrevBlock = prev_hash;
122  next_header.hashMerkleRoot = merkle_root;
123  next_header.nTime = ++prev_time;
124  next_header.nBits = nBits;
125 
126  FindProofOfWork(next_header);
127  prev_hash = next_header.GetHash();
128  }
129  return headers;
130 }
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 BOOST_AUTO_TEST_CASE(sneaky_redownload)
145 {
146  const auto& first_chain{FirstChain()};
147  const auto& second_chain{SecondChain()};
148 
149  // Feed the first chain to HeadersSyncState, by delivering 1 header
150  // initially and then the rest.
151  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  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  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  static_assert(TARGET_BLOCKS / COMMITMENT_PERIOD == 25);
173 
174  // Try to sneakily feed back the second chain during REDOWNLOAD.
175  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 }
182 
184 {
185  const auto& first_chain{FirstChain()};
186 
187  // Headers message that moves us to the next state doesn't need to be full.
188  for (const bool full_headers_message : {false, true}) {
189  // This time we feed the first chain twice.
190  HeadersSyncState hss{CreateState()};
191 
192  // Sufficient work transitions us from PRESYNC to REDOWNLOAD:
193  const auto genesis_hash{genesis.GetHash()};
194  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  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  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  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  }
224 }
225 
226 BOOST_AUTO_TEST_CASE(too_little_work)
227 {
228  const auto& second_chain{SecondChain()};
229 
230  // Verify that just trying to process the second chain would not succeed
231  // (too little work).
232  HeadersSyncState hss{CreateState()};
233  BOOST_REQUIRE_EQUAL(hss.GetState(), State::PRESYNC);
234 
235  // Pretend just the first message is "full", so we don't abort.
236  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  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 }
254 
constexpr size_t REDOWNLOAD_BUFFER_SIZE
State
The various states a (txhash,peer) pair can be in.
Definition: txrequest.cpp:42
constexpr size_t COMMITMENT_PERIOD
constexpr size_t TARGET_BLOCKS
BOOST_AUTO_TEST_SUITE_END()
HeadersSyncState:
Definition: headerssync.h:102
BOOST_AUTO_TEST_CASE(sneaky_redownload)
#define CHECK_RESULT(result_expression, hss, exp_state, exp_success, exp_request_more, exp_headers_size, exp_pow_validated_prev, exp_locator_hash)