From 0b6aa826b53470c9cc8ef4a153fa710dce80882f Mon Sep 17 00:00:00 2001 From: Suhas Daftuar Date: Wed, 9 Mar 2022 14:45:45 -0500 Subject: [PATCH] Add unit test for HeadersSyncState --- src/Makefile.test.include | 1 + src/test/headers_sync_chainwork_tests.cpp | 146 ++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/test/headers_sync_chainwork_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e80eb598f2b..8a2386a2b4b 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -93,6 +93,7 @@ BITCOIN_TESTS =\ test/fs_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ + test/headers_sync_chainwork_tests.cpp \ test/httpserver_tests.cpp \ test/i2p_tests.cpp \ test/interfaces_tests.cpp \ diff --git a/src/test/headers_sync_chainwork_tests.cpp b/src/test/headers_sync_chainwork_tests.cpp new file mode 100644 index 00000000000..41241ebee28 --- /dev/null +++ b/src/test/headers_sync_chainwork_tests.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 +#include +#include +#include +#include +#include +#include +#include + +#include + +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& 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& 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 first_chain; + std::vector second_chain; + + std::unique_ptr hss; + + 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 headers_batch; + + // 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()), first_chain.end()); + + hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work)); + (void)hss->ProcessNextHeaders({first_chain.front()}, true); + // Pretend the first header is still "full", so we don't abort. + auto result = hss->ProcessNextHeaders(headers_batch, true); + + // This chain should look valid, and we should have met the proof-of-work + // requirement. + BOOST_CHECK(result.success); + BOOST_CHECK(result.request_more); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD); + + // Try to sneakily feed back the second chain. + result = hss->ProcessNextHeaders(second_chain, true); + BOOST_CHECK(!result.success); // foiled! + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); + + // Now try again, this time feeding the first chain twice. + hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work)); + (void)hss->ProcessNextHeaders(first_chain, true); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD); + + result = hss->ProcessNextHeaders(first_chain, true); + BOOST_CHECK(result.success); + BOOST_CHECK(!result.request_more); + // All headers should be ready for acceptance: + BOOST_CHECK(result.pow_validated_headers.size() == first_chain.size()); + // Nothing left for the sync logic to do: + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); + + // Finally, verify that just trying to process the second chain would not + // succeed (too little work) + hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work)); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC); + // Pretend just the first message is "full", so we don't abort. + (void)hss->ProcessNextHeaders({second_chain.front()}, true); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC); + + headers_batch.clear(); + headers_batch.insert(headers_batch.end(), std::next(second_chain.begin(), 1), second_chain.end()); + // 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. + result = hss->ProcessNextHeaders(headers_batch, false); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); + BOOST_CHECK(result.pow_validated_headers.empty()); + BOOST_CHECK(!result.request_more); + // Nevertheless, no validation errors should have been detected with the + // chain: + BOOST_CHECK(result.success); +} + +BOOST_AUTO_TEST_SUITE_END()