diff --git a/src/chainparams.h b/src/chainparams.h --- a/src/chainparams.h +++ b/src/chainparams.h @@ -97,6 +97,11 @@ bool fMineBlocksOnDemand; CCheckpointData checkpointData; ChainTxData chainTxData; + + // For unit tests + void InsertCheckpoint(int height, uint256 hash) { + checkpointData.mapCheckpoints.insert({height, hash}); + } }; /** diff --git a/src/config.h b/src/config.h --- a/src/config.h +++ b/src/config.h @@ -110,7 +110,7 @@ void SetRPCCORSDomain(std::string corsDomain) override{}; std::string GetRPCCORSDomain() const override { return ""; }; -private: +protected: std::unique_ptr chainParams; }; diff --git a/src/test/checkpoints_tests.cpp b/src/test/checkpoints_tests.cpp --- a/src/test/checkpoints_tests.cpp +++ b/src/test/checkpoints_tests.cpp @@ -10,12 +10,15 @@ #include "checkpoints.h" #include "chainparams.h" +#include "config.h" +#include "miner.h" #include "test/test_bitcoin.h" #include "uint256.h" +#include "validation.h" #include -BOOST_FIXTURE_TEST_SUITE(checkpoints_tests, BasicTestingSetup) +BOOST_FIXTURE_TEST_SUITE(checkpoints_tests, TestChain100Setup) BOOST_AUTO_TEST_CASE(sanity) { const auto params = CreateChainParams(CBaseChainParams::MAIN); @@ -36,4 +39,72 @@ BOOST_CHECK(Checkpoints::CheckBlock(checkpoints, 134444 + 1, p11111)); } +class TestChainParams : public CChainParams { +public: + TestChainParams() { + // Copy regnet consensus params + auto regnetChainParams = CreateChainParams(CBaseChainParams::REGTEST); + consensus = regnetChainParams->GetConsensus(); + + // Add a checkpoint at the current chaintip + InsertCheckpoint(chainActive.Height(), + chainActive.Tip()->GetBlockHash()); + } +}; + +class TestConfig : public DummyConfig { +public: + TestConfig() : DummyConfig() { + chainParams = std::unique_ptr(new TestChainParams()); + } + + uint64_t GetMaxBlockSize() const override { return DEFAULT_MAX_BLOCK_SIZE; } +}; + +BOOST_AUTO_TEST_CASE(ban_fork_prior_to_checkpoint) { + TestConfig config; + CBlockIndex *genesisBlock = + mapBlockIndex[config.GetChainParams().GetConsensus().hashGenesisBlock]; + assert(genesisBlock); + + CBlock block; + block.nVersion = ComputeBlockVersion( + genesisBlock, config.GetChainParams().GetConsensus()); + block.nBits = 1; + block.hashPrevBlock = genesisBlock->GetBlockHash(); + block.nTime = UpdateTime(&block, config, genesisBlock); + + int height = 1; + CMutableTransaction coinbase; + coinbase.vin.resize(1); + coinbase.vin[0].prevout = COutPoint(); + coinbase.vin[0].scriptSig = CScript() << height << OP_0; + coinbase.vout.resize(1); + coinbase.vout[0].scriptPubKey = CScript(); + coinbase.vout[0].nValue = + GetBlockSubsidy(height, config.GetChainParams().GetConsensus()); + + block.vtx.push_back(MakeTransactionRef(coinbase)); + block.vtx.resize(1); + + unsigned int extraNonce = 0; + { + LOCK(cs_main); + IncrementExtraNonce(config, &block, genesisBlock, extraNonce); + } + while (!CheckProofOfWork(block.GetHash(), block.nBits, config)) { + ++block.nNonce; + } + + // Attempting to mine a new block before a checkpoint should fail + // Specifically, the block should not be stored to prevent out-of-memory + // exploits on old chains + std::shared_ptr shared_pblock = + std::make_shared(block); + bool newBlock = false; + BOOST_CHECK(!ProcessNewBlock(config, shared_pblock, true, &newBlock)); + BOOST_CHECK(!newBlock); + BOOST_CHECK(mapBlockIndex.find(block.GetHash()) == mapBlockIndex.end()); +} + BOOST_AUTO_TEST_SUITE_END()