diff --git a/src/miner.cpp b/src/miner.cpp index 655d0c704..11cb03287 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1,577 +1,580 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 <miner.h> #include <amount.h> #include <chain.h> #include <chainparams.h> #include <coins.h> #include <config.h> #include <consensus/activation.h> #include <consensus/consensus.h> #include <consensus/merkle.h> #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <minerfund.h> #include <net.h> #include <policy/policy.h> #include <policy/settings.h> #include <pow/pow.h> #include <primitives/transaction.h> #include <timedata.h> #include <util/moneystr.h> #include <util/system.h> #include <validation.h> #include <algorithm> #include <utility> int64_t UpdateTime(CBlockHeader *pblock, const CChainParams &chainParams, const CBlockIndex *pindexPrev) { int64_t nOldTime = pblock->nTime; int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast() + 1, GetAdjustedTime()); if (nOldTime < nNewTime) { pblock->nTime = nNewTime; } // Updating time can change work required on testnet: if (chainParams.GetConsensus().fPowAllowMinDifficultyBlocks) { pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainParams); } return nNewTime - nOldTime; } uint64_t CTxMemPoolModifiedEntry::GetVirtualSizeWithAncestors() const { return GetVirtualTransactionSize(nSizeWithAncestors, nSigOpCountWithAncestors); } BlockAssembler::Options::Options() : nExcessiveBlockSize(DEFAULT_MAX_BLOCK_SIZE), nMaxGeneratedBlockSize(DEFAULT_MAX_GENERATED_BLOCK_SIZE), blockMinFeeRate(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB) {} BlockAssembler::BlockAssembler(const CChainParams ¶ms, const CTxMemPool &mempool, const Options &options) : chainParams(params), m_mempool(mempool) { blockMinFeeRate = options.blockMinFeeRate; // Limit size to between 1K and options.nExcessiveBlockSize -1K for sanity: nMaxGeneratedBlockSize = std::max<uint64_t>( 1000, std::min<uint64_t>(options.nExcessiveBlockSize - 1000, options.nMaxGeneratedBlockSize)); // Calculate the max consensus sigchecks for this block. auto nMaxBlockSigChecks = GetMaxBlockSigChecksCount(nMaxGeneratedBlockSize); // Allow the full amount of signature check operations in lieu of a separate // config option. (We are mining relayed transactions with validity cached // by everyone else, and so the block will propagate quickly, regardless of // how many sigchecks it contains.) nMaxGeneratedBlockSigChecks = nMaxBlockSigChecks; } static BlockAssembler::Options DefaultOptions(const Config &config) { // Block resource limits // If -blockmaxsize is not given, limit to DEFAULT_MAX_GENERATED_BLOCK_SIZE // If only one is given, only restrict the specified resource. // If both are given, restrict both. BlockAssembler::Options options; options.nExcessiveBlockSize = config.GetMaxBlockSize(); if (gArgs.IsArgSet("-blockmaxsize")) { options.nMaxGeneratedBlockSize = gArgs.GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE); } Amount n = Amount::zero(); if (gArgs.IsArgSet("-blockmintxfee") && ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) { options.blockMinFeeRate = CFeeRate(n); } return options; } BlockAssembler::BlockAssembler(const Config &config, const CTxMemPool &mempool) : BlockAssembler(config.GetChainParams(), mempool, DefaultOptions(config)) { } void BlockAssembler::resetBlock() { inBlock.clear(); // Reserve space for coinbase tx. nBlockSize = 1000; nBlockSigOps = 100; // These counters do not include coinbase tx. nBlockTx = 0; nFees = Amount::zero(); } std::optional<int64_t> BlockAssembler::m_last_block_num_txs{std::nullopt}; std::optional<int64_t> BlockAssembler::m_last_block_size{std::nullopt}; std::unique_ptr<CBlockTemplate> -BlockAssembler::CreateNewBlock(const CScript &scriptPubKeyIn) { +BlockAssembler::CreateNewBlock(CChainState &chainstate, + const CScript &scriptPubKeyIn) { int64_t nTimeStart = GetTimeMicros(); resetBlock(); pblocktemplate.reset(new CBlockTemplate()); if (!pblocktemplate.get()) { return nullptr; } // Pointer for convenience. CBlock *const pblock = &pblocktemplate->block; // Add dummy coinbase tx as first transaction. It is updated at the end. pblocktemplate->entries.emplace_back(CTransactionRef(), -SATOSHI, -1); LOCK2(cs_main, m_mempool.cs); - CBlockIndex *pindexPrev = ::ChainActive().Tip(); + assert(std::addressof(*::ChainActive().Tip()) == + std::addressof(*chainstate.m_chain.Tip())); + CBlockIndex *pindexPrev = chainstate.m_chain.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; const Consensus::Params &consensusParams = chainParams.GetConsensus(); pblock->nVersion = ComputeBlockVersion(pindexPrev, consensusParams); // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainParams.MineBlocksOnDemand()) { pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion); } pblock->nTime = GetAdjustedTime(); nMedianTimePast = pindexPrev->GetMedianTimePast(); nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST) ? nMedianTimePast : pblock->GetBlockTime(); int nPackagesSelected = 0; int nDescendantsUpdated = 0; addPackageTxs(nPackagesSelected, nDescendantsUpdated); if (IsMagneticAnomalyEnabled(consensusParams, pindexPrev)) { // If magnetic anomaly is enabled, we make sure transaction are // canonically ordered. std::sort(std::begin(pblocktemplate->entries) + 1, std::end(pblocktemplate->entries), [](const CBlockTemplateEntry &a, const CBlockTemplateEntry &b) -> bool { return a.tx->GetId() < b.tx->GetId(); }); } // Copy all the transactions refs into the block pblock->vtx.reserve(pblocktemplate->entries.size()); for (const CBlockTemplateEntry &entry : pblocktemplate->entries) { pblock->vtx.push_back(entry.tx); } int64_t nTime1 = GetTimeMicros(); m_last_block_num_txs = nBlockTx; m_last_block_size = nBlockSize; // Create coinbase transaction. CMutableTransaction coinbaseTx; coinbaseTx.vin.resize(1); coinbaseTx.vin[0].prevout = COutPoint(); coinbaseTx.vout.resize(1); coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn; coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, consensusParams); coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; const std::vector<CTxDestination> whitelisted = GetMinerFundWhitelist(consensusParams, pindexPrev); if (!whitelisted.empty()) { const Amount fund = GetMinerFundAmount(coinbaseTx.vout[0].nValue); coinbaseTx.vout[0].nValue -= fund; coinbaseTx.vout.emplace_back(fund, GetScriptForDestination(whitelisted[0])); } // Make sure the coinbase is big enough. uint64_t coinbaseSize = ::GetSerializeSize(coinbaseTx, PROTOCOL_VERSION); if (coinbaseSize < MIN_TX_SIZE) { coinbaseTx.vin[0].scriptSig << std::vector<uint8_t>(MIN_TX_SIZE - coinbaseSize - 1); } pblocktemplate->entries[0].tx = MakeTransactionRef(coinbaseTx); pblocktemplate->entries[0].fees = -1 * nFees; pblock->vtx[0] = pblocktemplate->entries[0].tx; uint64_t nSerializeSize = GetSerializeSize(*pblock, PROTOCOL_VERSION); LogPrintf("CreateNewBlock(): total size: %u txs: %u fees: %ld sigops %d\n", nSerializeSize, nBlockTx, nFees, nBlockSigOps); // Fill in header. pblock->hashPrevBlock = pindexPrev->GetBlockHash(); UpdateTime(pblock, chainParams, pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainParams); pblock->nNonce = 0; pblocktemplate->entries[0].sigOpCount = 0; BlockValidationState state; - if (!TestBlockValidity(state, chainParams, ::ChainstateActive(), *pblock, - pindexPrev, + assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); + if (!TestBlockValidity(state, chainParams, chainstate, *pblock, pindexPrev, BlockValidationOptions(nMaxGeneratedBlockSize) .withCheckPoW(false) .withCheckMerkleRoot(false))) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } int64_t nTime2 = GetTimeMicros(); LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated " "descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart)); return std::move(pblocktemplate); } void BlockAssembler::onlyUnconfirmed(CTxMemPool::setEntries &testSet) { for (CTxMemPool::setEntries::iterator iit = testSet.begin(); iit != testSet.end();) { // Only test txs not already in the block. if (inBlock.count(*iit)) { testSet.erase(iit++); } else { iit++; } } } bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOps) const { auto blockSizeWithPackage = nBlockSize + packageSize; if (blockSizeWithPackage >= nMaxGeneratedBlockSize) { return false; } if (nBlockSigOps + packageSigOps >= nMaxGeneratedBlockSigChecks) { return false; } return true; } /** * Perform transaction-level checks before adding to block: * - Transaction finality (locktime) * - Serialized size (in case -blockmaxsize is in use) */ bool BlockAssembler::TestPackageTransactions( const CTxMemPool::setEntries &package) const { uint64_t nPotentialBlockSize = nBlockSize; for (CTxMemPool::txiter it : package) { TxValidationState state; if (!ContextualCheckTransaction(chainParams.GetConsensus(), it->GetTx(), state, nHeight, nLockTimeCutoff, nMedianTimePast)) { return false; } uint64_t nTxSize = ::GetSerializeSize(it->GetTx(), PROTOCOL_VERSION); if (nPotentialBlockSize + nTxSize >= nMaxGeneratedBlockSize) { return false; } nPotentialBlockSize += nTxSize; } return true; } void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) { pblocktemplate->entries.emplace_back(iter->GetSharedTx(), iter->GetFee(), iter->GetSigOpCount()); nBlockSize += iter->GetTxSize(); ++nBlockTx; nBlockSigOps += iter->GetSigOpCount(); nFees += iter->GetFee(); inBlock.insert(iter); bool fPrintPriority = gArgs.GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY); if (fPrintPriority) { LogPrintf( "fee %s txid %s\n", CFeeRate(iter->GetModifiedFee(), iter->GetTxSize()).ToString(), iter->GetTx().GetId().ToString()); } } int BlockAssembler::UpdatePackagesForAdded( const CTxMemPool::setEntries &alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) { int nDescendantsUpdated = 0; for (CTxMemPool::txiter it : alreadyAdded) { CTxMemPool::setEntries descendants; m_mempool.CalculateDescendants(it, descendants); // Insert all descendants (not yet in block) into the modified set. for (CTxMemPool::txiter desc : descendants) { if (alreadyAdded.count(desc)) { continue; } ++nDescendantsUpdated; modtxiter mit = mapModifiedTx.find(desc); if (mit == mapModifiedTx.end()) { CTxMemPoolModifiedEntry modEntry(desc); modEntry.nSizeWithAncestors -= it->GetTxSize(); modEntry.nModFeesWithAncestors -= it->GetModifiedFee(); modEntry.nSigOpCountWithAncestors -= it->GetSigOpCount(); mapModifiedTx.insert(modEntry); } else { mapModifiedTx.modify(mit, update_for_parent_inclusion(it)); } } } return nDescendantsUpdated; } // Skip entries in mapTx that are already in a block or are present in // mapModifiedTx (which implies that the mapTx ancestor state is stale due to // ancestor inclusion in the block). Also skip transactions that we've already // failed to add. This can happen if we consider a transaction in mapModifiedTx // and it fails: we can then potentially consider it again while walking mapTx. // It's currently guaranteed to fail again, but as a belt-and-suspenders check // we put it in failedTx and avoid re-evaluation, since the re-evaluation would // be using cached size/sigops/fee values that are not actually correct. bool BlockAssembler::SkipMapTxEntry( CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx) { assert(it != m_mempool.mapTx.end()); return mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it); } void BlockAssembler::SortForBlock( const CTxMemPool::setEntries &package, std::vector<CTxMemPool::txiter> &sortedEntries) { // Sort package by ancestor count. If a transaction A depends on transaction // B, then A's ancestor count must be greater than B's. So this is // sufficient to validly order the transactions for block inclusion. sortedEntries.clear(); sortedEntries.insert(sortedEntries.begin(), package.begin(), package.end()); std::sort(sortedEntries.begin(), sortedEntries.end(), CompareTxIterByAncestorCount()); } /** * addPackageTx includes transactions paying a fee by ensuring that * the partial ordering of transactions is maintained. That is to say * children come after parents, despite having a potentially larger fee. * @param[out] nPackagesSelected How many packages were selected * @param[out] nDescendantsUpdated Number of descendant transactions updated */ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) { // selection algorithm orders the mempool based on feerate of a // transaction including all unconfirmed ancestors. Since we don't remove // transactions from the mempool as we select them for block inclusion, we // need an alternate method of updating the feerate of a transaction with // its not-yet-selected ancestors as we go. This is accomplished by // walking the in-mempool descendants of selected transactions and storing // a temporary modified state in mapModifiedTxs. Each time through the // loop, we compare the best transaction in mapModifiedTxs with the next // transaction in the mempool to decide what transaction package to work // on next. // mapModifiedTx will store sorted packages after they are modified because // some of their txs are already in the block. indexed_modified_transaction_set mapModifiedTx; // Keep track of entries that failed inclusion, to avoid duplicate work. CTxMemPool::setEntries failedTx; // Start by adding all descendants of previously added txs to mapModifiedTx // and modifying them for their already included ancestors. UpdatePackagesForAdded(inBlock, mapModifiedTx); CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = m_mempool.mapTx.get<ancestor_score>().begin(); CTxMemPool::txiter iter; // Limit the number of attempts to add transactions to the block when it is // close to full; this is just a simple heuristic to finish quickly if the // mempool has a lot of entries. const int64_t MAX_CONSECUTIVE_FAILURES = 1000; int64_t nConsecutiveFailed = 0; while (mi != m_mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) { // First try to find a new transaction in mapTx to evaluate. if (mi != m_mempool.mapTx.get<ancestor_score>().end() && SkipMapTxEntry(m_mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) { ++mi; continue; } // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin(); if (mi == m_mempool.mapTx.get<ancestor_score>().end()) { // We're out of entries in mapTx; use the entry from mapModifiedTx iter = modit->iter; fUsingModified = true; } else { // Try to compare the mapTx entry to the mapModifiedTx entry. iter = m_mempool.mapTx.project<0>(mi); if (modit != mapModifiedTx.get<ancestor_score>().end() && CompareTxMemPoolEntryByAncestorFee()( *modit, CTxMemPoolModifiedEntry(iter))) { // The best entry in mapModifiedTx has higher score than the one // from mapTx. Switch which transaction (package) to consider iter = modit->iter; fUsingModified = true; } else { // Either no entry in mapModifiedTx, or it's worse than mapTx. // Increment mi for the next loop iteration. ++mi; } } // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't // contain anything that is inBlock. assert(!inBlock.count(iter)); uint64_t packageSize = iter->GetSizeWithAncestors(); Amount packageFees = iter->GetModFeesWithAncestors(); int64_t packageSigOps = iter->GetSigOpCountWithAncestors(); if (fUsingModified) { packageSize = modit->nSizeWithAncestors; packageFees = modit->nModFeesWithAncestors; packageSigOps = modit->nSigOpCountWithAncestors; } if (packageFees < blockMinFeeRate.GetFee(packageSize)) { // Don't include this package, but don't stop yet because something // else we might consider may have a sufficient fee rate (since txes // are ordered by virtualsize feerate, not actual feerate). if (fUsingModified) { // Since we always look at the best entry in mapModifiedTx, we // must erase failed entries so that we can consider the next // best entry on the next loop iteration mapModifiedTx.get<ancestor_score>().erase(modit); failedTx.insert(iter); } continue; } // The following must not use virtual size since TestPackage relies on // having an accurate call to // GetMaxBlockSigOpsCount(blockSizeWithPackage). if (!TestPackage(packageSize, packageSigOps)) { if (fUsingModified) { // Since we always look at the best entry in mapModifiedTx, we // must erase failed entries so that we can consider the next // best entry on the next loop iteration mapModifiedTx.get<ancestor_score>().erase(modit); failedTx.insert(iter); } ++nConsecutiveFailed; if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockSize > nMaxGeneratedBlockSize - 1000) { // Give up if we're close to full and haven't succeeded in a // while. break; } continue; } CTxMemPool::setEntries ancestors; uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; m_mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); onlyUnconfirmed(ancestors); ancestors.insert(iter); // Test if all tx's are Final. if (!TestPackageTransactions(ancestors)) { if (fUsingModified) { mapModifiedTx.get<ancestor_score>().erase(modit); failedTx.insert(iter); } continue; } // This transaction will make it in; reset the failed counter. nConsecutiveFailed = 0; // Package can be added. Sort the entries in a valid order. std::vector<CTxMemPool::txiter> sortedEntries; SortForBlock(ancestors, sortedEntries); for (auto &entry : sortedEntries) { AddToBlock(entry); // Erase from the modified set, if present mapModifiedTx.erase(entry); } ++nPackagesSelected; // Update transactions that depend on each of these nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx); } } static const std::vector<uint8_t> getExcessiveBlockSizeSig(uint64_t nExcessiveBlockSize) { std::string cbmsg = "/EB" + getSubVersionEB(nExcessiveBlockSize) + "/"; std::vector<uint8_t> vec(cbmsg.begin(), cbmsg.end()); return vec; } void IncrementExtraNonce(CBlock *pblock, const CBlockIndex *pindexPrev, uint64_t nExcessiveBlockSize, unsigned int &nExtraNonce) { // Update nExtraNonce static uint256 hashPrevBlock; if (hashPrevBlock != pblock->hashPrevBlock) { nExtraNonce = 0; hashPrevBlock = pblock->hashPrevBlock; } ++nExtraNonce; // Height first in coinbase required for block.version=2 unsigned int nHeight = pindexPrev->nHeight + 1; CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce) << getExcessiveBlockSizeSig(nExcessiveBlockSize)); // Make sure the coinbase is big enough. uint64_t coinbaseSize = ::GetSerializeSize(txCoinbase, PROTOCOL_VERSION); if (coinbaseSize < MIN_TX_SIZE) { txCoinbase.vin[0].scriptSig << std::vector<uint8_t>(MIN_TX_SIZE - coinbaseSize - 1); } assert(txCoinbase.vin[0].scriptSig.size() <= MAX_COINBASE_SCRIPTSIG_SIZE); assert(::GetSerializeSize(txCoinbase, PROTOCOL_VERSION) >= MIN_TX_SIZE); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); } diff --git a/src/miner.h b/src/miner.h index 01b23ba83..df20677f3 100644 --- a/src/miner.h +++ b/src/miner.h @@ -1,234 +1,234 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_MINER_H #define BITCOIN_MINER_H #include <primitives/block.h> #include <txmempool.h> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index_container.hpp> #include <cstdint> #include <memory> #include <optional> class CBlockIndex; class CChainParams; class Config; class CScript; namespace Consensus { struct Params; } static const bool DEFAULT_PRINTPRIORITY = false; struct CBlockTemplateEntry { CTransactionRef tx; Amount fees; int64_t sigOpCount; CBlockTemplateEntry(CTransactionRef _tx, Amount _fees, int64_t _sigOpCount) : tx(_tx), fees(_fees), sigOpCount(_sigOpCount){}; }; struct CBlockTemplate { CBlock block; std::vector<CBlockTemplateEntry> entries; }; // Container for tracking updates to ancestor feerate as we include (parent) // transactions in a block struct CTxMemPoolModifiedEntry { explicit CTxMemPoolModifiedEntry(CTxMemPool::txiter entry) { iter = entry; nSizeWithAncestors = entry->GetSizeWithAncestors(); nModFeesWithAncestors = entry->GetModFeesWithAncestors(); nSigOpCountWithAncestors = entry->GetSigOpCountWithAncestors(); } Amount GetModifiedFee() const { return iter->GetModifiedFee(); } uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } uint64_t GetVirtualSizeWithAncestors() const; Amount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } size_t GetTxSize() const { return iter->GetTxSize(); } size_t GetTxVirtualSize() const { return iter->GetTxVirtualSize(); } const CTransaction &GetTx() const { return iter->GetTx(); } CTxMemPool::txiter iter; uint64_t nSizeWithAncestors; Amount nModFeesWithAncestors; int64_t nSigOpCountWithAncestors; }; /** * Comparator for CTxMemPool::txiter objects. * It simply compares the internal memory address of the CTxMemPoolEntry object * pointed to. This means it has no meaning, and is only useful for using them * as key in other indexes. */ struct CompareCTxMemPoolIter { bool operator()(const CTxMemPool::txiter &a, const CTxMemPool::txiter &b) const { return &(*a) < &(*b); } }; struct modifiedentry_iter { typedef CTxMemPool::txiter result_type; result_type operator()(const CTxMemPoolModifiedEntry &entry) const { return entry.iter; } }; // A comparator that sorts transactions based on number of ancestors. // This is sufficient to sort an ancestor package in an order that is valid // to appear in a block. struct CompareTxIterByAncestorCount { bool operator()(const CTxMemPool::txiter &a, const CTxMemPool::txiter &b) const { if (a->GetCountWithAncestors() != b->GetCountWithAncestors()) { return a->GetCountWithAncestors() < b->GetCountWithAncestors(); } return CompareIteratorById()(a, b); } }; typedef boost::multi_index_container< CTxMemPoolModifiedEntry, boost::multi_index::indexed_by< boost::multi_index::ordered_unique<modifiedentry_iter, CompareCTxMemPoolIter>, // sorted by modified ancestor fee rate boost::multi_index::ordered_non_unique< // Reuse same tag from CTxMemPool's similar index boost::multi_index::tag<ancestor_score>, boost::multi_index::identity<CTxMemPoolModifiedEntry>, CompareTxMemPoolEntryByAncestorFee>>> indexed_modified_transaction_set; typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter; typedef indexed_modified_transaction_set::index<ancestor_score>::type::iterator modtxscoreiter; struct update_for_parent_inclusion { explicit update_for_parent_inclusion(CTxMemPool::txiter it) : iter(it) {} void operator()(CTxMemPoolModifiedEntry &e) { e.nModFeesWithAncestors -= iter->GetFee(); e.nSizeWithAncestors -= iter->GetTxSize(); e.nSigOpCountWithAncestors -= iter->GetSigOpCount(); } CTxMemPool::txiter iter; }; /** Generate a new block, without valid proof-of-work */ class BlockAssembler { private: // The constructed block template std::unique_ptr<CBlockTemplate> pblocktemplate; // Configuration parameters for the block size uint64_t nMaxGeneratedBlockSize; uint64_t nMaxGeneratedBlockSigChecks; CFeeRate blockMinFeeRate; // Information on the current status of the block uint64_t nBlockSize; uint64_t nBlockTx; uint64_t nBlockSigOps; Amount nFees; CTxMemPool::setEntries inBlock; // Chain context for the block int nHeight; int64_t nLockTimeCutoff; int64_t nMedianTimePast; const CChainParams &chainParams; const CTxMemPool &m_mempool; public: struct Options { Options(); uint64_t nExcessiveBlockSize; uint64_t nMaxGeneratedBlockSize; CFeeRate blockMinFeeRate; }; BlockAssembler(const Config &config, const CTxMemPool &mempool); BlockAssembler(const CChainParams ¶ms, const CTxMemPool &mempool, const Options &options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> - CreateNewBlock(const CScript &scriptPubKeyIn); + CreateNewBlock(CChainState &chainstate, const CScript &scriptPubKeyIn); uint64_t GetMaxGeneratedBlockSize() const { return nMaxGeneratedBlockSize; } static std::optional<int64_t> m_last_block_num_txs; static std::optional<int64_t> m_last_block_size; private: // utility functions /** Clear the block's state and prepare for assembling a new block */ void resetBlock(); /** Add a tx to the block */ void AddToBlock(CTxMemPool::txiter iter); // Methods for how to add transactions to a block. /** * Add transactions based on feerate including unconfirmed ancestors. * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ void onlyUnconfirmed(CTxMemPool::setEntries &testSet); /** Test if a new package would "fit" in the block */ bool TestPackage(uint64_t packageSize, int64_t packageSigOpCount) const; /** * Perform checks on each transaction in a package: * locktime, serialized size (if necessary). These checks should always * succeed, and they're here only as an extra check in case of suboptimal * node configuration. */ bool TestPackageTransactions(const CTxMemPool::setEntries &package) const; /** * Return true if given transaction from mapTx has already been evaluated, * or if the transaction's cached data in mapTx is incorrect. */ bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); /** Sort the package in an order that is valid to appear in a block */ void SortForBlock(const CTxMemPool::setEntries &package, std::vector<CTxMemPool::txiter> &sortedEntries); /** * Add descendants of given transactions to mapModifiedTx with ancestor * state updated assuming given transactions are inBlock. Returns number of * updated descendants. */ int UpdatePackagesForAdded(const CTxMemPool::setEntries &alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); }; /** Modify the extranonce in a block */ void IncrementExtraNonce(CBlock *pblock, const CBlockIndex *pindexPrev, uint64_t nExcessiveBlockSize, unsigned int &nExtraNonce); int64_t UpdateTime(CBlockHeader *pblock, const CChainParams &chainParams, const CBlockIndex *pindexPrev); #endif // BITCOIN_MINER_H diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 9c0d3189c..79e145ea3 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,1218 +1,1220 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2018 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 <amount.h> #include <blockvalidity.h> #include <cashaddrenc.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <consensus/activation.h> #include <consensus/consensus.h> #include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> #include <key_io.h> #include <miner.h> #include <minerfund.h> #include <net.h> #include <node/context.h> #include <policy/policy.h> #include <pow/pow.h> #include <rpc/blockchain.h> #include <rpc/mining.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <script/script.h> #include <shutdown.h> #include <txmempool.h> #include <univalue.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> #include <warnings.h> #include <cstdint> /** * Return average network hashes per second based on the last 'lookup' blocks, * or from the last difficulty change if 'lookup' is nonpositive. If 'height' is * nonnegative, compute the estimate at the time when a given block was found. */ static UniValue GetNetworkHashPS(int lookup, int height) { CBlockIndex *pb = ::ChainActive().Tip(); if (height >= 0 && height < ::ChainActive().Height()) { pb = ::ChainActive()[height]; } if (pb == nullptr || !pb->nHeight) { return 0; } // If lookup is -1, then use blocks since last difficulty change. if (lookup <= 0) { lookup = pb->nHeight % Params().GetConsensus().DifficultyAdjustmentInterval() + 1; } // If lookup is larger than chain, then set it to chain length. if (lookup > pb->nHeight) { lookup = pb->nHeight; } CBlockIndex *pb0 = pb; int64_t minTime = pb0->GetBlockTime(); int64_t maxTime = minTime; for (int i = 0; i < lookup; i++) { pb0 = pb0->pprev; int64_t time = pb0->GetBlockTime(); minTime = std::min(time, minTime); maxTime = std::max(time, maxTime); } // In case there's a situation where minTime == maxTime, we don't want a // divide by zero exception. if (minTime == maxTime) { return 0; } arith_uint256 workDiff = pb->nChainWork - pb0->nChainWork; int64_t timeDiff = maxTime - minTime; return workDiff.getdouble() / timeDiff; } static RPCHelpMan getnetworkhashps() { return RPCHelpMan{ "getnetworkhashps", "Returns the estimated network hashes per second based on the last n " "blocks.\n" "Pass in [blocks] to override # of blocks, -1 specifies since last " "difficulty change.\n" "Pass in [height] to estimate the network speed at the time when a " "certain block was found.\n", { {"nblocks", RPCArg::Type::NUM, /* default */ "120", "The number of blocks, or -1 for blocks since last difficulty " "change."}, {"height", RPCArg::Type::NUM, /* default */ "-1", "To estimate at the time of the given height."}, }, RPCResult{RPCResult::Type::NUM, "", "Hashes per second estimated"}, RPCExamples{HelpExampleCli("getnetworkhashps", "") + HelpExampleRpc("getnetworkhashps", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); return GetNetworkHashPS( !request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1); }, }; } static bool GenerateBlock(const Config &config, ChainstateManager &chainman, CBlock &block, uint64_t &max_tries, unsigned int &extra_nonce, BlockHash &block_hash) { block_hash.SetNull(); const uint64_t nExcessiveBlockSize = config.GetMaxBlockSize(); { LOCK(cs_main); IncrementExtraNonce(&block, ::ChainActive().Tip(), nExcessiveBlockSize, extra_nonce); } const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, params) && !ShutdownRequested()) { ++block.nNonce; --max_tries; } if (max_tries == 0 || ShutdownRequested()) { return false; } if (block.nNonce == std::numeric_limits<uint32_t>::max()) { return true; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); if (!chainman.ProcessNewBlock(config, shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } block_hash = block.GetHash(); return true; } static UniValue generateBlocks(const Config &config, ChainstateManager &chainman, const CTxMemPool &mempool, const CScript &coinbase_script, int nGenerate, uint64_t nMaxTries) { int nHeightEnd = 0; int nHeight = 0; { // Don't keep cs_main locked. LOCK(cs_main); nHeight = ::ChainActive().Height(); nHeightEnd = nHeight + nGenerate; } unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) { std::unique_ptr<CBlockTemplate> pblocktemplate( - BlockAssembler(config, mempool).CreateNewBlock(coinbase_script)); + BlockAssembler(config, mempool) + .CreateNewBlock(::ChainstateActive(), coinbase_script)); if (!pblocktemplate.get()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } CBlock *pblock = &pblocktemplate->block; BlockHash block_hash; if (!GenerateBlock(config, chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) { break; } if (!block_hash.IsNull()) { ++nHeight; blockHashes.push_back(block_hash.GetHex()); } } return blockHashes; } static bool getScriptFromDescriptor(const std::string &descriptor, CScript &script, std::string &error) { FlatSigningProvider key_provider; const auto desc = Parse(descriptor, key_provider, error, /* require_checksum = */ false); if (desc) { if (desc->IsRange()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass " "through deriveaddresses first?"); } FlatSigningProvider provider; std::vector<CScript> scripts; if (!desc->Expand(0, key_provider, scripts, provider)) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys")); } // Combo descriptors can have 2 scripts, so we can't just check // scripts.size() == 1 CHECK_NONFATAL(scripts.size() > 0 && scripts.size() <= 2); if (scripts.size() == 1) { script = scripts.at(0); } else { // Else take the 2nd script, since it is p2pkh script = scripts.at(1); } return true; } return false; } static RPCHelpMan generatetodescriptor() { return RPCHelpMan{ "generatetodescriptor", "Mine blocks immediately to a specified descriptor (before the RPC " "call returns)\n", { {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."}, {"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."}, }, RPCResult{RPCResult::Type::ARR, "", "hashes of blocks generated", { {RPCResult::Type::STR_HEX, "", "blockhash"}, }}, RPCExamples{"\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const int num_blocks{request.params[0].get_int()}; const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()}; CScript coinbase_script; std::string error; if (!getScriptFromDescriptor(request.params[1].get_str(), coinbase_script, error)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } const CTxMemPool &mempool = EnsureMemPool(request.context); ChainstateManager &chainman = EnsureChainman(request.context); return generateBlocks(config, chainman, mempool, coinbase_script, num_blocks, max_tries); }, }; } static RPCHelpMan generate() { return RPCHelpMan{"generate", "has been replaced by the -generate cli option. Refer to " "-help for more information.", {}, {}, RPCExamples{""}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { throw JSONRPCError(RPC_METHOD_NOT_FOUND, self.ToString()); }}; } static RPCHelpMan generatetoaddress() { return RPCHelpMan{ "generatetoaddress", "Mine blocks immediately to a specified address before the " "RPC call returns)\n", { {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."}, {"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."}, }, RPCResult{RPCResult::Type::ARR, "", "hashes of blocks generated", { {RPCResult::Type::STR_HEX, "", "blockhash"}, }}, RPCExamples{ "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"") + "If you are using the " PACKAGE_NAME " wallet, you can " "get a new address to send the newly generated bitcoin to with:\n" + HelpExampleCli("getnewaddress", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const int num_blocks{request.params[0].get_int()}; const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int64()}; CTxDestination destination = DecodeDestination( request.params[1].get_str(), config.GetChainParams()); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address"); } const CTxMemPool &mempool = EnsureMemPool(request.context); ChainstateManager &chainman = EnsureChainman(request.context); CScript coinbase_script = GetScriptForDestination(destination); return generateBlocks(config, chainman, mempool, coinbase_script, num_blocks, max_tries); }, }; } static RPCHelpMan generateblock() { return RPCHelpMan{ "generateblock", "Mine a block with a set of ordered transactions immediately to a " "specified address or descriptor (before the RPC call returns)\n", { {"output", RPCArg::Type::STR, RPCArg::Optional::NO, "The address or descriptor to send the newly generated bitcoin " "to."}, { "transactions", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings which are either txids or raw " "transactions.\n" "Txids must reference transactions currently in the mempool.\n" "All transactions must be valid and in valid order, otherwise " "the block will be rejected.", { {"rawtx/txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, }, }, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "hash of generated block"}, }}, RPCExamples{ "\nGenerate a block to myaddress, with txs rawtx and " "mempool_txid\n" + HelpExampleCli("generateblock", R"("myaddress" '["rawtx", "mempool_txid"]')")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const auto address_or_descriptor = request.params[0].get_str(); CScript coinbase_script; std::string error; const CChainParams &chainparams = config.GetChainParams(); if (!getScriptFromDescriptor(address_or_descriptor, coinbase_script, error)) { const auto destination = DecodeDestination(address_or_descriptor, chainparams); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address or descriptor"); } coinbase_script = GetScriptForDestination(destination); } const CTxMemPool &mempool = EnsureMemPool(request.context); std::vector<CTransactionRef> txs; const auto raw_txs_or_txids = request.params[1].get_array(); for (size_t i = 0; i < raw_txs_or_txids.size(); i++) { const auto str(raw_txs_or_txids[i].get_str()); uint256 hash; CMutableTransaction mtx; if (ParseHashStr(str, hash)) { const auto tx = mempool.get(TxId(hash)); if (!tx) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str)); } txs.emplace_back(tx); } else if (DecodeHexTx(mtx, str)) { txs.push_back(MakeTransactionRef(std::move(mtx))); } else { throw JSONRPCError( RPC_DESERIALIZATION_ERROR, strprintf("Transaction decode failed for %s", str)); } } CBlock block; { LOCK(cs_main); CTxMemPool empty_mempool; std::unique_ptr<CBlockTemplate> blocktemplate( BlockAssembler(config, empty_mempool) - .CreateNewBlock(coinbase_script)); + .CreateNewBlock(::ChainstateActive(), coinbase_script)); if (!blocktemplate) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } block = blocktemplate->block; } CHECK_NONFATAL(block.vtx.size() == 1); // Add transactions block.vtx.insert(block.vtx.end(), txs.begin(), txs.end()); { LOCK(cs_main); BlockValidationState state; if (!TestBlockValidity(state, chainparams, ::ChainstateActive(), block, g_chainman.m_blockman.LookupBlockIndex( block.hashPrevBlock), BlockValidationOptions(config) .withCheckPoW(false) .withCheckMerkleRoot(false))) { throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString())); } } BlockHash block_hash; uint64_t max_tries{DEFAULT_MAX_TRIES}; unsigned int extra_nonce{0}; if (!GenerateBlock(config, EnsureChainman(request.context), block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) { throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block."); } UniValue obj(UniValue::VOBJ); obj.pushKV("hash", block_hash.GetHex()); return obj; }, }; } static RPCHelpMan getmininginfo() { return RPCHelpMan{ "getmininginfo", "Returns a json object containing mining-related " "information.", {}, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "blocks", "The current block"}, {RPCResult::Type::NUM, "currentblocksize", /* optional */ true, "The block size of the last assembled block (only present if " "a block was ever assembled)"}, {RPCResult::Type::NUM, "currentblocktx", /* optional */ true, "The number of block transactions of the last assembled block " "(only present if a block was ever assembled)"}, {RPCResult::Type::NUM, "difficulty", "The current difficulty"}, {RPCResult::Type::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, {RPCResult::Type::STR, "chain", "current network name (main, test, regtest)"}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, RPCExamples{HelpExampleCli("getmininginfo", "") + HelpExampleRpc("getmininginfo", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); const CTxMemPool &mempool = EnsureMemPool(request.context); UniValue obj(UniValue::VOBJ); obj.pushKV("blocks", int(::ChainActive().Height())); if (BlockAssembler::m_last_block_size) { obj.pushKV("currentblocksize", *BlockAssembler::m_last_block_size); } if (BlockAssembler::m_last_block_num_txs) { obj.pushKV("currentblocktx", *BlockAssembler::m_last_block_num_txs); } obj.pushKV("difficulty", double(GetDifficulty(::ChainActive().Tip()))); obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(config, request)); obj.pushKV("pooledtx", uint64_t(mempool.size())); obj.pushKV("chain", config.GetChainParams().NetworkIDString()); obj.pushKV("warnings", GetWarnings(false).original); return obj; }, }; } // NOTE: Unlike wallet RPC (which use XEC values), mining RPCs follow GBT (BIP // 22) in using satoshi amounts static RPCHelpMan prioritisetransaction() { return RPCHelpMan{ "prioritisetransaction", "Accepts the transaction into mined blocks at a higher " "(or lower) priority\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id."}, {"dummy", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "API-Compatibility for previous API. Must be zero or null.\n" " DEPRECATED. For forward compatibility " "use named arguments and omit this parameter."}, {"fee_delta", RPCArg::Type::NUM, RPCArg::Optional::NO, "The fee value (in satoshis) to add (or subtract, if negative).\n" " The fee is not actually paid, only the " "algorithm for selecting transactions into a block\n" " considers the transaction as it would " "have paid a higher (or lower) fee."}, }, RPCResult{RPCResult::Type::BOOL, "", "Returns true"}, RPCExamples{ HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); TxId txid(ParseHashV(request.params[0], "txid")); Amount nAmount = request.params[2].get_int64() * SATOSHI; if (!(request.params[1].isNull() || request.params[1].get_real() == 0)) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Priority is no longer supported, dummy argument to " "prioritisetransaction must be 0."); } EnsureMemPool(request.context).PrioritiseTransaction(txid, nAmount); return true; }, }; } // NOTE: Assumes a conclusive result; if result is inconclusive, it must be // handled by caller static UniValue BIP22ValidationResult(const Config &config, const BlockValidationState &state) { if (state.IsValid()) { return NullUniValue; } if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); } if (state.IsInvalid()) { std::string strRejectReason = state.GetRejectReason(); if (strRejectReason.empty()) { return "rejected"; } return strRejectReason; } // Should be impossible. return "valid?"; } static RPCHelpMan getblocktemplate() { return RPCHelpMan{ "getblocktemplate", "If the request parameters include a 'mode' key, that is used to " "explicitly select between the default 'template' request or a " "'proposal'.\n" "It returns data needed to construct a block to work on.\n" "For full specification, see BIPs 22, 23, 9, and 145:\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0022.mediawiki\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0023.mediawiki\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0009.mediawiki#getblocktemplate_changes\n" " ", { {"template_request", RPCArg::Type::OBJ, "{}", "Format of the template", { {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP " "23), or omitted"}, { "capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", { {"support", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', " "'coinbasetxn', 'coinbasevalue', 'proposal', " "'serverlist', 'workid'"}, }, }, }, "\"template_request\""}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "version", "The preferred block version"}, {RPCResult::Type::STR, "previousblockhash", "The hash of current highest block"}, {RPCResult::Type::ARR, "transactions", "contents of non-coinbase transactions that should be " "included in the next block", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "data", "transaction data encoded in hexadecimal " "(byte-for-byte)"}, {RPCResult::Type::STR_HEX, "txid", "transaction id encoded in little-endian " "hexadecimal"}, {RPCResult::Type::STR_HEX, "hash", "hash encoded in little-endian hexadecimal"}, {RPCResult::Type::ARR, "depends", "array of numbers", { {RPCResult::Type::NUM, "", "transactions before this one (by 1-based " "index in 'transactions' list) that must be " "present in the final block if this one is"}, }}, {RPCResult::Type::NUM, "fee", "difference in value between transaction inputs and " "outputs (in satoshis); for coinbase transactions, " "this is a negative Number of the total collected " "block fees (ie, not including the block subsidy); " "if key is not present, fee is unknown and clients " "MUST NOT assume there isn't one"}, {RPCResult::Type::NUM, "sigops", "total SigOps cost, as counted for purposes of " "block limits; if key is not present, sigop cost is " "unknown and clients MUST NOT assume it is zero"}, }}, }}, {RPCResult::Type::OBJ, "coinbaseaux", "data that should be included in the coinbase's scriptSig " "content", { {RPCResult::Type::ELISION, "", ""}, }}, {RPCResult::Type::NUM, "coinbasevalue", "maximum allowable input to coinbase transaction, including " "the generation award and transaction fees (in satoshis)"}, {RPCResult::Type::OBJ, "coinbasetxn", "information for coinbase transaction", { {RPCResult::Type::OBJ, "minerfund", "information related to the coinbase miner fund", { {RPCResult::Type::ARR, "addresses", "List of valid addresses for the miner fund output", { {RPCResult::Type::ELISION, "", ""}, }}, {RPCResult::Type::STR_AMOUNT, "minimumvalue", "The minimum value the miner fund output must pay"}, }}, {RPCResult::Type::ELISION, "", ""}, }}, {RPCResult::Type::STR, "target", "The hash target"}, {RPCResult::Type::NUM_TIME, "mintime", "The minimum timestamp appropriate for the next block time, " "expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::ARR, "mutable", "list of ways the block template may be changed", { {RPCResult::Type::STR, "value", "A way the block template may be changed, e.g. 'time', " "'transactions', 'prevblock'"}, }}, {RPCResult::Type::STR_HEX, "noncerange", "A range of valid nonces"}, {RPCResult::Type::NUM, "sigoplimit", "limit of sigops in blocks"}, {RPCResult::Type::NUM, "sizelimit", "limit of block size"}, {RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME}, {RPCResult::Type::STR, "bits", "compressed target of next block"}, {RPCResult::Type::NUM, "height", "The height of the next block"}, }}, RPCExamples{HelpExampleCli("getblocktemplate", "") + HelpExampleRpc("getblocktemplate", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { LOCK(cs_main); const CChainParams &chainparams = config.GetChainParams(); std::string strMode = "template"; UniValue lpval = NullUniValue; std::set<std::string> setClientRules; if (!request.params[0].isNull()) { const UniValue &oparam = request.params[0].get_obj(); const UniValue &modeval = find_value(oparam, "mode"); if (modeval.isStr()) { strMode = modeval.get_str(); } else if (modeval.isNull()) { /* Do nothing */ } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); } lpval = find_value(oparam, "longpollid"); if (strMode == "proposal") { const UniValue &dataval = find_value(oparam, "data"); if (!dataval.isStr()) { throw JSONRPCError( RPC_TYPE_ERROR, "Missing data String key for proposal"); } CBlock block; if (!DecodeHexBlk(block, dataval.get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } const BlockHash hash = block.GetHash(); const CBlockIndex *pindex = g_chainman.m_blockman.LookupBlockIndex(hash); if (pindex) { if (pindex->IsValid(BlockValidity::SCRIPTS)) { return "duplicate"; } if (pindex->nStatus.isInvalid()) { return "duplicate-invalid"; } return "duplicate-inconclusive"; } CBlockIndex *const pindexPrev = ::ChainActive().Tip(); // TestBlockValidity only supports blocks built on the // current Tip if (block.hashPrevBlock != pindexPrev->GetBlockHash()) { return "inconclusive-not-best-prevblk"; } BlockValidationState state; TestBlockValidity(state, chainparams, ::ChainstateActive(), block, pindexPrev, BlockValidationOptions(config) .withCheckPoW(false) .withCheckMerkleRoot(true)); return BIP22ValidationResult(config, state); } } if (strMode != "template") { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); } NodeContext &node = EnsureNodeContext(request.context); if (!node.connman) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Bitcoin is not connected!"); } if (::ChainstateActive().IsInitialBlockDownload()) { throw JSONRPCError( RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks..."); } static unsigned int nTransactionsUpdatedLast; const CTxMemPool &mempool = EnsureMemPool(request.context); if (!lpval.isNull()) { // Wait to respond until either the best block changes, OR a // minute has passed and there are more transactions uint256 hashWatchedChain; std::chrono::steady_clock::time_point checktxtime; unsigned int nTransactionsUpdatedLastLP; if (lpval.isStr()) { // Format: <hashBestChain><nTransactionsUpdatedLast> std::string lpstr = lpval.get_str(); hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid"); nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64)); } else { // NOTE: Spec does not specify behaviour for non-string // longpollid, but this makes testing easier hashWatchedChain = ::ChainActive().Tip()->GetBlockHash(); nTransactionsUpdatedLastLP = nTransactionsUpdatedLast; } // Release lock while waiting LEAVE_CRITICAL_SECTION(cs_main); { checktxtime = std::chrono::steady_clock::now() + std::chrono::minutes(1); WAIT_LOCK(g_best_block_mutex, lock); while (g_best_block == hashWatchedChain && IsRPCRunning()) { if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout) { // Timeout: Check transactions for update // without holding the mempool look to avoid // deadlocks if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) { break; } checktxtime += std::chrono::seconds(10); } } } ENTER_CRITICAL_SECTION(cs_main); if (!IsRPCRunning()) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); } // TODO: Maybe recheck connections/IBD and (if something wrong) // send an expires-immediately template to stop miners? } // Update block static CBlockIndex *pindexPrev; static int64_t nStart; static std::unique_ptr<CBlockTemplate> pblocktemplate; if (pindexPrev != ::ChainActive().Tip() || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite // any failures from here on pindexPrev = nullptr; // Store the pindexBest used before CreateNewBlock, to avoid // races nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex *pindexPrevNew = ::ChainActive().Tip(); nStart = GetTime(); // Create new block CScript scriptDummy = CScript() << OP_TRUE; pblocktemplate = - BlockAssembler(config, mempool).CreateNewBlock(scriptDummy); + BlockAssembler(config, mempool) + .CreateNewBlock(::ChainstateActive(), scriptDummy); if (!pblocktemplate) { throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); } // Need to update only after we know CreateNewBlock succeeded pindexPrev = pindexPrevNew; } CHECK_NONFATAL(pindexPrev); // pointer for convenience CBlock *pblock = &pblocktemplate->block; // Update nTime UpdateTime(pblock, chainparams, pindexPrev); pblock->nNonce = 0; UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); Amount coinbasevalue = Amount::zero(); UniValue transactions(UniValue::VARR); transactions.reserve(pblock->vtx.size()); int index_in_template = 0; for (const auto &it : pblock->vtx) { const CTransaction &tx = *it; const TxId txId = tx.GetId(); if (tx.IsCoinBase()) { index_in_template++; for (const auto &o : pblock->vtx[0]->vout) { coinbasevalue += o.nValue; } continue; } UniValue entry(UniValue::VOBJ); entry.reserve(5); entry.__pushKV("data", EncodeHexTx(tx)); entry.__pushKV("txid", txId.GetHex()); entry.__pushKV("hash", tx.GetHash().GetHex()); entry.__pushKV("fee", pblocktemplate->entries[index_in_template].fees / SATOSHI); int64_t nTxSigOps = pblocktemplate->entries[index_in_template].sigOpCount; entry.__pushKV("sigops", nTxSigOps); transactions.push_back(entry); index_in_template++; } UniValue aux(UniValue::VOBJ); UniValue minerFundList(UniValue::VARR); const Consensus::Params &consensusParams = chainparams.GetConsensus(); for (auto fundDestination : GetMinerFundWhitelist(consensusParams, pindexPrev)) { minerFundList.push_back( EncodeDestination(fundDestination, config)); } int64_t minerFundMinValue = 0; if (IsAxionEnabled(consensusParams, pindexPrev)) { minerFundMinValue = int64_t(GetMinerFundAmount(coinbasevalue) / SATOSHI); } UniValue minerFund(UniValue::VOBJ); minerFund.pushKV("addresses", minerFundList); minerFund.pushKV("minimumvalue", minerFundMinValue); UniValue coinbasetxn(UniValue::VOBJ); coinbasetxn.pushKV("minerfund", minerFund); arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); UniValue aMutable(UniValue::VARR); aMutable.push_back("time"); aMutable.push_back("transactions"); aMutable.push_back("prevblock"); UniValue result(UniValue::VOBJ); result.pushKV("capabilities", aCaps); result.pushKV("version", pblock->nVersion); result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); result.pushKV("transactions", transactions); result.pushKV("coinbaseaux", aux); result.pushKV("coinbasetxn", coinbasetxn); result.pushKV("coinbasevalue", int64_t(coinbasevalue / SATOSHI)); result.pushKV("longpollid", ::ChainActive().Tip()->GetBlockHash().GetHex() + ToString(nTransactionsUpdatedLast)); result.pushKV("target", hashTarget.GetHex()); result.pushKV("mintime", int64_t(pindexPrev->GetMedianTimePast()) + 1); result.pushKV("mutable", aMutable); result.pushKV("noncerange", "00000000ffffffff"); result.pushKV("sigoplimit", GetMaxBlockSigChecksCount(DEFAULT_MAX_BLOCK_SIZE)); result.pushKV("sizelimit", DEFAULT_MAX_BLOCK_SIZE); result.pushKV("curtime", pblock->GetBlockTime()); result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("height", int64_t(pindexPrev->nHeight) + 1); return result; }, }; } class submitblock_StateCatcher final : public CValidationInterface { public: uint256 hash; bool found; BlockValidationState state; explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {} protected: void BlockChecked(const CBlock &block, const BlockValidationState &stateIn) override { if (block.GetHash() != hash) { return; } found = true; state = stateIn; } }; static RPCHelpMan submitblock() { // We allow 2 arguments for compliance with BIP22. Argument 2 is ignored. return RPCHelpMan{ "submitblock", "Attempts to submit new block to network.\n" "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n", { {"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded block data to submit"}, {"dummy", RPCArg::Type::STR, /* default */ "ignored", "dummy value, for compatibility with BIP22. This value is " "ignored."}, }, RPCResult{RPCResult::Type::NONE, "", "Returns JSON Null when valid, a string according to BIP22 " "otherwise"}, RPCExamples{HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); CBlock &block = *blockptr; if (!DecodeHexBlk(block, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block does not start with a coinbase"); } const BlockHash hash = block.GetHash(); { LOCK(cs_main); const CBlockIndex *pindex = g_chainman.m_blockman.LookupBlockIndex(hash); if (pindex) { if (pindex->IsValid(BlockValidity::SCRIPTS)) { return "duplicate"; } if (pindex->nStatus.isInvalid()) { return "duplicate-invalid"; } } } bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); bool accepted = EnsureChainman(request.context) .ProcessNewBlock(config, blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; } if (!sc->found) { return "inconclusive"; } return BIP22ValidationResult(config, sc->state); }, }; } static RPCHelpMan submitheader() { return RPCHelpMan{ "submitheader", "Decode the given hexdata as a header and submit it as a candidate " "chain tip if valid." "\nThrows when the header is invalid.\n", { {"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded block header data"}, }, RPCResult{RPCResult::Type::NONE, "", "None"}, RPCExamples{HelpExampleCli("submitheader", "\"aabbcc\"") + HelpExampleRpc("submitheader", "\"aabbcc\"")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { CBlockHeader h; if (!DecodeHexBlockHeader(h, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block header decode failed"); } { LOCK(cs_main); if (!g_chainman.m_blockman.LookupBlockIndex(h.hashPrevBlock)) { throw JSONRPCError(RPC_VERIFY_ERROR, "Must submit previous header (" + h.hashPrevBlock.GetHex() + ") first"); } } BlockValidationState state; EnsureChainman(request.context) .ProcessNewBlockHeaders(config, {h}, state); if (state.IsValid()) { return NullUniValue; } if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); } throw JSONRPCError(RPC_VERIFY_ERROR, state.GetRejectReason()); }, }; } static RPCHelpMan estimatefee() { return RPCHelpMan{ "estimatefee", "Estimates the approximate fee per kilobyte needed for a " "transaction\n", {}, RPCResult{RPCResult::Type::NUM, "", "estimated fee-per-kilobyte"}, RPCExamples{HelpExampleCli("estimatefee", "")}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { const CTxMemPool &mempool = EnsureMemPool(request.context); return mempool.estimateFee().GetFeePerK(); }, }; } void RegisterMiningRPCCommands(CRPCTable &t) { // clang-format off static const CRPCCommand commands[] = { // category actor (function) // ---------- ---------------------- {"mining", getnetworkhashps, }, {"mining", getmininginfo, }, {"mining", prioritisetransaction, }, {"mining", getblocktemplate, }, {"mining", submitblock, }, {"mining", submitheader, }, {"generating", generatetoaddress, }, {"generating", generatetodescriptor, }, {"generating", generateblock, }, {"util", estimatefee, }, {"hidden", generate, }, }; // clang-format on for (const auto &c : commands) { t.appendCommand(c.name, &c); } } diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index cb1442bd2..1f644c894 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -1,337 +1,338 @@ // Copyright (c) 2017-2018 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 <blockfilter.h> #include <chainparams.h> #include <config.h> #include <consensus/validation.h> #include <index/blockfilterindex.h> #include <miner.h> #include <pow/pow.h> #include <script/standard.h> #include <test/util/blockfilter.h> #include <test/util/setup_common.h> #include <validation.h> #include <boost/test/unit_test.hpp> BOOST_AUTO_TEST_SUITE(blockfilter_index_tests) struct BuildChainTestingSetup : public TestChain100Setup { CBlock CreateBlock(const CBlockIndex *prev, const std::vector<CMutableTransaction> &txns, const CScript &scriptPubKey); bool BuildChain(const CBlockIndex *pindex, const CScript &coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>> &chain); }; static bool CheckFilterLookups(BlockFilterIndex &filter_index, const CBlockIndex *block_index, uint256 &last_header) { BlockFilter expected_filter; if (!ComputeFilter(filter_index.GetFilterType(), block_index, expected_filter)) { BOOST_ERROR("ComputeFilter failed on block " << block_index->nHeight); return false; } BlockFilter filter; uint256 filter_header; std::vector<BlockFilter> filters; std::vector<uint256> filter_hashes; BOOST_CHECK(filter_index.LookupFilter(block_index, filter)); BOOST_CHECK(filter_index.LookupFilterHeader(block_index, filter_header)); BOOST_CHECK(filter_index.LookupFilterRange(block_index->nHeight, block_index, filters)); BOOST_CHECK(filter_index.LookupFilterHashRange(block_index->nHeight, block_index, filter_hashes)); BOOST_CHECK_EQUAL(filters.size(), 1U); BOOST_CHECK_EQUAL(filter_hashes.size(), 1U); BOOST_CHECK_EQUAL(filter.GetHash(), expected_filter.GetHash()); BOOST_CHECK_EQUAL(filter_header, expected_filter.ComputeHeader(last_header)); BOOST_CHECK_EQUAL(filters[0].GetHash(), expected_filter.GetHash()); BOOST_CHECK_EQUAL(filter_hashes[0], expected_filter.GetHash()); filters.clear(); filter_hashes.clear(); last_header = filter_header; return true; } CBlock BuildChainTestingSetup::CreateBlock( const CBlockIndex *prev, const std::vector<CMutableTransaction> &txns, const CScript &scriptPubKey) { const Config &config = GetConfig(); std::unique_ptr<CBlockTemplate> pblocktemplate = - BlockAssembler(config, *m_node.mempool).CreateNewBlock(scriptPubKey); + BlockAssembler(config, *m_node.mempool) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); CBlock &block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; // Replace mempool-selected txns with just coinbase plus passed-in txns: block.vtx.resize(1); for (const CMutableTransaction &tx : txns) { block.vtx.push_back(MakeTransactionRef(tx)); } // IncrementExtraNonce creates a valid coinbase and merkleRoot unsigned int extraNonce = 0; IncrementExtraNonce(&block, prev, config.GetMaxBlockSize(), extraNonce); while (!CheckProofOfWork(block.GetHash(), block.nBits, config.GetChainParams().GetConsensus())) { ++block.nNonce; } return block; } bool BuildChainTestingSetup::BuildChain( const CBlockIndex *pindex, const CScript &coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>> &chain) { std::vector<CMutableTransaction> no_txns; chain.resize(length); for (auto &block : chain) { block = std::make_shared<CBlock>( CreateBlock(pindex, no_txns, coinbase_script_pub_key)); CBlockHeader header = block->GetBlockHeader(); BlockValidationState state; if (!Assert(m_node.chainman) ->ProcessNewBlockHeaders(GetConfig(), {header}, state, &pindex)) { return false; } } return true; } BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { BlockFilterIndex filter_index(BlockFilterType::BASIC, 1 << 20, true); uint256 last_header; // Filter should not be found in the index before it is started. { LOCK(cs_main); BlockFilter filter; uint256 filter_header; std::vector<BlockFilter> filters; std::vector<uint256> filter_hashes; for (const CBlockIndex *block_index = ::ChainActive().Genesis(); block_index != nullptr; block_index = ::ChainActive().Next(block_index)) { BOOST_CHECK(!filter_index.LookupFilter(block_index, filter)); BOOST_CHECK( !filter_index.LookupFilterHeader(block_index, filter_header)); BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters)); BOOST_CHECK(!filter_index.LookupFilterHashRange( block_index->nHeight, block_index, filter_hashes)); } } // BlockUntilSyncedToCurrentChain should return false before index is // started. BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain()); filter_index.Start(); // Allow filter index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; int64_t time_start = GetTimeMillis(); while (!filter_index.BlockUntilSyncedToCurrentChain()) { BOOST_REQUIRE(time_start + timeout_ms > GetTimeMillis()); UninterruptibleSleep(std::chrono::milliseconds{100}); } // Check that filter index has all blocks that were in the chain before it // started. { LOCK(cs_main); const CBlockIndex *block_index; for (block_index = ::ChainActive().Genesis(); block_index != nullptr; block_index = ::ChainActive().Next(block_index)) { CheckFilterLookups(filter_index, block_index, last_header); } } // Create two forks. const CBlockIndex *tip; { LOCK(cs_main); tip = ::ChainActive().Tip(); } // Make sure we disable reorg protection. gArgs.ForceSetArg("-parkdeepreorg", "false"); CKey coinbase_key_A, coinbase_key_B; coinbase_key_A.MakeNewKey(true); coinbase_key_B.MakeNewKey(true); CScript coinbase_script_pub_key_A = GetScriptForDestination(PKHash(coinbase_key_A.GetPubKey())); CScript coinbase_script_pub_key_B = GetScriptForDestination(PKHash(coinbase_key_B.GetPubKey())); std::vector<std::shared_ptr<CBlock>> chainA, chainB; BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_A, 10, chainA)); BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_B, 10, chainB)); // Check that new blocks on chain A get indexed. uint256 chainA_last_header = last_header; for (size_t i = 0; i < 2; i++) { const auto &block = chainA[i]; BOOST_REQUIRE(Assert(m_node.chainman) ->ProcessNewBlock(GetConfig(), block, true, nullptr)); } for (size_t i = 0; i < 2; i++) { const auto &block = chainA[i]; const CBlockIndex *block_index; { LOCK(cs_main); block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainA_last_header); } // Reorg to chain B. uint256 chainB_last_header = last_header; for (size_t i = 0; i < 3; i++) { const auto &block = chainB[i]; BOOST_REQUIRE(Assert(m_node.chainman) ->ProcessNewBlock(GetConfig(), block, true, nullptr)); } for (size_t i = 0; i < 3; i++) { const auto &block = chainB[i]; const CBlockIndex *block_index; { LOCK(cs_main); block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainB_last_header); } // Check that filters for stale blocks on A can be retrieved. chainA_last_header = last_header; for (size_t i = 0; i < 2; i++) { const auto &block = chainA[i]; const CBlockIndex *block_index; { LOCK(cs_main); block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainA_last_header); } // Reorg back to chain A. for (size_t i = 2; i < 4; i++) { const auto &block = chainA[i]; BOOST_REQUIRE(Assert(m_node.chainman) ->ProcessNewBlock(GetConfig(), block, true, nullptr)); } // Check that chain A and B blocks can be retrieved. chainA_last_header = last_header; chainB_last_header = last_header; for (size_t i = 0; i < 3; i++) { const CBlockIndex *block_index; { LOCK(cs_main); block_index = g_chainman.m_blockman.LookupBlockIndex(chainA[i]->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainA_last_header); { LOCK(cs_main); block_index = g_chainman.m_blockman.LookupBlockIndex(chainB[i]->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainB_last_header); } // Test lookups for a range of filters/hashes. std::vector<BlockFilter> filters; std::vector<uint256> filter_hashes; { LOCK(cs_main); tip = ::ChainActive().Tip(); } BOOST_CHECK(filter_index.LookupFilterRange(0, tip, filters)); BOOST_CHECK(filter_index.LookupFilterHashRange(0, tip, filter_hashes)); BOOST_CHECK(tip->nHeight >= 0); BOOST_CHECK_EQUAL(filters.size(), tip->nHeight + 1U); BOOST_CHECK_EQUAL(filter_hashes.size(), tip->nHeight + 1U); filters.clear(); filter_hashes.clear(); filter_index.Interrupt(); filter_index.Stop(); } BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup) { BlockFilterIndex *filter_index; filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); BOOST_CHECK(filter_index == nullptr); BOOST_CHECK( InitBlockFilterIndex(BlockFilterType::BASIC, 1 << 20, true, false)); filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); BOOST_CHECK(filter_index != nullptr); BOOST_CHECK(filter_index->GetFilterType() == BlockFilterType::BASIC); // Initialize returns false if index already exists. BOOST_CHECK( !InitBlockFilterIndex(BlockFilterType::BASIC, 1 << 20, true, false)); int iter_count = 0; ForEachBlockFilterIndex( [&iter_count](BlockFilterIndex &_index) { iter_count++; }); BOOST_CHECK_EQUAL(iter_count, 1); BOOST_CHECK(DestroyBlockFilterIndex(BlockFilterType::BASIC)); // Destroy returns false because index was already destroyed. BOOST_CHECK(!DestroyBlockFilterIndex(BlockFilterType::BASIC)); filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); BOOST_CHECK(filter_index == nullptr); // Reinitialize index. BOOST_CHECK( InitBlockFilterIndex(BlockFilterType::BASIC, 1 << 20, true, false)); DestroyAllBlockFilterIndexes(); filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); BOOST_CHECK(filter_index == nullptr); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index e3c56ba7b..55c5021e9 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -1,740 +1,758 @@ // Copyright (c) 2011-2019 The Bitcoin Core developers // Copyright (c) 2017-2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <miner.h> #include <chain.h> #include <chainparams.h> #include <coins.h> #include <config.h> #include <consensus/consensus.h> #include <consensus/merkle.h> #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <policy/policy.h> #include <script/standard.h> #include <txmempool.h> #include <uint256.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> #include <validation.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> #include <memory> namespace miner_tests { struct MinerTestingSetup : public TestingSetup { void TestPackageSelection(const CChainParams &chainparams, const CScript &scriptPubKey, const std::vector<CTransactionRef> &txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) { return CheckSequenceLocks(::ChainstateActive(), *m_node.mempool, tx, flags); } BlockAssembler AssemblerForTest(const CChainParams ¶ms); }; } // namespace miner_tests BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup) static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB); BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams ¶ms) { BlockAssembler::Options options; options.blockMinFeeRate = blockMinFeeRate; return BlockAssembler(params, *m_node.mempool, options); } constexpr static struct { uint8_t extranonce; uint32_t nonce; } blockinfo[] = { {4, 0xa4a3e223}, {2, 0x15c32f9e}, {1, 0x0375b547}, {1, 0x7004a8a5}, {2, 0xce440296}, {2, 0x52cfe198}, {1, 0x77a72cd0}, {2, 0xbb5d6f84}, {2, 0x83f30c2c}, {1, 0x48a73d5b}, {1, 0xef7dcd01}, {2, 0x6809c6c4}, {2, 0x0883ab3c}, {1, 0x087bbbe2}, {2, 0x2104a814}, {2, 0xdffb6daa}, {1, 0xee8a0a08}, {2, 0xba4237c1}, {1, 0xa70349dc}, {1, 0x344722bb}, {3, 0xd6294733}, {2, 0xec9f5c94}, {2, 0xca2fbc28}, {1, 0x6ba4f406}, {2, 0x015d4532}, {1, 0x6e119b7c}, {2, 0x43e8f314}, {2, 0x27962f38}, {2, 0xb571b51b}, {2, 0xb36bee23}, {2, 0xd17924a8}, {2, 0x6bc212d9}, {1, 0x630d4948}, {2, 0x9a4c4ebb}, {2, 0x554be537}, {1, 0xd63ddfc7}, {2, 0xa10acc11}, {1, 0x759a8363}, {2, 0xfb73090d}, {1, 0xe82c6a34}, {1, 0xe33e92d7}, {3, 0x658ef5cb}, {2, 0xba32ff22}, {5, 0x0227a10c}, {1, 0xa9a70155}, {5, 0xd096d809}, {1, 0x37176174}, {1, 0x830b8d0f}, {1, 0xc6e3910e}, {2, 0x823f3ca8}, {1, 0x99850849}, {1, 0x7521fb81}, {1, 0xaacaabab}, {1, 0xd645a2eb}, {5, 0x7aea1781}, {5, 0x9d6e4b78}, {1, 0x4ce90fd8}, {1, 0xabdc832d}, {6, 0x4a34f32a}, {2, 0xf2524c1c}, {2, 0x1bbeb08a}, {1, 0xad47f480}, {1, 0x9f026aeb}, {1, 0x15a95049}, {2, 0xd1cb95b2}, {2, 0xf84bbda5}, {1, 0x0fa62cd1}, {1, 0xe05f9169}, {1, 0x78d194a9}, {5, 0x3e38147b}, {5, 0x737ba0d4}, {1, 0x63378e10}, {1, 0x6d5f91cf}, {2, 0x88612eb8}, {2, 0xe9639484}, {1, 0xb7fabc9d}, {2, 0x19b01592}, {1, 0x5a90dd31}, {2, 0x5bd7e028}, {2, 0x94d00323}, {1, 0xa9b9c01a}, {1, 0x3a40de61}, {1, 0x56e7eec7}, {5, 0x859f7ef6}, {1, 0xfd8e5630}, {1, 0x2b0c9f7f}, {1, 0xba700e26}, {1, 0x7170a408}, {1, 0x70de86a8}, {1, 0x74d64cd5}, {1, 0x49e738a1}, {2, 0x6910b602}, {0, 0x643c565f}, {1, 0x54264b3f}, {2, 0x97ea6396}, {2, 0x55174459}, {2, 0x03e8779a}, {1, 0x98f34d8f}, {1, 0xc07b2b07}, {1, 0xdfe29668}, {1, 0x3141c7c1}, {1, 0xb3b595f4}, {1, 0x735abf08}, {5, 0x623bfbce}, {2, 0xd351e722}, {1, 0xf4ca48c9}, {1, 0x5b19c670}, {1, 0xa164bf0e}, {2, 0xbbbeb305}, {2, 0xfe1c810a}, }; static CBlockIndex CreateBlockIndex(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { CBlockIndex index; index.nHeight = nHeight; index.pprev = ::ChainActive().Tip(); return index; } // Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. void MinerTestingSetup::TestPackageSelection( const CChainParams &chainparams, const CScript &scriptPubKey, const std::vector<CTransactionRef> &txFirst) { // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; // Test that a medium fee transaction will be selected after a higher fee // rate package with a low fee rate parent. CMutableTransaction tx; tx.vin.resize(1); tx.vin[0].scriptSig = CScript() << OP_1; tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); tx.vout.resize(1); tx.vout[0].nValue = int64_t(5000000000LL - 1000) * SATOSHI; // This tx has a low fee: 1000 satoshis. // Save this txid for later use. TxId parentTxId = tx.GetId(); m_node.mempool->addUnchecked(entry.Fee(1000 * SATOSHI) .Time(GetTime()) .SpendsCoinbase(true) .FromTx(tx)); // This tx has a medium fee: 10000 satoshis. tx.vin[0].prevout = COutPoint(txFirst[1]->GetId(), 0); tx.vout[0].nValue = int64_t(5000000000LL - 10000) * SATOSHI; TxId mediumFeeTxId = tx.GetId(); m_node.mempool->addUnchecked(entry.Fee(10000 * SATOSHI) .Time(GetTime()) .SpendsCoinbase(true) .FromTx(tx)); // This tx has a high fee, but depends on the first transaction. tx.vin[0].prevout = COutPoint(parentTxId, 0); // 50k satoshi fee. tx.vout[0].nValue = int64_t(5000000000LL - 1000 - 50000) * SATOSHI; TxId highFeeTxId = tx.GetId(); m_node.mempool->addUnchecked(entry.Fee(50000 * SATOSHI) .Time(GetTime()) .SpendsCoinbase(false) .FromTx(tx)); std::unique_ptr<CBlockTemplate> pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetId() == parentTxId); BOOST_CHECK(pblocktemplate->block.vtx[2]->GetId() == highFeeTxId); BOOST_CHECK(pblocktemplate->block.vtx[3]->GetId() == mediumFeeTxId); // Test that a package below the block min tx fee doesn't get included tx.vin[0].prevout = COutPoint(highFeeTxId, 0); // 0 fee. tx.vout[0].nValue = int64_t(5000000000LL - 1000 - 50000) * SATOSHI; TxId freeTxId = tx.GetId(); m_node.mempool->addUnchecked(entry.Fee(Amount::zero()).FromTx(tx)); size_t freeTxSize = GetSerializeSize(tx, PROTOCOL_VERSION); // Calculate a fee on child transaction that will put the package just // below the block min tx fee (assuming 1 child tx of the same size). Amount feeToUse = blockMinFeeRate.GetFee(2 * freeTxSize) - SATOSHI; tx.vin[0].prevout = COutPoint(freeTxId, 0); tx.vout[0].nValue = int64_t(5000000000LL - 1000 - 50000) * SATOSHI - feeToUse; TxId lowFeeTxId = tx.GetId(); m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected. for (const auto &txn : pblocktemplate->block.vtx) { BOOST_CHECK(txn->GetId() != freeTxId); BOOST_CHECK(txn->GetId() != lowFeeTxId); } // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee. Remove the low fee // transaction and replace with a higher fee transaction m_node.mempool->removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); // Now we should be just over the min relay fee. tx.vout[0].nValue -= 2 * SATOSHI; lowFeeTxId = tx.GetId(); m_node.mempool->addUnchecked(entry.Fee(feeToUse + 2 * SATOSHI).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetId() == freeTxId); BOOST_CHECK(pblocktemplate->block.vtx[5]->GetId() == lowFeeTxId); // Test that transaction selection properly updates ancestor fee // calculations as ancestor transactions get included in a block. Add a // 0-fee transaction that has 2 outputs. tx.vin[0].prevout = COutPoint(txFirst[2]->GetId(), 0); tx.vout.resize(2); tx.vout[0].nValue = int64_t(5000000000LL - 100000000) * SATOSHI; // 1BCC output. tx.vout[1].nValue = 100000000 * SATOSHI; TxId freeTxId2 = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(Amount::zero()).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself. tx.vin[0].prevout = COutPoint(freeTxId2, 0); tx.vout.resize(1); feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.vout[0].nValue = int64_t(5000000000LL - 100000000) * SATOSHI - feeToUse; TxId lowFeeTxId2 = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); // Verify that this tx isn't selected. for (const auto &txn : pblocktemplate->block.vtx) { BOOST_CHECK(txn->GetId() != freeTxId2); BOOST_CHECK(txn->GetId() != lowFeeTxId2); } // This tx will be mineable, and should cause lowFeeTxId2 to be selected as // well. tx.vin[0].prevout = COutPoint(freeTxId2, 1); // 10k satoshi fee. tx.vout[0].nValue = (100000000 - 10000) * SATOSHI; m_node.mempool->addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetId() == lowFeeTxId2); } void TestCoinbaseMessageEB(uint64_t eb, std::string cbmsg, const CTxMemPool &mempool) { GlobalConfig config; config.SetMaxBlockSize(eb); CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909" "a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112" "de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; std::unique_ptr<CBlockTemplate> pblocktemplate = - BlockAssembler(config, mempool).CreateNewBlock(scriptPubKey); + BlockAssembler(config, mempool) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); CBlock *pblock = &pblocktemplate->block; // IncrementExtraNonce creates a valid coinbase and merkleRoot unsigned int extraNonce = 0; IncrementExtraNonce(pblock, ::ChainActive().Tip(), config.GetMaxBlockSize(), extraNonce); unsigned int nHeight = ::ChainActive().Tip()->nHeight + 1; std::vector<uint8_t> vec(cbmsg.begin(), cbmsg.end()); BOOST_CHECK(pblock->vtx[0]->vin[0].scriptSig == (CScript() << nHeight << CScriptNum(extraNonce) << vec)); } // Coinbase scriptSig has to contains the correct EB value // converted to MB, rounded down to the first decimal BOOST_AUTO_TEST_CASE(CheckCoinbase_EB) { TestCoinbaseMessageEB(1000001, "/EB1.0/", *m_node.mempool); TestCoinbaseMessageEB(2000000, "/EB2.0/", *m_node.mempool); TestCoinbaseMessageEB(8000000, "/EB8.0/", *m_node.mempool); TestCoinbaseMessageEB(8320000, "/EB8.3/", *m_node.mempool); } // NOTE: These tests rely on CreateNewBlock doing its own self-validation! BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { // Note that by default, these tests run with size accounting enabled. GlobalConfig config; const CChainParams &chainparams = config.GetChainParams(); CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909" "a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112" "de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; std::unique_ptr<CBlockTemplate> pblocktemplate; CMutableTransaction tx; CScript script; TestMemPoolEntryHelper entry; entry.nFee = 11 * SATOSHI; entry.nHeight = 11; fCheckpointsEnabled = false; // Simple block creation, nothing special yet: BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); // We can't make transactions until we have inputs. // Therefore, load 110 blocks :) static_assert(std::size(blockinfo) == 110, "Should have 110 blocks to import"); int baseheight = 0; std::vector<CTransactionRef> txFirst; for (const auto &bi : blockinfo) { // pointer for convenience CBlock *pblock = &pblocktemplate->block; { LOCK(cs_main); pblock->nVersion = 1; pblock->nTime = ::ChainActive().Tip()->GetMedianTimePast() + 1; CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.nVersion = 1; txCoinbase.vin[0].scriptSig = CScript(); txCoinbase.vin[0].scriptSig.push_back(bi.extranonce); txCoinbase.vin[0].scriptSig.push_back(::ChainActive().Height()); txCoinbase.vout.resize(1); txCoinbase.vout[0].scriptPubKey = CScript(); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); if (txFirst.size() == 0) { baseheight = ::ChainActive().Height(); } if (txFirst.size() < 4) { txFirst.push_back(pblock->vtx[0]); } pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); pblock->nNonce = bi.nonce; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); BOOST_CHECK( Assert(m_node.chainman) ->ProcessNewBlock(config, shared_pblock, true, nullptr)); pblock->hashPrevBlock = pblock->GetHash(); } LOCK(cs_main); LOCK(m_node.mempool->cs); // Just to make sure we can still make simple blocks. BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); const Amount BLOCKSUBSIDY = 50 * COIN; const Amount LOWFEE = CENT; const Amount HIGHFEE = COIN; const Amount HIGHERFEE = 4 * COIN; // block size > limit tx.vin.resize(1); tx.vin[0].scriptSig = CScript(); // 18 * (520char + DROP) + OP_1 = 9433 bytes std::vector<uint8_t> vchData(520); for (unsigned int i = 0; i < 18; ++i) { tx.vin[0].scriptSig << vchData << OP_DROP; } tx.vin[0].scriptSig << OP_1; tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); tx.vout.resize(1); tx.vout[0].nValue = BLOCKSUBSIDY; for (unsigned int i = 0; i < 128; ++i) { tx.vout[0].nValue -= LOWFEE; const TxId txid = tx.GetId(); // Only first tx spends coinbase. bool spendsCoinbase = i == 0; m_node.mempool->addUnchecked(entry.Fee(LOWFEE) .Time(GetTime()) .SpendsCoinbase(spendsCoinbase) .FromTx(tx)); tx.vin[0].prevout = COutPoint(txid, 0); } BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); m_node.mempool->clear(); // Orphan in mempool, template creation fails. m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); BOOST_CHECK_EXCEPTION( - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); m_node.mempool->clear(); // Child with higher priority than parent. tx.vin[0].scriptSig = CScript() << OP_1; tx.vin[0].prevout = COutPoint(txFirst[1]->GetId(), 0); tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; TxId txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout = COutPoint(txid, 0); tx.vin.resize(2); tx.vin[1].scriptSig = CScript() << OP_1; tx.vin[1].prevout = COutPoint(txFirst[0]->GetId(), 0); // First txn output + fresh coinbase - new txn fee. tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); m_node.mempool->clear(); // Coinbase in mempool, template creation fails. tx.vin.resize(1); tx.vin[0].prevout = COutPoint(); tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; tx.vout[0].nValue = Amount::zero(); txid = tx.GetId(); // Give it a fee so it'll get mined. m_node.mempool->addUnchecked( entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // Should throw bad-tx-coinbase BOOST_CHECK_EXCEPTION( - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-tx-coinbase")); m_node.mempool->clear(); // Double spend txn pair in mempool, template creation fails. tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); tx.vin[0].scriptSig = CScript() << OP_1; tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; tx.vout[0].scriptPubKey = CScript() << OP_1; txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); tx.vout[0].scriptPubKey = CScript() << OP_2; txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK_EXCEPTION( - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); m_node.mempool->clear(); // Subsidy changing. int nHeight = ::ChainActive().Height(); // Create an actual 209999-long block chain (without valid blocks). while (::ChainActive().Tip()->nHeight < 209999) { CBlockIndex *prev = ::ChainActive().Tip(); CBlockIndex *next = new CBlockIndex(); next->phashBlock = new BlockHash(InsecureRand256()); ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); ::ChainActive().SetTip(next); } BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); // Extend to a 210000-long block chain. while (::ChainActive().Tip()->nHeight < 210000) { CBlockIndex *prev = ::ChainActive().Tip(); CBlockIndex *next = new CBlockIndex(); next->phashBlock = new BlockHash(InsecureRand256()); ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); ::ChainActive().SetTip(next); } BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); // Invalid p2sh txn in mempool, template creation fails tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); tx.vin[0].scriptSig = CScript() << OP_1; tx.vout[0].nValue = BLOCKSUBSIDY - LOWFEE; script = CScript() << OP_0; tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout = COutPoint(txid, 0); tx.vin[0].scriptSig = CScript() << std::vector<uint8_t>(script.begin(), script.end()); tx.vout[0].nValue -= LOWFEE; txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // Should throw blk-bad-inputs BOOST_CHECK_EXCEPTION( - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("blk-bad-inputs")); m_node.mempool->clear(); // Delete the dummy blocks again. while (::ChainActive().Tip()->nHeight > nHeight) { CBlockIndex *del = ::ChainActive().Tip(); ::ChainActive().SetTip(del->pprev); ::ChainstateActive().CoinsTip().SetBestBlock( del->pprev->GetBlockHash()); delete del->phashBlock; delete del; } // non-final txs in mempool SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1); uint32_t flags = LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST; // height map std::vector<int> prevheights; // Relative height locked. tx.nVersion = 2; tx.vin.resize(1); prevheights.resize(1); // Only 1 transaction. tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); tx.vin[0].scriptSig = CScript() << OP_1; // txFirst[0] is the 2nd block tx.vin[0].nSequence = ::ChainActive().Tip()->nHeight + 1; prevheights[0] = baseheight + 1; tx.vout.resize(1); tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = 0; txid = tx.GetId(); m_node.mempool->addUnchecked( entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); const Consensus::Params ¶ms = chainparams.GetConsensus(); { // Locktime passes. TxValidationState state; BOOST_CHECK(ContextualCheckTransactionForCurrentBlock( ::ChainActive().Tip(), params, CTransaction(tx), state, flags)); } // Sequence locks fail. BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass on 2nd block. BOOST_CHECK( SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 2))); // Relative time locked. tx.vin[0].prevout = COutPoint(txFirst[1]->GetId(), 0); // txFirst[1] is the 3rd block. tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((::ChainActive().Tip()->GetMedianTimePast() + 1 - ::ChainActive()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); prevheights[0] = baseheight + 2; txid = tx.GetId(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); { // Locktime passes. TxValidationState state; BOOST_CHECK(ContextualCheckTransactionForCurrentBlock( ::ChainActive().Tip(), params, CTransaction(tx), state, flags)); } // Sequence locks fail. BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) { // Trick the MedianTimePast. ::ChainActive() .Tip() ->GetAncestor(::ChainActive().Tip()->nHeight - i) ->nTime += 512; } // Sequence locks pass 512 seconds later. BOOST_CHECK( SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 1))); for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) { // Undo tricked MTP. ::ChainActive() .Tip() ->GetAncestor(::ChainActive().Tip()->nHeight - i) ->nTime -= 512; } // Absolute height locked. tx.vin[0].prevout = COutPoint(txFirst[2]->GetId(), 0); tx.vin[0].nSequence = CTxIn::SEQUENCE_FINAL - 1; prevheights[0] = baseheight + 3; tx.nLockTime = ::ChainActive().Tip()->nHeight + 1; txid = tx.GetId(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); { // Locktime fails. TxValidationState state; BOOST_CHECK(!ContextualCheckTransactionForCurrentBlock( ::ChainActive().Tip(), params, CTransaction(tx), state, flags)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-nonfinal"); } // Sequence locks pass. BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); { // Locktime passes on 2nd block. TxValidationState state; int64_t nMedianTimePast = ::ChainActive().Tip()->GetMedianTimePast(); BOOST_CHECK(ContextualCheckTransaction( params, CTransaction(tx), state, ::ChainActive().Tip()->nHeight + 2, nMedianTimePast, nMedianTimePast)); } // Absolute time locked. tx.vin[0].prevout = COutPoint(txFirst[3]->GetId(), 0); tx.nLockTime = ::ChainActive().Tip()->GetMedianTimePast(); prevheights.resize(1); prevheights[0] = baseheight + 4; txid = tx.GetId(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); { // Locktime fails. TxValidationState state; BOOST_CHECK(!ContextualCheckTransactionForCurrentBlock( ::ChainActive().Tip(), params, CTransaction(tx), state, flags)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-nonfinal"); } // Sequence locks pass. BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); { // Locktime passes 1 second later. TxValidationState state; int64_t nMedianTimePast = ::ChainActive().Tip()->GetMedianTimePast() + 1; BOOST_CHECK(ContextualCheckTransaction( params, CTransaction(tx), state, ::ChainActive().Tip()->nHeight + 1, nMedianTimePast, nMedianTimePast)); } // mempool-dependent transactions (not added) tx.vin[0].prevout = COutPoint(txid, 0); prevheights[0] = ::ChainActive().Tip()->nHeight + 1; tx.nLockTime = 0; tx.vin[0].nSequence = 0; { // Locktime passes. TxValidationState state; BOOST_CHECK(ContextualCheckTransactionForCurrentBlock( ::ChainActive().Tip(), params, CTransaction(tx), state, flags)); } // Sequence locks pass. BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); tx.vin[0].nSequence = 1; // Sequence locks fail. BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG; // Sequence locks pass. BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1; // Sequence locks fail. BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_CHECK(pblocktemplate); // None of the of the absolute height/time locked tx should have made it // into the template because we still check IsFinalTx in CreateNewBlock, but // relative locked txs will if inconsistently added to g_mempool. For now // these will still generate a valid template until BIP68 soft fork. BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3UL); // However if we advance height by 1 and time by 512, all of them should be // mined. for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) { // Trick the MedianTimePast. ::ChainActive() .Tip() ->GetAncestor(::ChainActive().Tip()->nHeight - i) ->nTime += 512; } ::ChainActive().Tip()->nHeight++; SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1); BOOST_CHECK(pblocktemplate = - AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + AssemblerForTest(chainparams) + .CreateNewBlock(::ChainstateActive(), scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5UL); ::ChainActive().Tip()->nHeight--; SetMockTime(0); m_node.mempool->clear(); TestPackageSelection(chainparams, scriptPubKey, txFirst); fCheckpointsEnabled = true; } void CheckBlockMaxSize(const Config &config, const CTxMemPool &mempool, uint64_t size, uint64_t expected) { gArgs.ForceSetArg("-blockmaxsize", ToString(size)); BlockAssembler ba(config, mempool); BOOST_CHECK_EQUAL(ba.GetMaxGeneratedBlockSize(), expected); } BOOST_AUTO_TEST_CASE(BlockAssembler_construction) { GlobalConfig config; // We are working on a fake chain and need to protect ourselves. LOCK(cs_main); // Test around historical 1MB (plus one byte because that's mandatory) config.SetMaxBlockSize(ONE_MEGABYTE + 1); CheckBlockMaxSize(config, *m_node.mempool, 0, 1000); CheckBlockMaxSize(config, *m_node.mempool, 1000, 1000); CheckBlockMaxSize(config, *m_node.mempool, 1001, 1001); CheckBlockMaxSize(config, *m_node.mempool, 12345, 12345); CheckBlockMaxSize(config, *m_node.mempool, ONE_MEGABYTE - 1001, ONE_MEGABYTE - 1001); CheckBlockMaxSize(config, *m_node.mempool, ONE_MEGABYTE - 1000, ONE_MEGABYTE - 1000); CheckBlockMaxSize(config, *m_node.mempool, ONE_MEGABYTE - 999, ONE_MEGABYTE - 999); CheckBlockMaxSize(config, *m_node.mempool, ONE_MEGABYTE, ONE_MEGABYTE - 999); // Test around default cap config.SetMaxBlockSize(DEFAULT_MAX_BLOCK_SIZE); // Now we can use the default max block size. CheckBlockMaxSize(config, *m_node.mempool, DEFAULT_MAX_BLOCK_SIZE - 1001, DEFAULT_MAX_BLOCK_SIZE - 1001); CheckBlockMaxSize(config, *m_node.mempool, DEFAULT_MAX_BLOCK_SIZE - 1000, DEFAULT_MAX_BLOCK_SIZE - 1000); CheckBlockMaxSize(config, *m_node.mempool, DEFAULT_MAX_BLOCK_SIZE - 999, DEFAULT_MAX_BLOCK_SIZE - 1000); CheckBlockMaxSize(config, *m_node.mempool, DEFAULT_MAX_BLOCK_SIZE, DEFAULT_MAX_BLOCK_SIZE - 1000); // If the parameter is not specified, we use // DEFAULT_MAX_GENERATED_BLOCK_SIZE { gArgs.ClearForcedArg("-blockmaxsize"); BlockAssembler ba(config, *m_node.mempool); BOOST_CHECK_EQUAL(ba.GetMaxGeneratedBlockSize(), DEFAULT_MAX_GENERATED_BLOCK_SIZE); } } BOOST_AUTO_TEST_CASE(TestCBlockTemplateEntry) { const CTransaction tx; CTransactionRef txRef = MakeTransactionRef(tx); CBlockTemplateEntry txEntry(txRef, 1 * SATOSHI, 10); BOOST_CHECK_MESSAGE(txEntry.tx == txRef, "Transactions did not match"); BOOST_CHECK_EQUAL(txEntry.fees, 1 * SATOSHI); BOOST_CHECK_EQUAL(txEntry.sigOpCount, 10); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 6ce0d7251..9d8b5f8e7 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -1,57 +1,57 @@ // Copyright (c) 2019 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 <test/util/mining.h> #include <chainparams.h> #include <config.h> #include <consensus/merkle.h> #include <key_io.h> #include <miner.h> #include <node/context.h> #include <pow/pow.h> #include <script/standard.h> #include <util/check.h> #include <validation.h> CTxIn generatetoaddress(const Config &config, const NodeContext &node, const std::string &address) { const auto dest = DecodeDestination(address, config.GetChainParams()); assert(IsValidDestination(dest)); const auto coinbase_script = GetScriptForDestination(dest); return MineBlock(config, node, coinbase_script); } CTxIn MineBlock(const Config &config, const NodeContext &node, const CScript &coinbase_scriptPubKey) { auto block = PrepareBlock(config, node, coinbase_scriptPubKey); while (!CheckProofOfWork(block->GetHash(), block->nBits, config.GetChainParams().GetConsensus())) { ++block->nNonce; assert(block->nNonce); } bool processed{ Assert(node.chainman)->ProcessNewBlock(config, block, true, nullptr)}; assert(processed); return CTxIn{block->vtx[0]->GetId(), 0}; } std::shared_ptr<CBlock> PrepareBlock(const Config &config, const NodeContext &node, const CScript &coinbase_scriptPubKey) { - auto block = - std::make_shared<CBlock>(BlockAssembler{config, *Assert(node.mempool)} - .CreateNewBlock(coinbase_scriptPubKey) - ->block); + auto block = std::make_shared<CBlock>( + BlockAssembler{config, *Assert(node.mempool)} + .CreateNewBlock(::ChainstateActive(), coinbase_scriptPubKey) + ->block); LOCK(cs_main); block->nTime = ::ChainActive().Tip()->GetMedianTimePast() + 1; block->hashMerkleRoot = BlockMerkleRoot(*block); return block; } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index be690a2bb..d96660ec3 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -1,416 +1,417 @@ // Copyright (c) 2011-2019 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 <test/util/setup_common.h> #include <banman.h> #include <chainparams.h> #include <config.h> #include <consensus/consensus.h> #include <consensus/validation.h> #include <crypto/sha256.h> #include <init.h> #include <interfaces/chain.h> #include <logging.h> #include <miner.h> #include <net.h> #include <net_processing.h> #include <noui.h> #include <pow/pow.h> #include <rpc/blockchain.h> #include <rpc/register.h> #include <rpc/server.h> #include <scheduler.h> #include <script/script_error.h> #include <script/scriptcache.h> #include <script/sigcache.h> #include <streams.h> #include <txdb.h> #include <txmempool.h> #include <util/strencodings.h> #include <util/time.h> #include <util/translation.h> #include <util/vector.h> #include <validation.h> #include <validationinterface.h> #include <walletinitinterface.h> #include <functional> #include <memory> const std::function<std::string(const char *)> G_TRANSLATION_FUN = nullptr; FastRandomContext g_insecure_rand_ctx; /** * Random context to get unique temp data dirs. Separate from * g_insecure_rand_ctx, which can be seeded from a const env var */ static FastRandomContext g_insecure_rand_ctx_temp_path; /** * Return the unsigned from the environment var if available, * otherwise 0 */ static uint256 GetUintFromEnv(const std::string &env_name) { const char *num = std::getenv(env_name.c_str()); if (!num) { return {}; } return uint256S(num); } void Seed(FastRandomContext &ctx) { // Should be enough to get the seed once for the process static uint256 seed{}; static const std::string RANDOM_CTX_SEED{"RANDOM_CTX_SEED"}; if (seed.IsNull()) { seed = GetUintFromEnv(RANDOM_CTX_SEED); } if (seed.IsNull()) { seed = GetRandHash(); } LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__, RANDOM_CTX_SEED, seed.GetHex()); ctx = FastRandomContext(seed); } std::ostream &operator<<(std::ostream &os, const uint256 &num) { os << num.ToString(); return os; } std::ostream &operator<<(std::ostream &os, const ScriptError &err) { os << ScriptErrorString(err); return os; } std::vector<const char *> fixture_extra_args{}; BasicTestingSetup::BasicTestingSetup( const std::string &chainName, const std::vector<const char *> &extra_args) : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()}, m_args{} { // clang-format off std::vector<const char *> arguments = Cat( { "dummy", "-printtoconsole=0", "-logsourcelocations", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", }, extra_args); // clang-format on arguments = Cat(arguments, fixture_extra_args); auto &config = const_cast<Config &>(GetConfig()); SetMockTime(0); fs::create_directories(m_path_root); m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root)); gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); gArgs.ClearPathCache(); { SetupServerArgs(m_node); std::string error; const bool success{m_node.args->ParseParameters( arguments.size(), arguments.data(), error)}; assert(success); assert(error.empty()); } SelectParams(chainName); SeedInsecureRand(); InitLogging(*m_node.args); AppInitParameterInteraction(config, *m_node.args); LogInstance().StartLogging(); SHA256AutoDetect(); ECC_Start(); SetupEnvironment(); SetupNetworking(); InitSignatureCache(); InitScriptExecutionCache(); m_node.chain = interfaces::MakeChain(m_node, config.GetChainParams()); g_wallet_init_interface.Construct(m_node); fCheckBlockIndex = true; static bool noui_connected = false; if (!noui_connected) { noui_connect(); noui_connected = true; } } BasicTestingSetup::~BasicTestingSetup() { LogInstance().DisconnectTestLogger(); fs::remove_all(m_path_root); gArgs.ClearArgs(); ECC_Stop(); } ChainTestingSetup::ChainTestingSetup( const std::string &chainName, const std::vector<const char *> &extra_args) : BasicTestingSetup(chainName, extra_args) { // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. m_node.scheduler = std::make_unique<CScheduler>(); m_node.scheduler->m_service_thread = std::thread([&] { TraceThread("scheduler", [&] { m_node.scheduler->serviceQueue(); }); }); GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); m_node.mempool = std::make_unique<CTxMemPool>(1); m_node.chainman = &::g_chainman; constexpr int script_check_threads = 2; StartScriptCheckWorkerThreads(script_check_threads); } ChainTestingSetup::~ChainTestingSetup() { if (m_node.scheduler) { m_node.scheduler->stop(); } StopScriptCheckWorkerThreads(); GetMainSignals().FlushBackgroundCallbacks(); GetMainSignals().UnregisterBackgroundSignalScheduler(); m_node.connman.reset(); m_node.banman.reset(); m_node.args = nullptr; UnloadBlockIndex(m_node.mempool.get(), *m_node.chainman); m_node.mempool.reset(); m_node.scheduler.reset(); m_node.chainman->Reset(); m_node.chainman = nullptr; pblocktree.reset(); } TestingSetup::TestingSetup(const std::string &chainName, const std::vector<const char *> &extra_args) : ChainTestingSetup(chainName, extra_args) { const Config &config = GetConfig(); const CChainParams &chainparams = config.GetChainParams(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. RPCServer rpcServer; RegisterAllRPCCommands(config, rpcServer, tableRPC); /** * RPC does not come out of the warmup state on its own. Normally, this is * handled in bitcoind's init path, but unit tests do not trigger this * codepath, so we call it explicitly as part of setup. */ std::string rpcWarmupStatus; if (RPCIsInWarmup(&rpcWarmupStatus)) { SetRPCWarmupFinished(); } m_node.chainman->InitializeChainstate(*m_node.mempool); ::ChainstateActive().InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); assert(!::ChainstateActive().CanFlushToDisk()); ::ChainstateActive().InitCoinsCache(1 << 23); assert(::ChainstateActive().CanFlushToDisk()); if (!::ChainstateActive().LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } { BlockValidationState state; if (!::ChainstateActive().ActivateBestChain(config, state)) { throw std::runtime_error( strprintf("ActivateBestChain failed. (%s)", state.ToString())); } } m_node.banman = std::make_unique<BanMan>( m_args.GetDataDirPath() / "banlist.dat", chainparams, nullptr, DEFAULT_MISBEHAVING_BANTIME); // Deterministic randomness for tests. m_node.connman = std::make_unique<CConnman>(config, 0x1337, 0x1337); m_node.peerman = PeerManager::make( chainparams, *m_node.connman, m_node.banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); { CConnman::Options options; options.m_msgproc = m_node.peerman.get(); m_node.connman->Init(options); } } TestChain100Setup::TestChain100Setup() { // Generate a 100-block chain: coinbaseKey.MakeNewKey(true); CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; for (int i = 0; i < COINBASE_MATURITY; i++) { std::vector<CMutableTransaction> noTxns; CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey); m_coinbase_txns.push_back(b.vtx[0]); } } CBlock TestChain100Setup::CreateAndProcessBlock( const std::vector<CMutableTransaction> &txns, const CScript &scriptPubKey) { const Config &config = GetConfig(); CTxMemPool empty_pool; - CBlock block = - BlockAssembler(config, empty_pool).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler(config, empty_pool) + .CreateNewBlock(::ChainstateActive(), scriptPubKey) + ->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction &tx : txns) { block.vtx.push_back(MakeTransactionRef(tx)); } // Order transactions by canonical order std::sort(std::begin(block.vtx) + 1, std::end(block.vtx), [](const std::shared_ptr<const CTransaction> &txa, const std::shared_ptr<const CTransaction> &txb) -> bool { return txa->GetId() < txb->GetId(); }); // IncrementExtraNonce creates a valid coinbase and merkleRoot { LOCK(cs_main); unsigned int extraNonce = 0; IncrementExtraNonce(&block, ::ChainActive().Tip(), config.GetMaxBlockSize(), extraNonce); } const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); while (!CheckProofOfWork(block.GetHash(), block.nBits, params)) { ++block.nNonce; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); Assert(m_node.chainman) ->ProcessNewBlock(config, shared_pblock, true, nullptr); return block; } TestChain100Setup::~TestChain100Setup() {} CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction &tx) const { return FromTx(MakeTransactionRef(tx)); } CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef &tx) const { return CTxMemPoolEntry(tx, nFee, nTime, nHeight, spendsCoinbase, nSigOpCount, LockPoints()); } /** * @returns a real block * (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) with 9 * txs. */ CBlock getBlock13b8a() { CBlock block; CDataStream stream( ParseHex( "0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb680000000" "0000005275289558f51c9966699404ae2294730c3c9f9bda53523ce50e9b95e558" "da2fdb261b4d4c86041b1ab1bf9309010000000100000000000000000000000000" "00000000000000000000000000000000000000ffffffff07044c86041b0146ffff" "ffff0100f2052a01000000434104e18f7afbe4721580e81e8414fc8c24d7cfacf2" "54bb5c7b949450c3e997c2dc1242487a8169507b631eb3771f2b425483fb13102c" "4eb5d858eef260fe70fbfae0ac00000000010000000196608ccbafa16abada9027" "80da4dc35dafd7af05fa0da08cf833575f8cf9e836000000004a493046022100da" "b24889213caf43ae6adc41cf1c9396c08240c199f5225acf45416330fd7dbd0221" "00fe37900e0644bf574493a07fc5edba06dbc07c311b947520c2d514bc5725dcb4" "01ffffffff0100f2052a010000001976a914f15d1921f52e4007b146dfa60f369e" "d2fc393ce288ac000000000100000001fb766c1288458c2bafcfec81e48b24d98e" "c706de6b8af7c4e3c29419bfacb56d000000008c493046022100f268ba165ce0ad" "2e6d93f089cfcd3785de5c963bb5ea6b8c1b23f1ce3e517b9f022100da7c0f21ad" "c6c401887f2bfd1922f11d76159cbc597fbd756a23dcbb00f4d7290141042b4e86" "25a96127826915a5b109852636ad0da753c9e1d5606a50480cd0c40f1f8b8d8982" "35e571fe9357d9ec842bc4bba1827daaf4de06d71844d0057707966affffffff02" "80969800000000001976a9146963907531db72d0ed1a0cfb471ccb63923446f388" "ac80d6e34c000000001976a914f0688ba1c0d1ce182c7af6741e02658c7d4dfcd3" "88ac000000000100000002c40297f730dd7b5a99567eb8d27b78758f607507c522" "92d02d4031895b52f2ff010000008b483045022100f7edfd4b0aac404e5bab4fd3" "889e0c6c41aa8d0e6fa122316f68eddd0a65013902205b09cc8b2d56e1cd1f7f2f" "afd60a129ed94504c4ac7bdc67b56fe67512658b3e014104732012cb962afa90d3" "1b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83aba" "f975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffffca5065ff9617cb" "cba45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefb000000008a473044" "022068010362a13c7f9919fa832b2dee4e788f61f6f5d344a7c2a0da6ae7406056" "58022006d1af525b9a14a35c003b78b72bd59738cd676f845d1ff3fc25049e0100" "3614014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c342" "3e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc" "2d68ecffffffff01001ec4110200000043410469ab4181eceb28985b9b4e895c13" "fa5e68d85761b7eee311db5addef76fa8621865134a221bd01f28ec9999ee3e021" "e60766e9d1f3458c115fb28650605f11c9ac000000000100000001cdaf2f758e91" "c514655e2dc50633d1e4c84989f8aa90a0dbc883f0d23ed5c2fa010000008b4830" "4502207ab51be6f12a1962ba0aaaf24a20e0b69b27a94fac5adf45aa7d2d18ffd9" "236102210086ae728b370e5329eead9accd880d0cb070aea0c96255fae6c4f1ddc" "ce1fd56e014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d78990" "4f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8" "ebbb12dcd4ffffffff02404b4c00000000001976a9142b6ba7c9d796b75eef7942" "fc9288edd37c32f5c388ac002d3101000000001976a9141befba0cdc1ad5652937" "1864d9f6cb042faa06b588ac000000000100000001b4a47603e71b61bc3326efd9" "0111bf02d2f549b067f4c4a8fa183b57a0f800cb010000008a4730440220177c37" "f9a505c3f1a1f0ce2da777c339bd8339ffa02c7cb41f0a5804f473c9230220585b" "25a2ee80eb59292e52b987dad92acb0c64eced92ed9ee105ad153cdb12d0014104" "43bd44f683467e549dae7d20d1d79cbdb6df985c6e9c029c8d0c6cb46cc1a4d3cf" "7923c5021b27f7a0b562ada113bc85d5fda5a1b41e87fe6e8802817cf69996ffff" "ffff0280651406000000001976a9145505614859643ab7b547cd7f1f5e7e2a1232" "2d3788ac00aa0271000000001976a914ea4720a7a52fc166c55ff2298e07baf70a" "e67e1b88ac00000000010000000586c62cd602d219bb60edb14a3e204de0705176" "f9022fe49a538054fb14abb49e010000008c493046022100f2bc2aba2534becbdf" "062eb993853a42bbbc282083d0daf9b4b585bd401aa8c9022100b1d7fd7ee0b956" "00db8535bbf331b19eed8d961f7a8e54159c53675d5f69df8c014104462e76fd40" "67b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c6" "9b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff03ad0e" "58ccdac3df9dc28a218bcf6f1997b0a93306faaa4b3a28ae83447b217901000000" "8b483045022100be12b2937179da88599e27bb31c3525097a07cdb52422d165b3c" "a2f2020ffcf702200971b51f853a53d644ebae9ec8f3512e442b1bcb6c315a5b49" "1d119d10624c83014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33" "d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312e" "f1c0e8ebbb12dcd4ffffffff2acfcab629bbc8685792603762c921580030ba144a" "f553d271716a95089e107b010000008b483045022100fa579a840ac258871365dd" "48cd7552f96c8eea69bd00d84f05b283a0dab311e102207e3c0ee9234814cfbb1b" "659b83671618f45abc1326b9edcc77d552a4f2a805c0014104462e76fd4067b3a0" "aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc3" "1895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffdcdc6023bbc9" "944a658ddc588e61eacb737ddf0a3cd24f113b5a8634c517fcd2000000008b4830" "450221008d6df731df5d32267954bd7d2dda2302b74c6c2a6aa5c0ca64ecbabc1a" "f03c75022010e55c571d65da7701ae2da1956c442df81bbf076cdbac25133f99d9" "8a9ed34c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d78990" "4f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8" "ebbb12dcd4ffffffffe15557cd5ce258f479dfd6dc6514edf6d7ed5b21fcfa4a03" "8fd69f06b83ac76e010000008b483045022023b3e0ab071eb11de2eb1cc3a67261" "b866f86bf6867d4558165f7c8c8aca2d86022100dc6e1f53a91de3efe8f6351285" "0811f26284b62f850c70ca73ed5de8771fb451014104462e76fd4067b3a0aa4207" "0082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0" "c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff01404b4c0000000000" "1976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000010000" "000166d7577163c932b4f9690ca6a80b6e4eb001f0a2fa9023df5595602aae96ed" "8d000000008a4730440220262b42546302dfb654a229cefc86432b89628ff259dc" "87edd1154535b16a67e102207b4634c020a97c3e7bbd0d4d19da6aa2269ad9dded" "4026e896b213d73ca4b63f014104979b82d02226b3a4597523845754d44f13639e" "3bf2df5e82c6aab2bdc79687368b01b1ab8b19875ae3c90d661a3d0a33161dab29" "934edeb36aa01976be3baf8affffffff02404b4c00000000001976a9144854e695" "a02af0aeacb823ccbc272134561e0a1688ac40420f00000000001976a914abee93" "376d6b37b5c2940655a6fcaf1c8e74237988ac0000000001000000014e3f8ef2e9" "1349a9059cb4f01e54ab2597c1387161d3da89919f7ea6acdbb371010000008c49" "304602210081f3183471a5ca22307c0800226f3ef9c353069e0773ac76bb580654" "d56aa523022100d4c56465bdc069060846f4fbf2f6b20520b2a80b08b168b31e66" "ddb9c694e240014104976c79848e18251612f8940875b2b08d06e6dc73b9840e88" "60c066b7e87432c477e9a59a453e71e6d76d5fe34058b800a098fc1740ce3012e8" "fc8a00c96af966ffffffff02c0e1e400000000001976a9144134e75a6fcb604203" "4aab5e18570cf1f844f54788ac404b4c00000000001976a9142b6ba7c9d796b75e" "ef7942fc9288edd37c32f5c388ac00000000"), SER_NETWORK, PROTOCOL_VERSION); stream >> block; return block; } diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 05bfd6264..6ccb2e39b 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -1,389 +1,389 @@ // Copyright (c) 2018-2019 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 <chain.h> #include <chainparams.h> #include <config.h> #include <consensus/merkle.h> #include <consensus/validation.h> #include <miner.h> #include <pow/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> namespace validation_block_tests { struct MinerTestingSetup : public RegTestingSetup { std::shared_ptr<CBlock> Block(const Config &config, const BlockHash &prev_hash); std::shared_ptr<const CBlock> GoodBlock(const Config &config, const BlockHash &prev_hash); std::shared_ptr<const CBlock> BadBlock(const Config &config, const BlockHash &prev_hash); std::shared_ptr<CBlock> FinalizeBlock(const Consensus::Params ¶ms, std::shared_ptr<CBlock> pblock); void BuildChain(const Config &config, const BlockHash &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_tests BOOST_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 Config &config, const BlockHash &prev_hash) { static int i = 0; static uint64_t time = config.GetChainParams().GenesisBlock().nTime; CScript pubKey; pubKey << i++ << OP_TRUE; - auto ptemplate = - BlockAssembler(config, *m_node.mempool).CreateNewBlock(pubKey); + auto ptemplate = BlockAssembler(config, *m_node.mempool) + .CreateNewBlock(::ChainstateActive(), pubKey); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; pubKey.clear(); { pubKey << OP_HASH160 << ToByteVector(CScriptID(CScript() << OP_TRUE)) << OP_EQUAL; } // 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 P2SH with OP_TRUE as scriptPubKey to make it easy to // spend CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.vout.resize(2); txCoinbase.vout[1].scriptPubKey = pubKey; txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue; txCoinbase.vout[0].nValue = Amount::zero(); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); return pblock; } std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(const Consensus::Params ¶ms, std::shared_ptr<CBlock> pblock) { pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, params)) { ++(pblock->nNonce); } return pblock; } // construct a valid block std::shared_ptr<const CBlock> MinerTestingSetup::GoodBlock(const Config &config, const BlockHash &prev_hash) { return FinalizeBlock(config.GetChainParams().GetConsensus(), Block(config, prev_hash)); } // construct an invalid block (but with a valid header) std::shared_ptr<const CBlock> MinerTestingSetup::BadBlock(const Config &config, const BlockHash &prev_hash) { auto pblock = Block(config, prev_hash); CMutableTransaction coinbase_spend; coinbase_spend.vin.push_back( CTxIn(COutPoint(pblock->vtx[0]->GetId(), 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(config.GetChainParams().GetConsensus(), pblock); return ret; } void MinerTestingSetup::BuildChain( const Config &config, const BlockHash &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(config, root) : GoodBlock(config, root); blocks.push_back(pblock); if (!gen_invalid) { BuildChain(config, pblock->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks); } if (gen_fork) { blocks.push_back(GoodBlock(config, root)); BuildChain(config, blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks); } } BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) { GlobalConfig config; const CChainParams &chainParams = config.GetChainParams(); // 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(config, chainParams.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(config, headers, state)); // Connect the genesis block and drain any outstanding events BOOST_CHECK(Assert(m_node.chainman) ->ProcessNewBlock( config, std::make_shared<CBlock>(chainParams.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 tlignored; FastRandomContext insecure; for (int j = 0; j < 1000; j++) { auto block = blocks[insecure.randrange(blocks.size() - 1)]; Assert(m_node.chainman) ->ProcessNewBlock(config, block, true, &tlignored); } // 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(config, block, true, &tlignored); 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) { GlobalConfig config; const CChainParams &chainParams = config.GetChainParams(); bool ignored; auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool { return Assert(m_node.chainman) ->ProcessNewBlock(config, block, /* fForceProcessing */ true, /* fNewBlock */ &ignored); }; // Process all mined blocks BOOST_REQUIRE( ProcessBlock(std::make_shared<CBlock>(chainParams.GenesisBlock()))); auto last_mined = GoodBlock(config, chainParams.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 BlockHash 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]->GetId(), 1), CScript() << ToByteVector(CScript() << OP_TRUE))); // Two outputs to make sure the transaction is larger than 100 bytes for (int i = 1; i < 3; ++i) { mtx.vout.emplace_back( CTxOut(50000 * SATOSHI, CScript() << OP_DUP << OP_HASH160 << ToByteVector(CScriptID(CScript() << i)) << OP_EQUALVERIFY << OP_CHECKSIG)); } txs.push_back(MakeTransactionRef(mtx)); last_mined = GoodBlock(config, 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(config, last_mined->GetHash()); BOOST_REQUIRE(ProcessBlock(last_mined)); } // Mine a reorg (and hold it back) before adding the txs to the mempool const BlockHash tip_init{last_mined->GetHash()}; std::vector<std::shared_ptr<const CBlock>> reorg; last_mined = GoodBlock(config, split_hash); reorg.push_back(last_mined); for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) { last_mined = GoodBlock(config, last_mined->GetHash()); reorg.push_back(last_mined); } // Add the txs to the tx pool { LOCK(cs_main); TxValidationState state; for (const auto &tx : txs) { BOOST_REQUIRE_MESSAGE( AcceptToMemoryPool(::ChainstateActive(), config, *m_node.mempool, state, tx, /* bypass_limits */ false), state.GetRejectReason()); } } // 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()); }}; // Make sure we disable reorg protection. gArgs.ForceSetArg("-parkdeepreorg", "false"); // 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_SUITE_END()