Repository: bitcoin/bitcoin
File: src/test/headers_sync_chainwork_tests.cpp (C++)

Code before:
    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()


Review comment (bug):
👍 for reducing the scope of each separate check.

Was hoping we could compare the result object directly as:
```C++
BOOST_CHECK_EQUAL(result, (HeadersSyncState::ProcessingResult{
    .pow_validated_headers = {},
    .success = true,
    .request_more = true,
}));
```
But unfortunately we'd need to add equality operators to `ProcessingResult` and `CBlockHeader` which would defeat the purpose (+ the `operator<<(std::ostream&` for printing)...

But if we extract a validation helper function instead, we'd have keep the scope reduction and make the differences between the checks less noisy:
```C++
// Standard set of checks common to all scenarios; macro keeps failure lines at the call-site.
#define CHECK_RESULT(res, state, exp_state, exp_success, exp_request_more, exp_headers_size) \
    do {                                                                                     \
        const auto& [res_headers, res_success, res_request_more]{res};                       \
        BOOST_REQUIRE_EQUAL(state.GetState(), exp_state);                                    \
        BOOST_CHECK_EQUAL(res_headers.size(), exp_headers_size);                             \
        BOOST_CHECK_EQUAL(res_success, exp_success);                                         \
        BOOST_CHECK_EQUAL(res_request_more, exp_request_more);                               \
    } while (false)
```

and using it as e.g.
```C++
// Repeat the second set of headers in both phases to demonstrate
// behavior when the chain a peer provides has too little work.
static void TooLittleWork(const CBlockIndex* chain_start, const std::vector<CBlockHeader>& chain2)
{
    // Verify that just trying to process the second chain would not succeed (too little work).
    HeadersSyncState state{CreateState(Params(), HeadersSyncParams{COMMITMENT_PERIOD, REDOWNLOAD_BUFFER_SIZE}, chain_start)};

    // Pretend just the first message is "full", so we don't abort.
    CHECK_RESULT(state.ProcessNextHeaders({chain2.begin(), 1}, /*full_headers_message=*/true),
                 state, State::PRESYNC,
                 /*exp_success=*/true,
                 /*exp_request_more=*/true,
                 /*exp_headers_size=*/0);

    // 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 cause
    // the sync to end with no headers for acceptance.
    CHECK_RESULT(state.ProcessNextHeaders({chain2.begin() + 1, chain2.end()}, /*full_headers_message=*/false),
                 state, State::FINAL,
                 /*exp_success=*/true,
                 /*exp_request_more=*/false,
                 /*exp_headers_size=*/0);
}
```


Code after:
            uint256 prev_hash, int32_t nVersion, uint32_t prev_time,
            const uint256& merkle_root, uint32_t nBits);
};

void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header)
{
    while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) {
        ++starting_header.nNonce;
    }
}

std::vector<CBlockHeader> HeadersGeneratorSetup::GenerateHeaders(
        const size_t count, uint256 prev_hash, const int32_t nVersion,
        uint32_t prev_time, const uint256& merkle_root, const uint32_t nBits)
{
    std::vector<CBlockHeader> headers(count);
    for (auto& next_header : headers) {
        next_header.nVersion = nVersion;
        next_header.hashPrevBlock = prev_hash;
        next_header.hashMerkleRoot = merkle_root;
        next_header.nTime = ++prev_time;
        next_header.nBits = nBits;

        FindProofOfWork(next_header);
        prev_hash = next_header.GetHash();
    }
    return headers;
}

// 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.
//    Then we deliver the second set of headers and verify that they fail
//    processing (presumably due to commitments not matching).
// 2. Verify that repeating with the first set of headers in both phases is
//    successful.
// 3. Repeat the second set of headers in both phases to demonstrate behavior
//    when the chain a peer provides has too little work.
BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup)

BOOST_AUTO_TEST_CASE(sneaky_redownload)
{
    const auto& first_chain{FirstChain()};
    const auto& second_chain{SecondChain()};

    // Feed the first chain to HeadersSyncState, by delivering 1 header
    // initially and then the rest.
    HeadersSyncState hss{CreateState()};

    // Just feed one header and check state.