Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2017-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <consensus/validation.h>6#include <key_io.h>7#include <policy/packages.h>8#include <policy/policy.h>9#include <policy/ephemeral_policy.h>10#include <policy/truc_policy.h>11#include <primitives/transaction.h>12#include <random.h>13#include <script/script.h>14#include <test/util/common.h>15#include <test/util/setup_common.h>16#include <test/util/txmempool.h>17#include <validation.h>19#include <boost/test/unit_test.hpp>22BOOST_AUTO_TEST_SUITE(txvalidation_tests)24/**25* Ensure that the mempool won't accept coinbase transactions.26*/27BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)28{29CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;30CMutableTransaction coinbaseTx;32coinbaseTx.version = 1;33coinbaseTx.vin.resize(1);34coinbaseTx.vout.resize(1);35coinbaseTx.vin[0].scriptSig = CScript() << OP_11 << OP_EQUAL;36coinbaseTx.vout[0].nValue = 1 * CENT;37coinbaseTx.vout[0].scriptPubKey = scriptPubKey;39BOOST_CHECK(CTransaction(coinbaseTx).IsCoinBase());41LOCK(cs_main);43unsigned int initialPoolSize = m_node.mempool->size();44const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(coinbaseTx));46BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID);48// Check that the transaction hasn't been added to mempool.49BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);51// Check that the validation state reflects the unsuccessful attempt.52BOOST_CHECK(result.m_state.IsInvalid());53BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");54BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);55}57// Generate a number of random, nonexistent outpoints.58static inline std::vector<COutPoint> random_outpoints(size_t num_outpoints) {59std::vector<COutPoint> outpoints;60for (size_t i{0}; i < num_outpoints; ++i) {61outpoints.emplace_back(Txid::FromUint256(GetRandHash()), 0);62}63return outpoints;64}66static inline std::vector<CPubKey> random_keys(size_t num_keys) {67std::vector<CPubKey> keys;68keys.reserve(num_keys);69for (size_t i{0}; i < num_keys; ++i) {70CKey key;71key.MakeNewKey(true);72keys.emplace_back(key.GetPubKey());73}74return keys;75}77// Creates a placeholder tx (not valid) with 25 outputs. Specify the version and the inputs.78static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, int32_t version)79{80CMutableTransaction mtx = CMutableTransaction{};81mtx.version = version;82mtx.vin.resize(inputs.size());83mtx.vout.resize(25);84for (size_t i{0}; i < inputs.size(); ++i) {85mtx.vin[i].prevout = inputs[i];86}87for (auto i{0}; i < 25; ++i) {88mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;89mtx.vout[i].nValue = 10000;90}91return MakeTransactionRef(mtx);92}94static constexpr auto NUM_EPHEMERAL_TX_OUTPUTS = 3;95static constexpr auto EPHEMERAL_DUST_INDEX = NUM_EPHEMERAL_TX_OUTPUTS - 1;97// Same as make_tx but adds 2 normal outputs and 0-value dust to end of vout98static inline CTransactionRef make_ephemeral_tx(const std::vector<COutPoint>& inputs, int32_t version)99{100CMutableTransaction mtx = CMutableTransaction{};101mtx.version = version;102mtx.vin.resize(inputs.size());103for (size_t i{0}; i < inputs.size(); ++i) {104mtx.vin[i].prevout = inputs[i];105}106mtx.vout.resize(NUM_EPHEMERAL_TX_OUTPUTS);107for (auto i{0}; i < NUM_EPHEMERAL_TX_OUTPUTS; ++i) {108mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;109mtx.vout[i].nValue = (i == EPHEMERAL_DUST_INDEX) ? 0 : 10000;110}111return MakeTransactionRef(mtx);112}114BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)115{116CTxMemPool& pool = *Assert(m_node.mempool);117LOCK2(cs_main, pool.cs);118TestMemPoolEntryHelper entry;120TxValidationState child_state;121Wtxid child_wtxid;123// Arbitrary non-0 feerate for these tests124CFeeRate dustrelay(DUST_RELAY_TX_FEE);126// Basic transaction with dust127auto grandparent_tx_1 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);128const auto dust_txid = grandparent_tx_1->GetHash();130// Child transaction spending dust131auto dust_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}}, /*version=*/2);133// We first start with nothing "in the mempool", using package checks135// Trivial single transaction with no dust136BOOST_CHECK(CheckEphemeralSpends({dust_spend}, dustrelay, pool, child_state, child_wtxid));137BOOST_CHECK(child_state.IsValid());138BOOST_CHECK_EQUAL(child_wtxid, Wtxid());140// Now with dust, ok because the tx has no dusty parents141BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));142BOOST_CHECK(child_state.IsValid());143BOOST_CHECK_EQUAL(child_wtxid, Wtxid());145// Dust checks pass146BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, CFeeRate(0), pool, child_state, child_wtxid));147BOOST_CHECK(child_state.IsValid());148BOOST_CHECK_EQUAL(child_wtxid, Wtxid());149BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, dustrelay, pool, child_state, child_wtxid));150BOOST_CHECK(child_state.IsValid());151BOOST_CHECK_EQUAL(child_wtxid, Wtxid());153auto dust_non_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);155// Child spending non-dust only from parent should be disallowed even if dust otherwise spent156const auto dust_non_spend_wtxid{dust_non_spend->GetWitnessHash()};157BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend, dust_spend}, dustrelay, pool, child_state, child_wtxid));158BOOST_CHECK(!child_state.IsValid());159BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);160child_state = TxValidationState();161child_wtxid = Wtxid();163BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_spend, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));164BOOST_CHECK(!child_state.IsValid());165BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);166child_state = TxValidationState();167child_wtxid = Wtxid();169BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));170BOOST_CHECK(!child_state.IsValid());171BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);172child_state = TxValidationState();173child_wtxid = Wtxid();175auto grandparent_tx_2 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);176const auto dust_txid_2 = grandparent_tx_2->GetHash();178// Spend dust from one but not another is ok, as long as second grandparent has no child179BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend}, dustrelay, pool, child_state, child_wtxid));180BOOST_CHECK(child_state.IsValid());181BOOST_CHECK_EQUAL(child_wtxid, Wtxid());183auto dust_non_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);184// But if we spend from the parent, it must spend dust185BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));186BOOST_CHECK(!child_state.IsValid());187BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());188child_state = TxValidationState();189child_wtxid = Wtxid();191auto dust_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);192BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));193BOOST_CHECK(child_state.IsValid());194BOOST_CHECK_EQUAL(child_wtxid, Wtxid());196// Spending other outputs is also correct, as long as the dusty one is spent197const std::vector<COutPoint> all_outpoints{COutPoint(dust_txid, 0), COutPoint(dust_txid, 1), COutPoint(dust_txid, 2),198COutPoint(dust_txid_2, 0), COutPoint(dust_txid_2, 1), COutPoint(dust_txid_2, 2)};199auto dust_spend_all_outpoints = make_tx(all_outpoints, /*version=*/2);200BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_all_outpoints}, dustrelay, pool, child_state, child_wtxid));201BOOST_CHECK(child_state.IsValid());202BOOST_CHECK_EQUAL(child_wtxid, Wtxid());204// 2 grandparents with dust <- 1 dust-spending parent with dust <- child with no dust205auto parent_with_dust = make_ephemeral_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);206// Ok for parent to have dust207BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));208BOOST_CHECK(child_state.IsValid());209BOOST_CHECK_EQUAL(child_wtxid, Wtxid());210auto child_no_dust = make_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);211BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_no_dust}, dustrelay, pool, child_state, child_wtxid));212BOOST_CHECK(child_state.IsValid());213BOOST_CHECK_EQUAL(child_wtxid, Wtxid());215// 2 grandparents with dust <- 1 dust-spending parent with dust <- child with dust216auto child_with_dust = make_ephemeral_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);217BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_with_dust}, dustrelay, pool, child_state, child_wtxid));218BOOST_CHECK(child_state.IsValid());219BOOST_CHECK_EQUAL(child_wtxid, Wtxid());221// Tests with parents in mempool223// Nothing in mempool, this should pass for any transaction224BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));225BOOST_CHECK(child_state.IsValid());226BOOST_CHECK_EQUAL(child_wtxid, Wtxid());228// Add first grandparent to mempool and fetch entry229TryAddToMempool(pool, entry.FromTx(grandparent_tx_1));231// Ignores ancestors that aren't direct parents232BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));233BOOST_CHECK(child_state.IsValid());234BOOST_CHECK_EQUAL(child_wtxid, Wtxid());236// Valid spend of dust with grandparent in mempool237BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));238BOOST_CHECK(child_state.IsValid());239BOOST_CHECK_EQUAL(child_wtxid, Wtxid());241// Second grandparent in same package242BOOST_CHECK(CheckEphemeralSpends({parent_with_dust, grandparent_tx_2}, dustrelay, pool, child_state, child_wtxid));243BOOST_CHECK(child_state.IsValid());244BOOST_CHECK_EQUAL(child_wtxid, Wtxid());246// Order in package doesn't matter247BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));248BOOST_CHECK(child_state.IsValid());249BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.