diff --git a/src/chain.cpp b/src/chain.cpp index 4e2d1bf0ac8..3dd22634110 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -5,6 +5,7 @@ #include #include +#include #include std::string CBlockFileInfo::ToString() const @@ -158,18 +159,26 @@ int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& fr /** Find the last common ancestor two blocks have. * Both pa and pb must be non-nullptr. */ const CBlockIndex* LastCommonAncestor(const CBlockIndex* pa, const CBlockIndex* pb) { + // First rewind to the last common height (the forking point cannot be past one of the two). if (pa->nHeight > pb->nHeight) { pa = pa->GetAncestor(pb->nHeight); } else if (pb->nHeight > pa->nHeight) { pb = pb->GetAncestor(pa->nHeight); } - - while (pa != pb && pa && pb) { + while (pa != pb) { + // Jump back until pa and pb have a common "skip" ancestor. + while (pa->pskip != pb->pskip) { + // This logic relies on the property that equal-height blocks have equal-height skip + // pointers. + Assume(pa->nHeight == pb->nHeight); + Assume(pa->pskip->nHeight == pb->pskip->nHeight); + pa = pa->pskip; + pb = pb->pskip; + } + // At this point, pa and pb are different, but have equal pskip. The forking point lies in + // between pa/pb on the one end, and pa->pskip/pb->pskip on the other end. pa = pa->pprev; pb = pb->pprev; } - - // Eventually all chain branches meet at the genesis block. - assert(pa == pb); return pa; } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index aacefb3f85c..b4c1ec69339 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable(test_bitcoin bloom_tests.cpp bswap_tests.cpp caches_tests.cpp + chain_tests.cpp chainstate_write_tests.cpp checkqueue_tests.cpp cluster_linearize_tests.cpp diff --git a/src/test/chain_tests.cpp b/src/test/chain_tests.cpp new file mode 100644 index 00000000000..a782e880f9f --- /dev/null +++ b/src/test/chain_tests.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 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 + +BOOST_FIXTURE_TEST_SUITE(chain_tests, BasicTestingSetup) + +namespace { + +const CBlockIndex* NaiveGetAncestor(const CBlockIndex* a, int height) +{ + while (a->nHeight > height) { + a = a->pprev; + } + BOOST_REQUIRE_EQUAL(a->nHeight, height); + return a; +} + +const CBlockIndex* NaiveLastCommonAncestor(const CBlockIndex* a, const CBlockIndex* b) +{ + while (a->nHeight > b->nHeight) { + a = a->pprev; + } + while (b->nHeight > a->nHeight) { + b = b->pprev; + } + while (a != b) { + BOOST_REQUIRE_EQUAL(a->nHeight, b->nHeight); + a = a->pprev; + b = b->pprev; + } + BOOST_REQUIRE_EQUAL(a, b); + return a; +} + +} // namespace + +BOOST_AUTO_TEST_CASE(chain_test) +{ + FastRandomContext ctx; + std::vector> block_index; + // Run 10 iterations of the whole test. + for (int i = 0; i < 10; ++i) { + block_index.clear(); + // Create genesis block. + auto genesis = std::make_unique(); + genesis->nHeight = 0; + block_index.push_back(std::move(genesis)); + // Create 10000 more blocks. + for (int b = 0; b < 10000; ++b) { + auto new_index = std::make_unique(); + // 95% of blocks build on top of the last block; the others fork off randomly. + if (ctx.randrange(20) != 0) { + new_index->pprev = block_index.back().get(); + } else { + new_index->pprev = block_index[ctx.randrange(block_index.size())].get(); + } + new_index->nHeight = new_index->pprev->nHeight + 1; + new_index->BuildSkip(); + block_index.push_back(std::move(new_index)); + } + // Run 10000 random GetAncestor queries. + for (int q = 0; q < 10000; ++q) { + const CBlockIndex* block = block_index[ctx.randrange(block_index.size())].get(); + unsigned height = ctx.randrange(block->nHeight + 1); + const CBlockIndex* result = block->GetAncestor(height); + BOOST_CHECK(result == NaiveGetAncestor(block, height)); + } + // Run 10000 random LastCommonAncestor queries. + for (int q = 0; q < 10000; ++q) { + const CBlockIndex* block1 = block_index[ctx.randrange(block_index.size())].get(); + const CBlockIndex* block2 = block_index[ctx.randrange(block_index.size())].get(); + const CBlockIndex* result = LastCommonAncestor(block1, block2); + BOOST_CHECK(result == NaiveLastCommonAncestor(block1, block2)); + } + } +} + +BOOST_AUTO_TEST_SUITE_END()