Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
147 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Copyright (c) 2022 The Bitcoin Core developers | ||
// Distributed under the MIT software license, see the accompanying | ||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
#include <chain.h> | ||
#include <chainparams.h> | ||
#include <consensus/params.h> | ||
#include <headerssync.h> | ||
#include <pow.h> | ||
#include <test/util/setup_common.h> | ||
#include <validation.h> | ||
#include <vector> | ||
|
||
#include <boost/test/unit_test.hpp> | ||
|
||
struct HeadersGeneratorSetup : public RegTestingSetup { | ||
/** Search for a nonce to meet (regtest) proof of work */ | ||
void FindProofOfWork(CBlockHeader& starting_header); | ||
/** | ||
* Generate headers in a chain that build off a given starting hash, using | ||
* the given nVersion, advancing time by 1 second from the starting | ||
* prev_time, and with a fixed merkle root hash. | ||
*/ | ||
void GenerateHeaders(std::vector<CBlockHeader>& headers, size_t count, | ||
const uint256& starting_hash, const int nVersion, int prev_time, | ||
const uint256& merkle_root, const uint32_t nBits); | ||
}; | ||
|
||
void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header) | ||
{ | ||
while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) { | ||
++(starting_header.nNonce); | ||
} | ||
} | ||
|
||
void HeadersGeneratorSetup::GenerateHeaders(std::vector<CBlockHeader>& headers, | ||
size_t count, const uint256& starting_hash, const int nVersion, int prev_time, | ||
const uint256& merkle_root, const uint32_t nBits) | ||
{ | ||
uint256 prev_hash = starting_hash; | ||
|
||
while (headers.size() < count) { | ||
headers.push_back(CBlockHeader()); | ||
CBlockHeader& next_header = headers.back();; | ||
next_header.nVersion = nVersion; | ||
next_header.hashPrevBlock = prev_hash; | ||
next_header.hashMerkleRoot = merkle_root; | ||
next_header.nTime = prev_time+1; | ||
next_header.nBits = nBits; | ||
|
||
FindProofOfWork(next_header); | ||
prev_hash = next_header.GetHash(); | ||
prev_time = next_header.nTime; | ||
} | ||
return; | ||
} | ||
|
||
BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup) | ||
|
||
// In this test, we construct two sets of headers from genesis, one with | ||
// sufficient proof of work and one without. | ||
// 1. We deliver the first set of headers and verify that the headers sync state | ||
// updates to the REDOWNLOAD phase successfully. | ||
// 2. Then we deliver the second set of headers and verify that they fail | ||
// processing (presumably due to commitments not matching). | ||
// 3. Finally, we verify that repeating with the first set of headers in both | ||
// phases is successful. | ||
BOOST_AUTO_TEST_CASE(headers_sync_state) | ||
{ | ||
std::vector<CBlockHeader> first_chain; | ||
std::vector<CBlockHeader> second_chain; | ||
|
||
HeadersSyncState *hss = new HeadersSyncState(0, Params().GetConsensus()); | ||
|
||
const int target_blocks = 15000; | ||
arith_uint256 chain_work = target_blocks*2; | ||
|
||
// Generate headers for two different chains (using differing merkle roots | ||
// to ensure the headers are different). | ||
GenerateHeaders(first_chain, target_blocks-1, Params().GenesisBlock().GetHash(), | ||
Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime, | ||
ArithToUint256(0), Params().GenesisBlock().nBits); | ||
|
||
GenerateHeaders(second_chain, target_blocks-2, Params().GenesisBlock().GetHash(), | ||
Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime, | ||
ArithToUint256(1), Params().GenesisBlock().nBits); | ||
|
||
const CBlockIndex* chain_start = WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(Params().GenesisBlock().GetHash())); | ||
std::vector<CBlockHeader> headers_batch; | ||
std::vector<CBlockHeader> headers_to_process; | ||
bool success{false}; | ||
|
||
// Feed the first chain to HeadersSyncState, by delivering 1 header | ||
// initially and then the rest. | ||
headers_batch.insert(headers_batch.end(), std::next(first_chain.begin(), 1), first_chain.end()); | ||
|
||
(void)hss->StartInitialDownload(chain_start, {first_chain.front()}, chain_work, m_node.chainman->ActiveChain().GetLocator(chain_start)); | ||
(void)hss->ProcessNextHeaders(headers_batch, true, headers_to_process, success); | ||
|
||
// This chain should look valid, and we should have met the proof-of-work | ||
// requirement. | ||
BOOST_CHECK(success); | ||
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD); | ||
|
||
// Try to sneakily feed back the second chain. | ||
(void)hss->ProcessNextHeaders(second_chain, true, headers_to_process, success); | ||
BOOST_CHECK(!success); // foiled! | ||
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); | ||
|
||
delete hss; hss = new HeadersSyncState(0, Params().GetConsensus()); | ||
|
||
// Now try again, this time feeding the first chain twice. | ||
(void)hss->StartInitialDownload(chain_start, first_chain, chain_work, | ||
m_node.chainman->ActiveChain().GetLocator(chain_start)); | ||
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD); | ||
|
||
(void)hss->ProcessNextHeaders(first_chain, true, headers_to_process, success); | ||
BOOST_CHECK(success); | ||
// All headers should be ready for acceptance: | ||
BOOST_CHECK(headers_to_process.size() == first_chain.size()); | ||
// Nothing left for the sync logic to do: | ||
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); | ||
|
||
delete hss; hss = new HeadersSyncState(0, Params().GetConsensus()); | ||
|
||
// Finally, verify that just trying to process the second chain would not | ||
// succeed (too little work) | ||
(void)hss->StartInitialDownload(chain_start, {second_chain.front()}, | ||
chain_work, m_node.chainman->ActiveChain().GetLocator(chain_start)); | ||
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::INITIAL_DOWNLOAD); | ||
|
||
headers_batch.clear(); | ||
headers_batch.insert(headers_batch.end(), std::next(second_chain.begin(), 1), second_chain.end()); | ||
headers_to_process.clear(); | ||
// Tell the sync logic that the headers message was not full, implying no | ||
// more headers can be requested. For a low-work-chain, this should causes | ||
// the sync to end with no headers for acceptance. | ||
(void)hss->ProcessNextHeaders(headers_batch, false, headers_to_process, success); | ||
BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); | ||
BOOST_CHECK(headers_to_process.empty()); | ||
// Nevertheless, no validation errors should have been detected with the | ||
// chain: | ||
BOOST_CHECK(success); | ||
} | ||
|
||
BOOST_AUTO_TEST_SUITE_END() |