Skip to content

Commit

Permalink
Add unit test for HeadersSyncState
Browse files Browse the repository at this point in the history
  • Loading branch information
sdaftuar committed Jul 26, 2022
1 parent 0b59d3b commit 4c73bd2
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Expand Up @@ -93,6 +93,7 @@ BITCOIN_TESTS =\
test/fs_tests.cpp \
test/getarg_tests.cpp \
test/hash_tests.cpp \
test/headers_sync_chainwork.cpp \
test/httpserver_tests.cpp \
test/i2p_tests.cpp \
test/interfaces_tests.cpp \
Expand Down
146 changes: 146 additions & 0 deletions src/test/headers_sync_chainwork.cpp
@@ -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()

0 comments on commit 4c73bd2

Please sign in to comment.