diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -40,6 +40,7 @@ test/bswap_tests.cpp \ test/cashaddr_tests.cpp \ test/cashaddrenc_tests.cpp \ + test/checkpoints_tests.cpp \ test/coins_tests.cpp \ test/compress_tests.cpp \ test/config_tests.cpp \ diff --git a/src/checkpoints.h b/src/checkpoints.h --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -18,6 +18,9 @@ */ namespace Checkpoints { +//! Returns true if block passes checkpoint checks +bool CheckBlock(const CCheckpointData &data, int nHeight, const uint256 &hash); + //! Returns last CBlockIndex* in mapBlockIndex that is a checkpoint CBlockIndex *GetLastCheckpoint(const CCheckpointData &data); diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -13,6 +13,16 @@ namespace Checkpoints { +bool CheckBlock(const CCheckpointData &data, int nHeight, const uint256 &hash) { + const MapCheckpoints &checkpoints = data.mapCheckpoints; + + MapCheckpoints::const_iterator i = checkpoints.find(nHeight); + if (i == checkpoints.end()) { + return true; + } + return hash == i->second; +} + CBlockIndex *GetLastCheckpoint(const CCheckpointData &data) { const MapCheckpoints &checkpoints = data.mapCheckpoints; diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -647,8 +647,8 @@ strprintf("Run checks every transactions (default: %u)", defaultChainParams->DefaultConsistencyChecks())); strUsage += HelpMessageOpt( - "-checkpoints", strprintf("Disable expensive verification for " - "known chain history (default: %d)", + "-checkpoints", strprintf("Only accept block chain matching " + "built-in checkpoints (default: %d)", DEFAULT_CHECKPOINTS_ENABLED)); strUsage += HelpMessageOpt( "-disablesafemode", strprintf("Disable safemode, override a real " diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -66,6 +66,7 @@ bswap_tests.cpp cashaddr_tests.cpp cashaddrenc_tests.cpp + checkpoints_tests.cpp coins_tests.cpp compress_tests.cpp config_tests.cpp diff --git a/src/test/checkpoints_tests.cpp b/src/test/checkpoints_tests.cpp new file mode 100644 --- /dev/null +++ b/src/test/checkpoints_tests.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2018 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// +// Unit tests for block-chain checkpoints +// + +#include "checkpoints.h" + +#include "chainparams.h" +#include "test/test_bitcoin.h" +#include "uint256.h" + +#include + +BOOST_FIXTURE_TEST_SUITE(checkpoints_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(sanity) { + const auto params = CreateChainParams(CBaseChainParams::MAIN); + const CCheckpointData &checkpoints = params->Checkpoints(); + uint256 p11111 = uint256S( + "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"); + uint256 p134444 = uint256S( + "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe"); + BOOST_CHECK(Checkpoints::CheckBlock(checkpoints, 11111, p11111)); + BOOST_CHECK(Checkpoints::CheckBlock(checkpoints, 134444, p134444)); + + // Wrong hashes at checkpoints should fail: + BOOST_CHECK(!Checkpoints::CheckBlock(checkpoints, 11111, p134444)); + BOOST_CHECK(!Checkpoints::CheckBlock(checkpoints, 134444, p11111)); + + // ... but any hash not at a checkpoint should succeed: + BOOST_CHECK(Checkpoints::CheckBlock(checkpoints, 11111 + 1, p134444)); + BOOST_CHECK(Checkpoints::CheckBlock(checkpoints, 134444 + 1, p11111)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3543,11 +3543,20 @@ } int nHeight = pindexPrev->nHeight + 1; + const CCheckpointData &checkpoints = chainparams.Checkpoints(); + + // Check that the block chain matches the known block chain up to a + // checkpoint. + if (!Checkpoints::CheckBlock(checkpoints, nHeight, hash)) { + return state.DoS(100, error("%s: rejected by checkpoint lock-in at %d", + __func__, nHeight), + REJECT_CHECKPOINT, "checkpoint mismatch"); + } + // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in // our MapBlockIndex. - CBlockIndex *pcheckpoint = - Checkpoints::GetLastCheckpoint(chainparams.Checkpoints()); + CBlockIndex *pcheckpoint = Checkpoints::GetLastCheckpoint(checkpoints); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { return state.DoS( 100,