Bitcoin Core 31.99.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>
13#include <test/util/time.h>
14#include <validation.h>
15
16#include <cstddef>
17#include <vector>
18
19#include <boost/test/unit_test.hpp>
20
22
23// Standard set of checks common to all scenarios. Macro keeps failure lines at the call-site.
24#define CHECK_RESULT(result_expression, hss, exp_state, exp_success, exp_request_more, \
25 exp_headers_size, exp_pow_validated_prev, exp_locator_hash) \
26 do { \
27 const auto result{result_expression}; \
28 BOOST_REQUIRE_EQUAL(hss.GetState(), exp_state); \
29 BOOST_CHECK_EQUAL(result.success, exp_success); \
30 BOOST_CHECK_EQUAL(result.request_more, exp_request_more); \
31 BOOST_CHECK_EQUAL(result.pow_validated_headers.size(), exp_headers_size); \
32 const std::optional<uint256> pow_validated_prev_opt{exp_pow_validated_prev}; \
33 if (pow_validated_prev_opt) { \
34 BOOST_CHECK_EQUAL(result.pow_validated_headers.at(0).hashPrevBlock, pow_validated_prev_opt); \
35 } else { \
36 BOOST_CHECK_EQUAL(exp_headers_size, 0); \
37 } \
38 const std::optional<uint256> locator_hash_opt{exp_locator_hash}; \
39 if (locator_hash_opt) { \
40 BOOST_CHECK_EQUAL(hss.NextHeadersRequestLocator().vHave.at(0), locator_hash_opt); \
41 } else { \
42 BOOST_CHECK_EQUAL(exp_state, State::FINAL); \
43 } \
44 } while (false)
45
46constexpr size_t TARGET_BLOCKS{15'000};
48
49// Subtract MAX_HEADERS_RESULTS (2000 headers/message) + an arbitrary smaller
50// value (123) so our redownload buffer is well below the number of blocks
51// required to reach the CHAIN_WORK threshold, to behave similarly to mainnet.
53constexpr size_t COMMITMENT_PERIOD{600}; // Somewhat close to mainnet.
54
57 CBlockIndex& chain_start{WITH_LOCK(::cs_main, return *Assert(m_node.chainman->m_blockman.LookupBlockIndex(genesis.GetHash())))};
58
59 // Generate headers for two different chains (using differing merkle roots
60 // to ensure the headers are different).
61 const std::vector<CBlockHeader>& FirstChain()
62 {
63 // Block header hash target is half of max uint256 (2**256 / 2), expressible
64 // roughly as the coefficient 0x7fffff with the exponent 0x20 (32 bytes).
65 // This implies around every 2nd hash attempt should succeed, which
66 // is why CHAIN_WORK == TARGET_BLOCKS * 2.
67 assert(genesis.nBits == 0x207fffff);
68
69 // Subtract 1 since the genesis block also contributes work so we reach
70 // the CHAIN_WORK target.
71 static const auto first_chain{GenerateHeaders(/*count=*/TARGET_BLOCKS - 1, genesis.GetHash(),
73 return first_chain;
74 }
75 const std::vector<CBlockHeader>& SecondChain()
76 {
77 // Subtract 2 to keep total work below the target.
78 static const auto second_chain{GenerateHeaders(/*count=*/TARGET_BLOCKS - 2, genesis.GetHash(),
80 return second_chain;
81 }
82
84 {
85 return {/*id=*/0,
89 .redownload_buffer_size = REDOWNLOAD_BUFFER_SIZE,
90 },
92 /*minimum_required_work=*/CHAIN_WORK};
93 }
94
95private:
97 void FindProofOfWork(CBlockHeader& starting_header);
103 std::vector<CBlockHeader> GenerateHeaders(size_t count,
104 uint256 prev_hash, int32_t nVersion, uint32_t prev_time,
105 const uint256& merkle_root, uint32_t nBits);
106};
107
109{
110 while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) {
111 ++starting_header.nNonce;
112 }
113}
114
116 const size_t count, uint256 prev_hash, const int32_t nVersion,
117 uint32_t prev_time, const uint256& merkle_root, const uint32_t nBits)
118{
119 std::vector<CBlockHeader> headers(count);
120 for (auto& next_header : headers) {
121 next_header.nVersion = nVersion;
122 next_header.hashPrevBlock = prev_hash;
123 next_header.hashMerkleRoot = merkle_root;
124 next_header.nTime = ++prev_time;
125 next_header.nBits = nBits;
126
127 FindProofOfWork(next_header);
128 prev_hash = next_header.GetHash();
129 }
130 return headers;
131}
132
133// In this test, we construct two sets of headers from genesis, one with
134// sufficient proof of work and one without.
135// 1. We deliver the first set of headers and verify that the headers sync state
136// updates to the REDOWNLOAD phase successfully.
137// Then we deliver the second set of headers and verify that they fail
138// processing (presumably due to commitments not matching).
139// 2. Verify that repeating with the first set of headers in both phases is
140// successful.
141// 3. Repeat the second set of headers in both phases to demonstrate behavior
142// when the chain a peer provides has too little work.
143BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup)
144
145BOOST_AUTO_TEST_CASE(sneaky_redownload)
146{
147 const auto& first_chain{FirstChain()};
148 const auto& second_chain{SecondChain()};
149
150 // Feed the first chain to HeadersSyncState, by delivering 1 header
151 // initially and then the rest.
152 HeadersSyncState hss{CreateState()};
153
154 // Just feed one header and check state.
155 // Pretend the message is still "full", so we don't abort.
156 CHECK_RESULT(hss.ProcessNextHeaders({{first_chain.front()}}, /*full_headers_message=*/true),
157 hss, /*exp_state=*/State::PRESYNC,
158 /*exp_success=*/true, /*exp_request_more=*/true,
159 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
160 /*exp_locator_hash=*/first_chain.front().GetHash());
161
162 // This chain should look valid, and we should have met the proof-of-work
163 // requirement during PRESYNC and transitioned to REDOWNLOAD.
164 CHECK_RESULT(hss.ProcessNextHeaders(std::span{first_chain}.subspan(1), true),
165 hss, /*exp_state=*/State::REDOWNLOAD,
166 /*exp_success=*/true, /*exp_request_more=*/true,
167 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
168 /*exp_locator_hash=*/genesis.GetHash());
169
170 // Below is the number of commitment bits that must randomly match between
171 // the two chains for this test to spuriously fail. 1 / 2^25 =
172 // 1 in 33'554'432 (somewhat less due to HeadersSyncState::m_commit_offset).
173 static_assert(TARGET_BLOCKS / COMMITMENT_PERIOD == 25);
174
175 // Try to sneakily feed back the second chain during REDOWNLOAD.
176 CHECK_RESULT(hss.ProcessNextHeaders(second_chain, true),
177 hss, /*exp_state=*/State::FINAL,
178 /*exp_success=*/false, // Foiled! We detected mismatching headers.
179 /*exp_request_more=*/false,
180 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
181 /*exp_locator_hash=*/std::nullopt);
182}
183
185{
186 const auto& first_chain{FirstChain()};
187
188 // Headers message that moves us to the next state doesn't need to be full.
189 for (const bool full_headers_message : {false, true}) {
190 // This time we feed the first chain twice.
191 HeadersSyncState hss{CreateState()};
192
193 // Sufficient work transitions us from PRESYNC to REDOWNLOAD:
194 const auto genesis_hash{genesis.GetHash()};
195 CHECK_RESULT(hss.ProcessNextHeaders(first_chain, full_headers_message),
196 hss, /*exp_state=*/State::REDOWNLOAD,
197 /*exp_success=*/true, /*exp_request_more=*/true,
198 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
199 /*exp_locator_hash=*/genesis_hash);
200
201 // Process only so that the internal threshold isn't exceeded, meaning
202 // validated headers shouldn't be returned yet:
203 CHECK_RESULT(hss.ProcessNextHeaders({first_chain.begin(), REDOWNLOAD_BUFFER_SIZE}, true),
204 hss, /*exp_state=*/State::REDOWNLOAD,
205 /*exp_success=*/true, /*exp_request_more=*/true,
206 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
207 /*exp_locator_hash=*/first_chain[REDOWNLOAD_BUFFER_SIZE - 1].GetHash());
208
209 // We start receiving headers for permanent storage before completing:
210 CHECK_RESULT(hss.ProcessNextHeaders({{first_chain[REDOWNLOAD_BUFFER_SIZE]}}, true),
211 hss, /*exp_state=*/State::REDOWNLOAD,
212 /*exp_success=*/true, /*exp_request_more=*/true,
213 /*exp_headers_size=*/1, /*exp_pow_validated_prev=*/genesis_hash,
214 /*exp_locator_hash=*/first_chain[REDOWNLOAD_BUFFER_SIZE].GetHash());
215
216 // Feed in remaining headers, meeting the work threshold again and
217 // completing the REDOWNLOAD phase:
218 CHECK_RESULT(hss.ProcessNextHeaders({first_chain.begin() + REDOWNLOAD_BUFFER_SIZE + 1, first_chain.end()}, full_headers_message),
219 hss, /*exp_state=*/State::FINAL,
220 /*exp_success=*/true, /*exp_request_more=*/false,
221 // All headers except the one already returned above:
222 /*exp_headers_size=*/first_chain.size() - 1, /*exp_pow_validated_prev=*/first_chain.front().GetHash(),
223 /*exp_locator_hash=*/std::nullopt);
224 }
225}
226
227BOOST_AUTO_TEST_CASE(too_little_work)
228{
229 const auto& second_chain{SecondChain()};
230
231 // Verify that just trying to process the second chain would not succeed
232 // (too little work).
233 HeadersSyncState hss{CreateState()};
234 BOOST_REQUIRE_EQUAL(hss.GetState(), State::PRESYNC);
235
236 // Pretend just the first message is "full", so we don't abort.
237 CHECK_RESULT(hss.ProcessNextHeaders({{second_chain.front()}}, true),
238 hss, /*exp_state=*/State::PRESYNC,
239 /*exp_success=*/true, /*exp_request_more=*/true,
240 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
241 /*exp_locator_hash=*/second_chain.front().GetHash());
242
243 // Tell the sync logic that the headers message was not full, implying no
244 // more headers can be requested. For a low-work-chain, this should cause
245 // the sync to end with no headers for acceptance.
246 CHECK_RESULT(hss.ProcessNextHeaders(std::span{second_chain}.subspan(1), false),
247 hss, /*exp_state=*/State::FINAL,
248 // Nevertheless, no validation errors should have been detected with the
249 // chain:
250 /*exp_success=*/true,
251 /*exp_request_more=*/false,
252 /*exp_headers_size=*/0, /*exp_pow_validated_prev=*/std::nullopt,
253 /*exp_locator_hash=*/std::nullopt);
254}
255
256BOOST_AUTO_TEST_CASE(system_clock_lagging_behind_chain_start)
257{
258 FakeNodeClock clock{(chain_start.GetBlockTime() - MAX_FUTURE_BLOCK_TIME) * 1s};
259 BOOST_CHECK_NO_THROW(CreateState());
260
261 clock -= 1s;
263}
264
constexpr int64_t MAX_FUTURE_BLOCK_TIME
Maximum amount of time that a block timestamp is allowed to exceed the current time before the block ...
Definition: chain.h:29
const CChainParams & Params()
Return the currently selected parameters.
#define Assert(val)
Identity function.
Definition: check.h:116
Nodes collect new transactions into a block, hash them into a hash tree, and scan through nonce value...
Definition: block.h:27
uint32_t nNonce
Definition: block.h:35
uint32_t nBits
Definition: block.h:34
uint32_t nTime
Definition: block.h:33
int32_t nVersion
Definition: block.h:30
uint256 GetHash() const
Definition: block.cpp:14
Definition: block.h:74
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:94
const CBlock & GenesisBlock() const
Definition: chainparams.h:94
const Consensus::Params & GetConsensus() const
Definition: chainparams.h:89
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition: time.h:54
HeadersSyncState:
Definition: headerssync.h:104
@ FINAL
We're done syncing with this peer and can discard any remaining state.
@ PRESYNC
PRESYNC means the peer has not yet demonstrated their chain has sufficient work and we're only buildi...
@ REDOWNLOAD
REDOWNLOAD means the peer has given us a high-enough-work chain, and now we're redownloading the head...
256-bit unsigned big integer.
256-bit opaque blob.
Definition: uint256.h:196
static const uint256 ONE
Definition: uint256.h:205
static const uint256 ZERO
Definition: uint256.h:204
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:8
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
constexpr size_t REDOWNLOAD_BUFFER_SIZE
constexpr size_t TARGET_BLOCKS
#define CHECK_RESULT(result_expression, hss, exp_state, exp_success, exp_request_more, exp_headers_size, exp_pow_validated_prev, exp_locator_hash)
BOOST_AUTO_TEST_CASE(sneaky_redownload)
constexpr size_t COMMITMENT_PERIOD
constexpr arith_uint256 CHAIN_WORK
HTTPHeaders headers
constexpr unsigned int MAX_HEADERS_RESULTS
Number of headers sent in one getheaders result.
#define BOOST_CHECK_THROW(stmt, excMatch)
Definition: object.cpp:18
#define BOOST_CHECK_NO_THROW(stmt)
Definition: object.cpp:27
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params &params)
Check whether a block hash satisfies the proof-of-work requirement specified by nBits.
Definition: pow.cpp:140
node::NodeContext m_node
Definition: setup_common.h:60
void FindProofOfWork(CBlockHeader &starting_header)
Search for a nonce to meet (regtest) proof of work.
std::vector< CBlockHeader > GenerateHeaders(size_t count, uint256 prev_hash, int32_t nVersion, uint32_t prev_time, const uint256 &merkle_root, uint32_t nBits)
Generate headers in a chain that build off a given starting hash, using the given nVersion,...
const std::vector< CBlockHeader > & SecondChain()
const std::vector< CBlockHeader > & FirstChain()
Configuration for headers sync memory usage.
Definition: chainparams.h:64
size_t commitment_period
Distance in blocks between header commitments.
Definition: chainparams.h:66
Identical to TestingSetup, but chain set to regtest.
Definition: setup_common.h:122
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:76
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:299
static int count
assert(!tx.IsCoinBase())