Skip to content

Commit 0b6aa82

Browse files
committed
Add unit test for HeadersSyncState
1 parent 83c6a0c commit 0b6aa82

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ BITCOIN_TESTS =\
9393
test/fs_tests.cpp \
9494
test/getarg_tests.cpp \
9595
test/hash_tests.cpp \
96+
test/headers_sync_chainwork_tests.cpp \
9697
test/httpserver_tests.cpp \
9798
test/i2p_tests.cpp \
9899
test/interfaces_tests.cpp \
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright (c) 2022 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 <pow.h>
10+
#include <test/util/setup_common.h>
11+
#include <validation.h>
12+
#include <vector>
13+
14+
#include <boost/test/unit_test.hpp>
15+
16+
struct HeadersGeneratorSetup : public RegTestingSetup {
17+
/** Search for a nonce to meet (regtest) proof of work */
18+
void FindProofOfWork(CBlockHeader& starting_header);
19+
/**
20+
* Generate headers in a chain that build off a given starting hash, using
21+
* the given nVersion, advancing time by 1 second from the starting
22+
* prev_time, and with a fixed merkle root hash.
23+
*/
24+
void GenerateHeaders(std::vector<CBlockHeader>& headers, size_t count,
25+
const uint256& starting_hash, const int nVersion, int prev_time,
26+
const uint256& merkle_root, const uint32_t nBits);
27+
};
28+
29+
void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header)
30+
{
31+
while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) {
32+
++(starting_header.nNonce);
33+
}
34+
}
35+
36+
void HeadersGeneratorSetup::GenerateHeaders(std::vector<CBlockHeader>& headers,
37+
size_t count, const uint256& starting_hash, const int nVersion, int prev_time,
38+
const uint256& merkle_root, const uint32_t nBits)
39+
{
40+
uint256 prev_hash = starting_hash;
41+
42+
while (headers.size() < count) {
43+
headers.push_back(CBlockHeader());
44+
CBlockHeader& next_header = headers.back();;
45+
next_header.nVersion = nVersion;
46+
next_header.hashPrevBlock = prev_hash;
47+
next_header.hashMerkleRoot = merkle_root;
48+
next_header.nTime = prev_time+1;
49+
next_header.nBits = nBits;
50+
51+
FindProofOfWork(next_header);
52+
prev_hash = next_header.GetHash();
53+
prev_time = next_header.nTime;
54+
}
55+
return;
56+
}
57+
58+
BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup)
59+
60+
// In this test, we construct two sets of headers from genesis, one with
61+
// sufficient proof of work and one without.
62+
// 1. We deliver the first set of headers and verify that the headers sync state
63+
// updates to the REDOWNLOAD phase successfully.
64+
// 2. Then we deliver the second set of headers and verify that they fail
65+
// processing (presumably due to commitments not matching).
66+
// 3. Finally, we verify that repeating with the first set of headers in both
67+
// phases is successful.
68+
BOOST_AUTO_TEST_CASE(headers_sync_state)
69+
{
70+
std::vector<CBlockHeader> first_chain;
71+
std::vector<CBlockHeader> second_chain;
72+
73+
std::unique_ptr<HeadersSyncState> hss;
74+
75+
const int target_blocks = 15000;
76+
arith_uint256 chain_work = target_blocks*2;
77+
78+
// Generate headers for two different chains (using differing merkle roots
79+
// to ensure the headers are different).
80+
GenerateHeaders(first_chain, target_blocks-1, Params().GenesisBlock().GetHash(),
81+
Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime,
82+
ArithToUint256(0), Params().GenesisBlock().nBits);
83+
84+
GenerateHeaders(second_chain, target_blocks-2, Params().GenesisBlock().GetHash(),
85+
Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime,
86+
ArithToUint256(1), Params().GenesisBlock().nBits);
87+
88+
const CBlockIndex* chain_start = WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(Params().GenesisBlock().GetHash()));
89+
std::vector<CBlockHeader> headers_batch;
90+
91+
// Feed the first chain to HeadersSyncState, by delivering 1 header
92+
// initially and then the rest.
93+
headers_batch.insert(headers_batch.end(), std::next(first_chain.begin()), first_chain.end());
94+
95+
hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work));
96+
(void)hss->ProcessNextHeaders({first_chain.front()}, true);
97+
// Pretend the first header is still "full", so we don't abort.
98+
auto result = hss->ProcessNextHeaders(headers_batch, true);
99+
100+
// This chain should look valid, and we should have met the proof-of-work
101+
// requirement.
102+
BOOST_CHECK(result.success);
103+
BOOST_CHECK(result.request_more);
104+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD);
105+
106+
// Try to sneakily feed back the second chain.
107+
result = hss->ProcessNextHeaders(second_chain, true);
108+
BOOST_CHECK(!result.success); // foiled!
109+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
110+
111+
// Now try again, this time feeding the first chain twice.
112+
hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work));
113+
(void)hss->ProcessNextHeaders(first_chain, true);
114+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD);
115+
116+
result = hss->ProcessNextHeaders(first_chain, true);
117+
BOOST_CHECK(result.success);
118+
BOOST_CHECK(!result.request_more);
119+
// All headers should be ready for acceptance:
120+
BOOST_CHECK(result.pow_validated_headers.size() == first_chain.size());
121+
// Nothing left for the sync logic to do:
122+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
123+
124+
// Finally, verify that just trying to process the second chain would not
125+
// succeed (too little work)
126+
hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work));
127+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC);
128+
// Pretend just the first message is "full", so we don't abort.
129+
(void)hss->ProcessNextHeaders({second_chain.front()}, true);
130+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC);
131+
132+
headers_batch.clear();
133+
headers_batch.insert(headers_batch.end(), std::next(second_chain.begin(), 1), second_chain.end());
134+
// Tell the sync logic that the headers message was not full, implying no
135+
// more headers can be requested. For a low-work-chain, this should causes
136+
// the sync to end with no headers for acceptance.
137+
result = hss->ProcessNextHeaders(headers_batch, false);
138+
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
139+
BOOST_CHECK(result.pow_validated_headers.empty());
140+
BOOST_CHECK(!result.request_more);
141+
// Nevertheless, no validation errors should have been detected with the
142+
// chain:
143+
BOOST_CHECK(result.success);
144+
}
145+
146+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)