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()