mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-06-03 16:02:34 +02:00
Merge bitcoin/bitcoin#23381: validation/refactor: refactoring for package submission
14cd7bf793
[test] call CheckPackage for package sanitization checks (glozow)6876378365
MOVEONLY: move package unit tests to their own file (glozow)c9b1439ca9
MOVEONLY: mempool checks to their own functions (glozow)9e910d8152
scripted-diff: clean up MemPoolAccept aliases (glozow)fd92b0c398
document workspace members (glozow)3d3e4598b6
[validation] cache iterators to mempool conflicts (glozow)36a8441912
[validation/rpc] cache + use vsize calculated in PreChecks (glozow)8fa2936b34
[validation] re-introduce bool for whether a transaction is RBF (glozow)cbb3598b5c
[validation/refactor] store precomputed txdata in workspace (glozow)0a79eaba72
[validation] case-based constructors for ATMPArgs (glozow) Pull request description: This contains the refactors and moves within #22674. There are no behavior changes, so it should be simpler to review. ACKs for top commit: ariard: Code Review ACK14cd7bf
jnewbery: Code review ACK14cd7bf793
laanwj: Code review ACK14cd7bf793
, thanks for adding documentation and clarifying the code t-bast: Code Review ACK14cd7bf793
Tree-SHA512: 580ed48b43713a3f9d81cd9b573ef6ac44efe5df2fc7b7b7036c232b52952b04bf5ea92940cf73739f4fbd54ecf980cef58032e8a2efe05229ad0b3c639de8a0
This commit is contained in:
commit
e70fb87a4f
@ -139,6 +139,7 @@ BITCOIN_TESTS =\
|
||||
test/transaction_tests.cpp \
|
||||
test/txindex_tests.cpp \
|
||||
test/txrequest_tests.cpp \
|
||||
test/txpackage_tests.cpp \
|
||||
test/txvalidation_tests.cpp \
|
||||
test/txvalidationcache_tests.cpp \
|
||||
test/uint256_tests.cpp \
|
||||
|
@ -977,7 +977,7 @@ static RPCHelpMan testmempoolaccept()
|
||||
if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
|
||||
const CAmount fee = tx_result.m_base_fees.value();
|
||||
// Check that fee does not exceed maximum fee
|
||||
const int64_t virtual_size = GetVirtualTransactionSize(*tx);
|
||||
const int64_t virtual_size = tx_result.m_vsize.value();
|
||||
const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
|
||||
if (max_raw_tx_fee && fee > max_raw_tx_fee) {
|
||||
result_inner.pushKV("allowed", false);
|
||||
|
117
src/test/txpackage_tests.cpp
Normal file
117
src/test/txpackage_tests.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2021 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 <consensus/validation.h>
|
||||
#include <key_io.h>
|
||||
#include <policy/packages.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/script.h>
|
||||
#include <script/standard.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(txpackage_tests)
|
||||
|
||||
// Create placeholder transactions that have no meaning.
|
||||
inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outputs)
|
||||
{
|
||||
CMutableTransaction mtx = CMutableTransaction();
|
||||
mtx.vin.resize(num_inputs);
|
||||
mtx.vout.resize(num_outputs);
|
||||
auto random_script = CScript() << ToByteVector(InsecureRand256()) << ToByteVector(InsecureRand256());
|
||||
for (size_t i{0}; i < num_inputs; ++i) {
|
||||
mtx.vin[i].prevout.hash = InsecureRand256();
|
||||
mtx.vin[i].prevout.n = 0;
|
||||
mtx.vin[i].scriptSig = random_script;
|
||||
}
|
||||
for (size_t o{0}; o < num_outputs; ++o) {
|
||||
mtx.vout[o].nValue = 1 * CENT;
|
||||
mtx.vout[o].scriptPubKey = random_script;
|
||||
}
|
||||
return MakeTransactionRef(mtx);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100Setup)
|
||||
{
|
||||
// Packages can't have more than 25 transactions.
|
||||
Package package_too_many;
|
||||
package_too_many.reserve(MAX_PACKAGE_COUNT + 1);
|
||||
for (size_t i{0}; i < MAX_PACKAGE_COUNT + 1; ++i) {
|
||||
package_too_many.emplace_back(create_placeholder_tx(1, 1));
|
||||
}
|
||||
PackageValidationState state_too_many;
|
||||
BOOST_CHECK(!CheckPackage(package_too_many, state_too_many));
|
||||
BOOST_CHECK_EQUAL(state_too_many.GetResult(), PackageValidationResult::PCKG_POLICY);
|
||||
BOOST_CHECK_EQUAL(state_too_many.GetRejectReason(), "package-too-many-transactions");
|
||||
|
||||
// Packages can't have a total size of more than 101KvB.
|
||||
CTransactionRef large_ptx = create_placeholder_tx(150, 150);
|
||||
Package package_too_large;
|
||||
auto size_large = GetVirtualTransactionSize(*large_ptx);
|
||||
size_t total_size{0};
|
||||
while (total_size <= MAX_PACKAGE_SIZE * 1000) {
|
||||
package_too_large.push_back(large_ptx);
|
||||
total_size += size_large;
|
||||
}
|
||||
BOOST_CHECK(package_too_large.size() <= MAX_PACKAGE_COUNT);
|
||||
PackageValidationState state_too_large;
|
||||
BOOST_CHECK(!CheckPackage(package_too_large, state_too_large));
|
||||
BOOST_CHECK_EQUAL(state_too_large.GetResult(), PackageValidationResult::PCKG_POLICY);
|
||||
BOOST_CHECK_EQUAL(state_too_large.GetRejectReason(), "package-too-large");
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
unsigned int initialPoolSize = m_node.mempool->size();
|
||||
|
||||
// Parent and Child Package
|
||||
CKey parent_key;
|
||||
parent_key.MakeNewKey(true);
|
||||
CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
|
||||
auto mtx_parent = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[0], /* vout */ 0,
|
||||
/* input_height */ 0, /* input_signing_key */ coinbaseKey,
|
||||
/* output_destination */ parent_locking_script,
|
||||
/* output_amount */ CAmount(49 * COIN), /* submit */ false);
|
||||
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
|
||||
|
||||
CKey child_key;
|
||||
child_key.MakeNewKey(true);
|
||||
CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
|
||||
auto mtx_child = CreateValidMempoolTransaction(/* input_transaction */ tx_parent, /* vout */ 0,
|
||||
/* input_height */ 101, /* input_signing_key */ parent_key,
|
||||
/* output_destination */ child_locking_script,
|
||||
/* output_amount */ CAmount(48 * COIN), /* submit */ false);
|
||||
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
|
||||
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /* test_accept */ true);
|
||||
BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(),
|
||||
"Package validation unexpectedly failed: " << result_parent_child.m_state.GetRejectReason());
|
||||
auto it_parent = result_parent_child.m_tx_results.find(tx_parent->GetWitnessHash());
|
||||
auto it_child = result_parent_child.m_tx_results.find(tx_child->GetWitnessHash());
|
||||
BOOST_CHECK(it_parent != result_parent_child.m_tx_results.end());
|
||||
BOOST_CHECK_MESSAGE(it_parent->second.m_state.IsValid(),
|
||||
"Package validation unexpectedly failed: " << it_parent->second.m_state.GetRejectReason());
|
||||
BOOST_CHECK(it_child != result_parent_child.m_tx_results.end());
|
||||
BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(),
|
||||
"Package validation unexpectedly failed: " << it_child->second.m_state.GetRejectReason());
|
||||
|
||||
|
||||
// A single, giant transaction submitted through ProcessNewPackage fails on single tx policy.
|
||||
CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
|
||||
BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > MAX_PACKAGE_SIZE * 1000);
|
||||
auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {giant_ptx}, /* test_accept */ true);
|
||||
BOOST_CHECK(result_single_large.m_state.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(result_single_large.m_state.GetResult(), PackageValidationResult::PCKG_TX);
|
||||
BOOST_CHECK_EQUAL(result_single_large.m_state.GetRejectReason(), "transaction failed");
|
||||
auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetWitnessHash());
|
||||
BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end());
|
||||
BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size");
|
||||
|
||||
// Check that mempool size hasn't changed.
|
||||
BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
|
||||
}
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
@ -50,99 +50,4 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
|
||||
BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");
|
||||
BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);
|
||||
}
|
||||
|
||||
// Create placeholder transactions that have no meaning.
|
||||
inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outputs)
|
||||
{
|
||||
CMutableTransaction mtx = CMutableTransaction();
|
||||
mtx.vin.resize(num_inputs);
|
||||
mtx.vout.resize(num_outputs);
|
||||
auto random_script = CScript() << ToByteVector(InsecureRand256()) << ToByteVector(InsecureRand256());
|
||||
for (size_t i{0}; i < num_inputs; ++i) {
|
||||
mtx.vin[i].prevout.hash = InsecureRand256();
|
||||
mtx.vin[i].prevout.n = 0;
|
||||
mtx.vin[i].scriptSig = random_script;
|
||||
}
|
||||
for (size_t o{0}; o < num_outputs; ++o) {
|
||||
mtx.vout[o].nValue = 1 * CENT;
|
||||
mtx.vout[o].scriptPubKey = random_script;
|
||||
}
|
||||
return MakeTransactionRef(mtx);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(package_tests, TestChain100Setup)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
unsigned int initialPoolSize = m_node.mempool->size();
|
||||
|
||||
// Parent and Child Package
|
||||
CKey parent_key;
|
||||
parent_key.MakeNewKey(true);
|
||||
CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
|
||||
auto mtx_parent = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[0], /* vout */ 0,
|
||||
/* input_height */ 0, /* input_signing_key */ coinbaseKey,
|
||||
/* output_destination */ parent_locking_script,
|
||||
/* output_amount */ CAmount(49 * COIN), /* submit */ false);
|
||||
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
|
||||
|
||||
CKey child_key;
|
||||
child_key.MakeNewKey(true);
|
||||
CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
|
||||
auto mtx_child = CreateValidMempoolTransaction(/* input_transaction */ tx_parent, /* vout */ 0,
|
||||
/* input_height */ 101, /* input_signing_key */ parent_key,
|
||||
/* output_destination */ child_locking_script,
|
||||
/* output_amount */ CAmount(48 * COIN), /* submit */ false);
|
||||
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
|
||||
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /* test_accept */ true);
|
||||
BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(),
|
||||
"Package validation unexpectedly failed: " << result_parent_child.m_state.GetRejectReason());
|
||||
auto it_parent = result_parent_child.m_tx_results.find(tx_parent->GetWitnessHash());
|
||||
auto it_child = result_parent_child.m_tx_results.find(tx_child->GetWitnessHash());
|
||||
BOOST_CHECK(it_parent != result_parent_child.m_tx_results.end());
|
||||
BOOST_CHECK_MESSAGE(it_parent->second.m_state.IsValid(),
|
||||
"Package validation unexpectedly failed: " << it_parent->second.m_state.GetRejectReason());
|
||||
BOOST_CHECK(it_child != result_parent_child.m_tx_results.end());
|
||||
BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(),
|
||||
"Package validation unexpectedly failed: " << it_child->second.m_state.GetRejectReason());
|
||||
|
||||
// Packages can't have more than 25 transactions.
|
||||
Package package_too_many;
|
||||
package_too_many.reserve(MAX_PACKAGE_COUNT + 1);
|
||||
for (size_t i{0}; i < MAX_PACKAGE_COUNT + 1; ++i) {
|
||||
package_too_many.emplace_back(create_placeholder_tx(1, 1));
|
||||
}
|
||||
auto result_too_many = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_too_many, /* test_accept */ true);
|
||||
BOOST_CHECK(result_too_many.m_state.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(result_too_many.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
|
||||
BOOST_CHECK_EQUAL(result_too_many.m_state.GetRejectReason(), "package-too-many-transactions");
|
||||
|
||||
// Packages can't have a total size of more than 101KvB.
|
||||
CTransactionRef large_ptx = create_placeholder_tx(150, 150);
|
||||
Package package_too_large;
|
||||
auto size_large = GetVirtualTransactionSize(*large_ptx);
|
||||
size_t total_size{0};
|
||||
while (total_size <= MAX_PACKAGE_SIZE * 1000) {
|
||||
package_too_large.push_back(large_ptx);
|
||||
total_size += size_large;
|
||||
}
|
||||
BOOST_CHECK(package_too_large.size() <= MAX_PACKAGE_COUNT);
|
||||
auto result_too_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_too_large, /* test_accept */ true);
|
||||
BOOST_CHECK(result_too_large.m_state.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(result_too_large.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
|
||||
BOOST_CHECK_EQUAL(result_too_large.m_state.GetRejectReason(), "package-too-large");
|
||||
|
||||
// A single, giant transaction submitted through ProcessNewPackage fails on single tx policy.
|
||||
CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
|
||||
BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > MAX_PACKAGE_SIZE * 1000);
|
||||
auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {giant_ptx}, /* test_accept */ true);
|
||||
BOOST_CHECK(result_single_large.m_state.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(result_single_large.m_state.GetResult(), PackageValidationResult::PCKG_TX);
|
||||
BOOST_CHECK_EQUAL(result_single_large.m_state.GetRejectReason(), "transaction failed");
|
||||
auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetWitnessHash());
|
||||
BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end());
|
||||
BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size");
|
||||
|
||||
// Check that mempool size hasn't changed.
|
||||
BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
|
||||
}
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -450,7 +450,36 @@ public:
|
||||
/** Whether we allow transactions to replace mempool transactions by BIP125 rules. If false,
|
||||
* any transaction spending the same inputs as a transaction in the mempool is considered
|
||||
* a conflict. */
|
||||
const bool m_allow_bip125_replacement{true};
|
||||
const bool m_allow_bip125_replacement;
|
||||
|
||||
/** Parameters for single transaction mempool validation. */
|
||||
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
|
||||
bool bypass_limits, std::vector<COutPoint>& coins_to_uncache,
|
||||
bool test_accept) {
|
||||
return ATMPArgs{/* m_chainparams */ chainparams,
|
||||
/* m_accept_time */ accept_time,
|
||||
/* m_bypass_limits */ bypass_limits,
|
||||
/* m_coins_to_uncache */ coins_to_uncache,
|
||||
/* m_test_accept */ test_accept,
|
||||
/* m_allow_bip125_replacement */ true,
|
||||
};
|
||||
}
|
||||
|
||||
/** Parameters for test package mempool validation through testmempoolaccept. */
|
||||
static ATMPArgs PackageTestAccept(const CChainParams& chainparams, int64_t accept_time,
|
||||
std::vector<COutPoint>& coins_to_uncache) {
|
||||
return ATMPArgs{/* m_chainparams */ chainparams,
|
||||
/* m_accept_time */ accept_time,
|
||||
/* m_bypass_limits */ false,
|
||||
/* m_coins_to_uncache */ coins_to_uncache,
|
||||
/* m_test_accept */ true,
|
||||
/* m_allow_bip125_replacement */ false,
|
||||
};
|
||||
}
|
||||
|
||||
// No default ctor to avoid exposing details to clients and allowing the possibility of
|
||||
// mixing up the order of the arguments. Use static functions above instead.
|
||||
ATMPArgs() = delete;
|
||||
};
|
||||
|
||||
// Single transaction acceptance
|
||||
@ -468,13 +497,29 @@ private:
|
||||
// of checking a given transaction.
|
||||
struct Workspace {
|
||||
explicit Workspace(const CTransactionRef& ptx) : m_ptx(ptx), m_hash(ptx->GetHash()) {}
|
||||
/** Txids of mempool transactions that this transaction directly conflicts with. */
|
||||
std::set<uint256> m_conflicts;
|
||||
/** Iterators to mempool entries that this transaction directly conflicts with. */
|
||||
CTxMemPool::setEntries m_iters_conflicting;
|
||||
/** Iterators to all mempool entries that would be replaced by this transaction, including
|
||||
* those it directly conflicts with and their descendants. */
|
||||
CTxMemPool::setEntries m_all_conflicting;
|
||||
/** All mempool ancestors of this transaction. */
|
||||
CTxMemPool::setEntries m_ancestors;
|
||||
/** Mempool entry constructed for this transaction. Constructed in PreChecks() but not
|
||||
* inserted into the mempool until Finalize(). */
|
||||
std::unique_ptr<CTxMemPoolEntry> m_entry;
|
||||
/** Pointers to the transactions that have been removed from the mempool and replaced by
|
||||
* this transaction, used to return to the MemPoolAccept caller. Only populated if
|
||||
* validation is successful and the original transactions are removed. */
|
||||
std::list<CTransactionRef> m_replaced_transactions;
|
||||
|
||||
/** Virtual size of the transaction as used by the mempool, calculated using serialized size
|
||||
* of the transaction and sigops. */
|
||||
int64_t m_vsize;
|
||||
/** Fees paid by this transaction: total input amounts subtracted by total output amounts. */
|
||||
CAmount m_base_fees;
|
||||
/** Base fees + any fee delta set by the user with prioritisetransaction. */
|
||||
CAmount m_modified_fees;
|
||||
/** Total modified fees of all transactions being replaced. */
|
||||
CAmount m_conflicting_fees{0};
|
||||
@ -482,8 +527,12 @@ private:
|
||||
size_t m_conflicting_size{0};
|
||||
|
||||
const CTransactionRef& m_ptx;
|
||||
/** Txid. */
|
||||
const uint256& m_hash;
|
||||
TxValidationState m_state;
|
||||
/** A temporary cache containing serialized transaction data for signature verification.
|
||||
* Reused across PolicyScriptChecks and ConsensusScriptChecks. */
|
||||
PrecomputedTransactionData m_precomputed_txdata;
|
||||
};
|
||||
|
||||
// Run the policy checks on a given transaction, excluding any script checks.
|
||||
@ -492,15 +541,23 @@ private:
|
||||
// only tests that are fast should be done here (to avoid CPU DoS).
|
||||
bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Run checks for mempool replace-by-fee.
|
||||
bool ReplacementChecks(Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Enforce package mempool ancestor/descendant limits (distinct from individual
|
||||
// ancestor/descendant limits done in PreChecks).
|
||||
bool PackageMempoolChecks(const std::vector<CTransactionRef>& txns,
|
||||
PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Run the script checks using our policy flags. As this can be slow, we should
|
||||
// only invoke this on transactions that have otherwise passed policy checks.
|
||||
bool PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
bool PolicyScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Re-run the script checks, using consensus flags, and try to cache the
|
||||
// result in the scriptcache. This should be done after
|
||||
// PolicyScriptChecks(). This requires that all inputs either be in our
|
||||
// utxo set or in the mempool.
|
||||
bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Try to add the transaction to the mempool, removing any conflicts first.
|
||||
// Returns true if the transaction is in the mempool after any size
|
||||
@ -536,6 +593,9 @@ private:
|
||||
// in-mempool conflicts; see below).
|
||||
size_t m_limit_descendants;
|
||||
size_t m_limit_descendant_size;
|
||||
|
||||
/** Whether the transaction(s) would replace any mempool transactions. If so, RBF rules apply. */
|
||||
bool m_rbf{false};
|
||||
};
|
||||
|
||||
bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
@ -551,13 +611,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
// Alias what we need out of ws
|
||||
TxValidationState& state = ws.m_state;
|
||||
std::set<uint256>& setConflicts = ws.m_conflicts;
|
||||
CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting;
|
||||
CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
|
||||
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
|
||||
CAmount& nModifiedFees = ws.m_modified_fees;
|
||||
CAmount& nConflictingFees = ws.m_conflicting_fees;
|
||||
size_t& nConflictingSize = ws.m_conflicting_size;
|
||||
|
||||
if (!CheckTransaction(tx, state)) {
|
||||
return false; // state filled in by CheckTransaction
|
||||
@ -603,7 +657,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
// Transaction conflicts with a mempool tx, but we're not allowing replacements.
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
|
||||
}
|
||||
if (!setConflicts.count(ptxConflicting->GetHash()))
|
||||
if (!ws.m_conflicts.count(ptxConflicting->GetHash()))
|
||||
{
|
||||
// Transactions that don't explicitly signal replaceability are
|
||||
// *not* replaceable with the current logic, even if one of their
|
||||
@ -616,7 +670,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
|
||||
}
|
||||
|
||||
setConflicts.insert(ptxConflicting->GetHash());
|
||||
ws.m_conflicts.insert(ptxConflicting->GetHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -680,9 +734,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
// nModifiedFees includes any fee deltas from PrioritiseTransaction
|
||||
nModifiedFees = ws.m_base_fees;
|
||||
m_pool.ApplyDelta(hash, nModifiedFees);
|
||||
// ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
|
||||
ws.m_modified_fees = ws.m_base_fees;
|
||||
m_pool.ApplyDelta(hash, ws.m_modified_fees);
|
||||
|
||||
// Keep track of transactions that spend a coinbase, which we re-scan
|
||||
// during reorgs to ensure COINBASE_MATURITY is still met.
|
||||
@ -697,7 +751,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(),
|
||||
fSpendsCoinbase, nSigOpsCost, lp));
|
||||
unsigned int nSize = entry->GetTxSize();
|
||||
ws.m_vsize = entry->GetTxSize();
|
||||
|
||||
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
|
||||
@ -705,11 +759,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
// No transactions are allowed below minRelayTxFee except from disconnected
|
||||
// blocks
|
||||
if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false;
|
||||
if (!bypass_limits && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
|
||||
|
||||
const CTxMemPool::setEntries setIterConflicting = m_pool.GetIterSet(setConflicts);
|
||||
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
|
||||
// Calculate in-mempool ancestors, up to a limit.
|
||||
if (setConflicts.size() == 1) {
|
||||
if (ws.m_conflicts.size() == 1) {
|
||||
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
|
||||
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
|
||||
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
|
||||
@ -737,16 +791,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
// the ancestor limits should be the same for both our new transaction and any conflicts).
|
||||
// We don't bother incrementing m_limit_descendants by the full removal count as that limit never comes
|
||||
// into force here (as we're only adding a single transaction).
|
||||
assert(setIterConflicting.size() == 1);
|
||||
CTxMemPool::txiter conflict = *setIterConflicting.begin();
|
||||
assert(ws.m_iters_conflicting.size() == 1);
|
||||
CTxMemPool::txiter conflict = *ws.m_iters_conflicting.begin();
|
||||
|
||||
m_limit_descendants += 1;
|
||||
m_limit_descendant_size += conflict->GetSizeWithDescendants();
|
||||
}
|
||||
|
||||
std::string errString;
|
||||
if (!m_pool.CalculateMemPoolAncestors(*entry, setAncestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) {
|
||||
setAncestors.clear();
|
||||
if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) {
|
||||
ws.m_ancestors.clear();
|
||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||
std::string dummy_err_string;
|
||||
// Contracting/payment channels CPFP carve-out:
|
||||
@ -760,60 +814,85 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
// to be secure by simply only having two immediately-spendable
|
||||
// outputs - one for each counterparty. For more info on the uses for
|
||||
// this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
|
||||
if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
|
||||
!m_pool.CalculateMemPoolAncestors(*entry, setAncestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
|
||||
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
|
||||
!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", errString);
|
||||
}
|
||||
}
|
||||
|
||||
// A transaction that spends outputs that would be replaced by it is invalid. Now
|
||||
// that we have the set of all ancestors we can detect this
|
||||
// pathological case by making sure setConflicts and setAncestors don't
|
||||
// pathological case by making sure ws.m_conflicts and ws.m_ancestors don't
|
||||
// intersect.
|
||||
if (const auto err_string{EntriesAndTxidsDisjoint(setAncestors, setConflicts, hash)}) {
|
||||
if (const auto err_string{EntriesAndTxidsDisjoint(ws.m_ancestors, ws.m_conflicts, hash)}) {
|
||||
// We classify this as a consensus error because a transaction depending on something it
|
||||
// conflicts with would be inconsistent.
|
||||
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", *err_string);
|
||||
}
|
||||
|
||||
m_rbf = !ws.m_conflicts.empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!setConflicts.empty()) {
|
||||
CFeeRate newFeeRate(nModifiedFees, nSize);
|
||||
// It's possible that the replacement pays more fees than its direct conflicts but not more
|
||||
// than all conflicts (i.e. the direct conflicts have high-fee descendants). However, if the
|
||||
// replacement doesn't pay more fees than its direct conflicts, then we can be sure it's not
|
||||
// more economically rational to mine. Before we go digging through the mempool for all
|
||||
// transactions that would need to be removed (direct conflicts and all descendants), check
|
||||
// that the replacement transaction pays more than its direct conflicts.
|
||||
if (const auto err_string{PaysMoreThanConflicts(setIterConflicting, newFeeRate, hash)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
|
||||
}
|
||||
bool MemPoolAccept::ReplacementChecks(Workspace& ws)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(m_pool.cs);
|
||||
|
||||
// Calculate all conflicting entries and enforce BIP125 Rule #5.
|
||||
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, setIterConflicting, allConflicting)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
"too many potential replacements", *err_string);
|
||||
}
|
||||
// Enforce BIP125 Rule #2.
|
||||
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, setIterConflicting)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
"replacement-adds-unconfirmed", *err_string);
|
||||
}
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
TxValidationState& state = ws.m_state;
|
||||
|
||||
// Check if it's economically rational to mine this transaction rather than the ones it
|
||||
// replaces and pays for its own relay fees. Enforce BIP125 Rules #3 and #4.
|
||||
for (CTxMemPool::txiter it : allConflicting) {
|
||||
nConflictingFees += it->GetModifiedFee();
|
||||
nConflictingSize += it->GetTxSize();
|
||||
}
|
||||
if (const auto err_string{PaysForRBF(nConflictingFees, nModifiedFees, nSize, ::incrementalRelayFee, hash)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
|
||||
}
|
||||
CFeeRate newFeeRate(ws.m_modified_fees, ws.m_vsize);
|
||||
// It's possible that the replacement pays more fees than its direct conflicts but not more
|
||||
// than all conflicts (i.e. the direct conflicts have high-fee descendants). However, if the
|
||||
// replacement doesn't pay more fees than its direct conflicts, then we can be sure it's not
|
||||
// more economically rational to mine. Before we go digging through the mempool for all
|
||||
// transactions that would need to be removed (direct conflicts and all descendants), check
|
||||
// that the replacement transaction pays more than its direct conflicts.
|
||||
if (const auto err_string{PaysMoreThanConflicts(ws.m_iters_conflicting, newFeeRate, hash)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
|
||||
}
|
||||
|
||||
// Calculate all conflicting entries and enforce BIP125 Rule #5.
|
||||
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, ws.m_all_conflicting)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
"too many potential replacements", *err_string);
|
||||
}
|
||||
// Enforce BIP125 Rule #2.
|
||||
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, ws.m_iters_conflicting)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
"replacement-adds-unconfirmed", *err_string);
|
||||
}
|
||||
// Check if it's economically rational to mine this transaction rather than the ones it
|
||||
// replaces and pays for its own relay fees. Enforce BIP125 Rules #3 and #4.
|
||||
for (CTxMemPool::txiter it : ws.m_all_conflicting) {
|
||||
ws.m_conflicting_fees += it->GetModifiedFee();
|
||||
ws.m_conflicting_size += it->GetTxSize();
|
||||
}
|
||||
if (const auto err_string{PaysForRBF(ws.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize,
|
||||
::incrementalRelayFee, hash)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txns,
|
||||
PackageValidationState& package_state)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(m_pool.cs);
|
||||
|
||||
std::string err_string;
|
||||
if (!m_pool.CheckPackageLimits(txns, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants,
|
||||
m_limit_descendant_size, err_string)) {
|
||||
// This is a package-wide error, separate from an individual transaction error.
|
||||
return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
TxValidationState& state = ws.m_state;
|
||||
@ -822,13 +901,13 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, Prec
|
||||
|
||||
// Check input scripts and signatures.
|
||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, txdata)) {
|
||||
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata)) {
|
||||
// SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
|
||||
// need to turn both off, and compare against just turning off CLEANSTACK
|
||||
// to see if the failure is specifically due to witness validation.
|
||||
TxValidationState state_dummy; // Want reported failures to be from first CheckInputScripts
|
||||
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
|
||||
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
|
||||
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata) &&
|
||||
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata)) {
|
||||
// Only the witness is missing, so the transaction itself may be fine.
|
||||
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
|
||||
state.GetRejectReason(), state.GetDebugMessage());
|
||||
@ -839,7 +918,7 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, Prec
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
@ -862,7 +941,8 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, P
|
||||
// invalid blocks (using TestBlockValidity), however allowing such
|
||||
// transactions into the mempool can be exploited as a DoS attack.
|
||||
unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus());
|
||||
if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, txdata, m_active_chainstate.CoinsTip())) {
|
||||
if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags,
|
||||
ws.m_precomputed_txdata, m_active_chainstate.CoinsTip())) {
|
||||
return error("%s: BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s",
|
||||
__func__, hash.ToString(), state.ToString());
|
||||
}
|
||||
@ -877,24 +957,19 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
|
||||
TxValidationState& state = ws.m_state;
|
||||
const bool bypass_limits = args.m_bypass_limits;
|
||||
|
||||
CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting;
|
||||
CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
|
||||
const CAmount& nModifiedFees = ws.m_modified_fees;
|
||||
const CAmount& nConflictingFees = ws.m_conflicting_fees;
|
||||
const size_t& nConflictingSize = ws.m_conflicting_size;
|
||||
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
|
||||
|
||||
// Remove conflicting transactions from the mempool
|
||||
for (CTxMemPool::txiter it : allConflicting)
|
||||
for (CTxMemPool::txiter it : ws.m_all_conflicting)
|
||||
{
|
||||
LogPrint(BCLog::MEMPOOL, "replacing tx %s with %s for %s additional fees, %d delta bytes\n",
|
||||
it->GetTx().GetHash().ToString(),
|
||||
hash.ToString(),
|
||||
FormatMoney(nModifiedFees - nConflictingFees),
|
||||
(int)entry->GetTxSize() - (int)nConflictingSize);
|
||||
FormatMoney(ws.m_modified_fees - ws.m_conflicting_fees),
|
||||
(int)entry->GetTxSize() - (int)ws.m_conflicting_size);
|
||||
ws.m_replaced_transactions.push_back(it->GetSharedTx());
|
||||
}
|
||||
m_pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
|
||||
m_pool.RemoveStaged(ws.m_all_conflicting, false, MemPoolRemovalReason::REPLACED);
|
||||
|
||||
// This transaction should only count for fee estimation if:
|
||||
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
|
||||
@ -903,7 +978,7 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
|
||||
bool validForFeeEstimation = !bypass_limits && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx);
|
||||
|
||||
// Store transaction in memory
|
||||
m_pool.addUnchecked(*entry, setAncestors, validForFeeEstimation);
|
||||
m_pool.addUnchecked(*entry, ws.m_ancestors, validForFeeEstimation);
|
||||
|
||||
// trim mempool and check if tx was trimmed
|
||||
if (!bypass_limits) {
|
||||
@ -923,26 +998,24 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
|
||||
|
||||
if (!PreChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
// Only compute the precomputed transaction data if we need to verify
|
||||
// scripts (ie, other policy checks pass). We perform the inexpensive
|
||||
// checks first and avoid hashing and signature verification unless those
|
||||
// checks pass, to mitigate CPU exhaustion denial-of-service attacks.
|
||||
PrecomputedTransactionData txdata;
|
||||
if (m_rbf && !ReplacementChecks(ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
if (!PolicyScriptChecks(args, ws, txdata)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
// Perform the inexpensive checks first and avoid hashing and signature verification unless
|
||||
// those checks pass, to mitigate CPU exhaustion denial-of-service attacks.
|
||||
if (!PolicyScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
if (!ConsensusScriptChecks(args, ws, txdata)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
if (!ConsensusScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
// Tx was accepted, but not added
|
||||
if (args.m_test_accept) {
|
||||
return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees);
|
||||
return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_vsize, ws.m_base_fees);
|
||||
}
|
||||
|
||||
if (!Finalize(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
GetMainSignals().TransactionAddedToMempool(ptx, m_pool.GetAndIncrementSequence());
|
||||
|
||||
return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees);
|
||||
return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_vsize, ws.m_base_fees);
|
||||
}
|
||||
|
||||
PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args)
|
||||
@ -981,18 +1054,12 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
|
||||
// because it's unnecessary. Also, CPFP carve out can increase the limit for individual
|
||||
// transactions, but this exemption is not extended to packages in CheckPackageLimits().
|
||||
std::string err_string;
|
||||
if (txns.size() > 1 &&
|
||||
!m_pool.CheckPackageLimits(txns, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants,
|
||||
m_limit_descendant_size, err_string)) {
|
||||
// All transactions must have individually passed mempool ancestor and descendant limits
|
||||
// inside of PreChecks(), so this is separate from an individual transaction error.
|
||||
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string);
|
||||
if (txns.size() > 1 && !PackageMempoolChecks(txns, package_state)) {
|
||||
return PackageMempoolAcceptResult(package_state, std::move(results));
|
||||
}
|
||||
|
||||
for (Workspace& ws : workspaces) {
|
||||
PrecomputedTransactionData txdata;
|
||||
if (!PolicyScriptChecks(args, ws, txdata)) {
|
||||
if (!PolicyScriptChecks(args, ws)) {
|
||||
// Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished.
|
||||
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
|
||||
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
|
||||
@ -1002,7 +1069,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
|
||||
// When test_accept=true, transactions that pass PolicyScriptChecks are valid because there are
|
||||
// no further mempool checks (passing PolicyScriptChecks implies passing ConsensusScriptChecks).
|
||||
results.emplace(ws.m_ptx->GetWitnessHash(),
|
||||
MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees));
|
||||
MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions),
|
||||
ws.m_vsize, ws.m_base_fees));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1019,9 +1087,7 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp
|
||||
EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
{
|
||||
std::vector<COutPoint> coins_to_uncache;
|
||||
MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache,
|
||||
test_accept, /* m_allow_bip125_replacement */ true };
|
||||
|
||||
auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, nAcceptTime, bypass_limits, coins_to_uncache, test_accept);
|
||||
const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args);
|
||||
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
|
||||
// Remove coins that were not present in the coins cache before calling
|
||||
@ -1054,8 +1120,7 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
|
||||
|
||||
std::vector<COutPoint> coins_to_uncache;
|
||||
const CChainParams& chainparams = Params();
|
||||
MemPoolAccept::ATMPArgs args { chainparams, GetTime(), /* bypass_limits */ false, coins_to_uncache,
|
||||
test_accept, /* m_allow_bip125_replacement */ false };
|
||||
auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache);
|
||||
const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
|
||||
|
||||
// Uncache coins pertaining to transactions that were not submitted to the mempool.
|
||||
|
@ -158,14 +158,16 @@ struct MempoolAcceptResult {
|
||||
// The following fields are only present when m_result_type = ResultType::VALID
|
||||
/** Mempool transactions replaced by the tx per BIP 125 rules. */
|
||||
const std::optional<std::list<CTransactionRef>> m_replaced_transactions;
|
||||
/** Virtual size as used by the mempool, calculated using serialized size and sigops. */
|
||||
const std::optional<int64_t> m_vsize;
|
||||
/** Raw base fees in satoshis. */
|
||||
const std::optional<CAmount> m_base_fees;
|
||||
static MempoolAcceptResult Failure(TxValidationState state) {
|
||||
return MempoolAcceptResult(state);
|
||||
}
|
||||
|
||||
static MempoolAcceptResult Success(std::list<CTransactionRef>&& replaced_txns, CAmount fees) {
|
||||
return MempoolAcceptResult(std::move(replaced_txns), fees);
|
||||
static MempoolAcceptResult Success(std::list<CTransactionRef>&& replaced_txns, int64_t vsize, CAmount fees) {
|
||||
return MempoolAcceptResult(std::move(replaced_txns), vsize, fees);
|
||||
}
|
||||
|
||||
// Private constructors. Use static methods MempoolAcceptResult::Success, etc. to construct.
|
||||
@ -177,9 +179,9 @@ private:
|
||||
}
|
||||
|
||||
/** Constructor for success case */
|
||||
explicit MempoolAcceptResult(std::list<CTransactionRef>&& replaced_txns, CAmount fees)
|
||||
explicit MempoolAcceptResult(std::list<CTransactionRef>&& replaced_txns, int64_t vsize, CAmount fees)
|
||||
: m_result_type(ResultType::VALID),
|
||||
m_replaced_transactions(std::move(replaced_txns)), m_base_fees(fees) {}
|
||||
m_replaced_transactions(std::move(replaced_txns)), m_vsize{vsize}, m_base_fees(fees) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user