|
| 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