git / moonbite-core / src/test/validation_block_tests.cpp · raw
// Copyright (c) 2018-2020 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 <boost/test/unit_test.hpp>#include <chainparams.h>#include <consensus/merkle.h>#include <consensus/validation.h>#include <miner.h>#include <pow.h>#include <random.h>#include <script/standard.h>#include <test/util/setup_common.h>#include <util/time.h>#include <validation.h>#include <validationinterface.h>#include <thread>static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE};namespace validation_block_tests {struct MinerTestingSetup : public RegTestingSetup { std::shared_ptr<CBlock> Block(const uint256& prev_hash); std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash); std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash); std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock); void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks);};} // namespace validation_block_testsBOOST_FIXTURE_TEST_SUITE(validation_block_tests, MinerTestingSetup)struct TestSubscriber final : public CValidationInterface { uint256 m_expected_tip; explicit TestSubscriber(uint256 tip) : m_expected_tip(tip) {} void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override { BOOST_CHECK_EQUAL(m_expected_tip, pindexNew->GetBlockHash()); } void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override { BOOST_CHECK_EQUAL(m_expected_tip, block->hashPrevBlock); BOOST_CHECK_EQUAL(m_expected_tip, pindex->pprev->GetBlockHash()); m_expected_tip = block->GetHash(); } void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override { BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash()); BOOST_CHECK_EQUAL(m_expected_tip, pindex->GetBlockHash()); m_expected_tip = block->hashPrevBlock; }};std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash){ static int i = 0; static uint64_t time = Params().GenesisBlock().nTime; CScript pubKey; pubKey << i++ << OP_TRUE; auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(pubKey); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; pubKey.clear(); { WitnessV0ScriptHash witness_program; CSHA256().Write(&V_OP_TRUE[0], V_OP_TRUE.size()).Finalize(witness_program.begin()); pubKey << OP_0 << ToByteVector(witness_program); } // Make the coinbase transaction with two outputs: // One zero-value one that has a unique pubkey to make sure that blocks at the same height can have a different hash // Another one that has the coinbase reward in a P2WSH with OP_TRUE as witness program to make it easy to spend CMutableTransaction txCoinbase(*pblock->vtx[0]); // MoonBite (ADR-005): CreateNewBlock adds a consensus treasury output; preserve // it, otherwise ConnectBlock rejects the block with bad-cb-treasury. const CScript treasuryScript(Params().GetConsensus().treasuryScript.begin(), Params().GetConsensus().treasuryScript.end()); std::vector<CTxOut> treasuryOuts; if (!treasuryScript.empty()) { for (const CTxOut& o : txCoinbase.vout) { if (o.scriptPubKey == treasuryScript) treasuryOuts.push_back(o); } } const CAmount minerValue = txCoinbase.vout[0].nValue; txCoinbase.vout.resize(2); txCoinbase.vout[1].scriptPubKey = pubKey; txCoinbase.vout[1].nValue = minerValue; txCoinbase.vout[0].nValue = 0; for (const CTxOut& o : treasuryOuts) txCoinbase.vout.push_back(o); txCoinbase.vin[0].scriptWitness.SetNull(); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); return pblock;}std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock){ LOCK(cs_main); // For LookupBlockIndex GenerateCoinbaseCommitment(*pblock, LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus()); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); while (!CheckProofOfWork(pblock->GetPoWHash(), pblock->nBits, Params().GetConsensus())) { ++(pblock->nNonce); } return pblock;}// construct a valid blockstd::shared_ptr<const CBlock> MinerTestingSetup::GoodBlock(const uint256& prev_hash){ return FinalizeBlock(Block(prev_hash));}// construct an invalid block (but with a valid header)std::shared_ptr<const CBlock> MinerTestingSetup::BadBlock(const uint256& prev_hash){ auto pblock = Block(prev_hash); CMutableTransaction coinbase_spend; coinbase_spend.vin.push_back(CTxIn(COutPoint(pblock->vtx[0]->GetHash(), 0), CScript(), 0)); coinbase_spend.vout.push_back(pblock->vtx[0]->vout[0]); CTransactionRef tx = MakeTransactionRef(coinbase_spend); pblock->vtx.push_back(tx); auto ret = FinalizeBlock(pblock); return ret;}void MinerTestingSetup::BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks){ if (height <= 0 || blocks.size() >= max_size) return; bool gen_invalid = InsecureRandRange(100) < invalid_rate; bool gen_fork = InsecureRandRange(100) < branch_rate; const std::shared_ptr<const CBlock> pblock = gen_invalid ? BadBlock(root) : GoodBlock(root); blocks.push_back(pblock); if (!gen_invalid) { BuildChain(pblock->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks); } if (gen_fork) { blocks.push_back(GoodBlock(root)); BuildChain(blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks); }}BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering){ // build a large-ish chain that's likely to have some forks std::vector<std::shared_ptr<const CBlock>> blocks; while (blocks.size() < 50) { blocks.clear(); BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks); } bool ignored; BlockValidationState state; std::vector<CBlockHeader> headers; std::transform(blocks.begin(), blocks.end(), std::back_inserter(headers), [](std::shared_ptr<const CBlock> b) { return b->GetBlockHeader(); }); // Process all the headers so we understand the toplogy of the chain BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders(headers, state, Params())); // Connect the genesis block and drain any outstanding events BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored)); SyncWithValidationInterfaceQueue(); // subscribe to events (this subscriber will validate event ordering) const CBlockIndex* initial_tip = nullptr; { LOCK(cs_main); initial_tip = ::ChainActive().Tip(); } auto sub = std::make_shared<TestSubscriber>(initial_tip->GetBlockHash()); RegisterSharedValidationInterface(sub); // create a bunch of threads that repeatedly process a block generated above at random // this will create parallelism and randomness inside validation - the ValidationInterface // will subscribe to events generated during block validation and assert on ordering invariance std::vector<std::thread> threads; for (int i = 0; i < 10; i++) { threads.emplace_back([&]() { bool ignored; FastRandomContext insecure; for (int i = 0; i < 1000; i++) { auto block = blocks[insecure.randrange(blocks.size() - 1)]; Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored); } // to make sure that eventually we process the full chain - do it here for (auto block : blocks) { if (block->vtx.size() == 1) { bool processed = Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored); assert(processed); } } }); } for (auto& t : threads) { t.join(); } SyncWithValidationInterfaceQueue(); UnregisterSharedValidationInterface(sub); LOCK(cs_main); BOOST_CHECK_EQUAL(sub->m_expected_tip, ::ChainActive().Tip()->GetBlockHash());}/** * Test that mempool updates happen atomically with reorgs. * * This prevents RPC clients, among others, from retrieving immediately-out-of-date mempool data * during large reorgs. * * The test verifies this by creating a chain of `num_txs` blocks, matures their coinbases, and then * submits txns spending from their coinbase to the mempool. A fork chain is then processed, * invalidating the txns and evicting them from the mempool. * * We verify that the mempool updates atomically by polling it continuously * from another thread during the reorg and checking that its size only changes * once. The size changing exactly once indicates that the polling thread's * view of the mempool is either consistent with the chain state before reorg, * or consistent with the chain state after the reorg, and not just consistent * with some intermediate state during the reorg. */BOOST_AUTO_TEST_CASE(mempool_locks_reorg){ bool ignored; auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool { return Assert(m_node.chainman)->ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored); }; // Process all mined blocks BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock()))); auto last_mined = GoodBlock(Params().GenesisBlock().GetHash()); BOOST_REQUIRE(ProcessBlock(last_mined)); // Run the test multiple times for (int test_runs = 3; test_runs > 0; --test_runs) { BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); // Later on split from here const uint256 split_hash{last_mined->hashPrevBlock}; // Create a bunch of transactions to spend the miner rewards of the // most recent blocks std::vector<CTransactionRef> txs; for (int num_txs = 22; num_txs > 0; --num_txs) { CMutableTransaction mtx; mtx.vin.push_back(CTxIn{COutPoint{last_mined->vtx[0]->GetHash(), 1}, CScript{}}); mtx.vin[0].scriptWitness.stack.push_back(V_OP_TRUE); mtx.vout.push_back(last_mined->vtx[0]->vout[1]); mtx.vout[0].nValue -= 1000; txs.push_back(MakeTransactionRef(mtx)); last_mined = GoodBlock(last_mined->GetHash()); BOOST_REQUIRE(ProcessBlock(last_mined)); } // Mature the inputs of the txs for (int j = COINBASE_MATURITY; j > 0; --j) { last_mined = GoodBlock(last_mined->GetHash()); BOOST_REQUIRE(ProcessBlock(last_mined)); } // Mine a reorg (and hold it back) before adding the txs to the mempool const uint256 tip_init{last_mined->GetHash()}; std::vector<std::shared_ptr<const CBlock>> reorg; last_mined = GoodBlock(split_hash); reorg.push_back(last_mined); for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) { last_mined = GoodBlock(last_mined->GetHash()); reorg.push_back(last_mined); } // Add the txs to the tx pool { LOCK(cs_main); TxValidationState state; std::list<CTransactionRef> plTxnReplaced; for (const auto& tx : txs) { BOOST_REQUIRE(AcceptToMemoryPool( *m_node.mempool, state, tx, &plTxnReplaced, /* bypass_limits */ false)); } } // Check that all txs are in the pool { LOCK(m_node.mempool->cs); BOOST_CHECK_EQUAL(m_node.mempool->mapTx.size(), txs.size()); } // Run a thread that simulates an RPC caller that is polling while // validation is doing a reorg std::thread rpc_thread{[&]() { // This thread is checking that the mempool either contains all of // the transactions invalidated by the reorg, or none of them, and // not some intermediate amount. while (true) { LOCK(m_node.mempool->cs); if (m_node.mempool->mapTx.size() == 0) { // We are done with the reorg break; } // Internally, we might be in the middle of the reorg, but // externally the reorg to the most-proof-of-work chain should // be atomic. So the caller assumes that the returned mempool // is consistent. That is, it has all txs that were there // before the reorg. assert(m_node.mempool->mapTx.size() == txs.size()); continue; } LOCK(cs_main); // We are done with the reorg, so the tip must have changed assert(tip_init != ::ChainActive().Tip()->GetBlockHash()); }}; // Submit the reorg in this thread to invalidate and remove the txs from the tx pool for (const auto& b : reorg) { ProcessBlock(b); } // Check that the reorg was eventually successful BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); // We can join the other thread, which returns when the reorg was successful rpc_thread.join(); }}BOOST_AUTO_TEST_CASE(witness_commitment_index){ CScript pubKey; pubKey << 1 << OP_TRUE; auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(pubKey); CBlock pblock = ptemplate->block; CTxOut witness; witness.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT); witness.scriptPubKey[0] = OP_RETURN; witness.scriptPubKey[1] = 0x24; witness.scriptPubKey[2] = 0xaa; witness.scriptPubKey[3] = 0x21; witness.scriptPubKey[4] = 0xa9; witness.scriptPubKey[5] = 0xed; // A witness larger than the minimum size is still valid CTxOut min_plus_one = witness; min_plus_one.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT + 1); CTxOut invalid = witness; invalid.scriptPubKey[0] = OP_VERIFY; CMutableTransaction txCoinbase(*pblock.vtx[0]); txCoinbase.vout.resize(4); txCoinbase.vout[0] = witness; txCoinbase.vout[1] = witness; txCoinbase.vout[2] = min_plus_one; txCoinbase.vout[3] = invalid; pblock.vtx[0] = MakeTransactionRef(std::move(txCoinbase)); BOOST_CHECK_EQUAL(GetWitnessCommitmentIndex(pblock), 2);}BOOST_AUTO_TEST_SUITE_END()