Skip to content

Commit ef8f296

Browse files
committed
Merge bitcoin/bitcoin#22084: package testmempoolaccept followups
ee862d6 MOVEONLY: context-free package policies (glozow) 5cac95c disallow_mempool_conflicts -> allow_bip125_replacement and check earlier (glozow) e8ecc62 [refactor] comment/naming improvements (glozow) 7d91442 [rpc] reserve space in txns (glozow) 6c5f19d [package] static_assert max package size >= max tx size (glozow) Pull request description: various followups from #20833 ACKs for top commit: jnewbery: utACK ee862d6 ariard: Code Review ACK ee862d6 Tree-SHA512: 96ecb41f7bbced84d4253070f5274b7267607bfe4033e2bb0d2f55ec778cc41e811130b6321131e0418b5835894e510a4be8a0f822bc9d68d9224418359ac837
2 parents e87fbee + ee862d6 commit ef8f296

File tree

10 files changed

+120
-84
lines changed

10 files changed

+120
-84
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ libbitcoin_server_a_SOURCES = \
351351
node/ui_interface.cpp \
352352
noui.cpp \
353353
policy/fees.cpp \
354+
policy/packages.cpp \
354355
policy/rbf.cpp \
355356
policy/settings.cpp \
356357
pow.cpp \

src/policy/packages.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <consensus/validation.h>
6+
#include <policy/packages.h>
7+
#include <primitives/transaction.h>
8+
#include <uint256.h>
9+
#include <util/hasher.h>
10+
11+
#include <numeric>
12+
#include <unordered_set>
13+
14+
bool CheckPackage(const Package& txns, PackageValidationState& state)
15+
{
16+
const unsigned int package_count = txns.size();
17+
18+
if (package_count > MAX_PACKAGE_COUNT) {
19+
return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-many-transactions");
20+
}
21+
22+
const int64_t total_size = std::accumulate(txns.cbegin(), txns.cend(), 0,
23+
[](int64_t sum, const auto& tx) { return sum + GetVirtualTransactionSize(*tx); });
24+
// If the package only contains 1 tx, it's better to report the policy violation on individual tx size.
25+
if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) {
26+
return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-large");
27+
}
28+
29+
// Require the package to be sorted in order of dependency, i.e. parents appear before children.
30+
// An unsorted package will fail anyway on missing-inputs, but it's better to quit earlier and
31+
// fail on something less ambiguous (missing-inputs could also be an orphan or trying to
32+
// spend nonexistent coins).
33+
std::unordered_set<uint256, SaltedTxidHasher> later_txids;
34+
std::transform(txns.cbegin(), txns.cend(), std::inserter(later_txids, later_txids.end()),
35+
[](const auto& tx) { return tx->GetHash(); });
36+
for (const auto& tx : txns) {
37+
for (const auto& input : tx->vin) {
38+
if (later_txids.find(input.prevout.hash) != later_txids.end()) {
39+
// The parent is a subsequent transaction in the package.
40+
return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-sorted");
41+
}
42+
}
43+
later_txids.erase(tx->GetHash());
44+
}
45+
46+
// Don't allow any conflicting transactions, i.e. spending the same inputs, in a package.
47+
std::unordered_set<COutPoint, SaltedOutpointHasher> inputs_seen;
48+
for (const auto& tx : txns) {
49+
for (const auto& input : tx->vin) {
50+
if (inputs_seen.find(input.prevout) != inputs_seen.end()) {
51+
// This input is also present in another tx in the package.
52+
return state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package");
53+
}
54+
}
55+
// Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could
56+
// catch duplicate inputs within a single tx. This is a more severe, consensus error,
57+
// and we want to report that from CheckTransaction instead.
58+
std::transform(tx->vin.cbegin(), tx->vin.cend(), std::inserter(inputs_seen, inputs_seen.end()),
59+
[](const auto& input) { return input.prevout; });
60+
}
61+
return true;
62+
}

src/policy/packages.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_POLICY_PACKAGES_H
77

88
#include <consensus/validation.h>
9+
#include <policy/policy.h>
910
#include <primitives/transaction.h>
1011

1112
#include <vector>
@@ -14,6 +15,7 @@
1415
static constexpr uint32_t MAX_PACKAGE_COUNT{25};
1516
/** Default maximum total virtual size of transactions in a package in KvB. */
1617
static constexpr uint32_t MAX_PACKAGE_SIZE{101};
18+
static_assert(MAX_PACKAGE_SIZE * WITNESS_SCALE_FACTOR * 1000 >= MAX_STANDARD_TX_WEIGHT);
1719

1820
/** A "reason" why a package was invalid. It may be that one or more of the included
1921
* transactions is invalid or the package itself violates our rules.
@@ -31,4 +33,12 @@ using Package = std::vector<CTransactionRef>;
3133

3234
class PackageValidationState : public ValidationState<PackageValidationResult> {};
3335

36+
/** Context-free package policy checks:
37+
* 1. The number of transactions cannot exceed MAX_PACKAGE_COUNT.
38+
* 2. The total virtual size cannot exceed MAX_PACKAGE_SIZE.
39+
* 3. If any dependencies exist between transactions, parents must appear before children.
40+
* 4. Transactions cannot conflict, i.e., spend the same inputs.
41+
*/
42+
bool CheckPackage(const Package& txns, PackageValidationState& state);
43+
3444
#endif // BITCOIN_POLICY_PACKAGES_H

src/rpc/rawtransaction.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ static RPCHelpMan testmempoolaccept()
889889
"\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
890890
"\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
891891
"\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
892-
"\nThe maximum number of transactions allowed is 25 (MAX_PACKAGE_COUNT)\n"
892+
"\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
893893
"\nThis checks if transactions violate the consensus or policy rules.\n"
894894
"\nSee sendrawtransaction call.\n",
895895
{
@@ -905,7 +905,7 @@ static RPCHelpMan testmempoolaccept()
905905
RPCResult{
906906
RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
907907
"Returns results for each transaction in the same order they were passed in.\n"
908-
"It is possible for transactions to not be fully validated ('allowed' unset) if an earlier transaction failed.\n",
908+
"It is possible for transactions to not be fully validated ('allowed' unset) if another transaction failed.\n",
909909
{
910910
{RPCResult::Type::OBJ, "", "",
911911
{
@@ -939,7 +939,6 @@ static RPCHelpMan testmempoolaccept()
939939
UniValue::VARR,
940940
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
941941
});
942-
943942
const UniValue raw_transactions = request.params[0].get_array();
944943
if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
945944
throw JSONRPCError(RPC_INVALID_PARAMETER,
@@ -951,6 +950,7 @@ static RPCHelpMan testmempoolaccept()
951950
CFeeRate(AmountFromValue(request.params[1]));
952951

953952
std::vector<CTransactionRef> txns;
953+
txns.reserve(raw_transactions.size());
954954
for (const auto& rawtx : raw_transactions.getValues()) {
955955
CMutableTransaction mtx;
956956
if (!DecodeHexTx(mtx, rawtx.get_str())) {
@@ -971,8 +971,8 @@ static RPCHelpMan testmempoolaccept()
971971
}();
972972

973973
UniValue rpc_result(UniValue::VARR);
974-
// We will check transaction fees we iterate through txns in order. If any transaction fee
975-
// exceeds maxfeerate, we will keave the rest of the validation results blank, because it
974+
// We will check transaction fees while we iterate through txns in order. If any transaction fee
975+
// exceeds maxfeerate, we will leave the rest of the validation results blank, because it
976976
// doesn't make sense to return a validation result for a transaction if its ancestor(s) would
977977
// not be submitted.
978978
bool exit_early{false};

src/test/miner_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ struct MinerTestingSetup : public TestingSetup {
2828
void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
2929
bool TestSequenceLocks(const CTransaction& tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
3030
{
31-
CCoinsViewMemPool viewMempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool);
32-
return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), viewMempool, tx, flags);
31+
CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool);
32+
return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), view_mempool, tx, flags);
3333
}
3434
BlockAssembler AssemblerForTest(const CChainParams& params);
3535
};

src/txmempool.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,9 @@ void CTxMemPool::removeForReorg(CChainState& active_chainstate, int flags)
515515
LockPoints lp = it->GetLockPoints();
516516
assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
517517
bool validLP = TestLockPointValidity(active_chainstate.m_chain, &lp);
518-
CCoinsViewMemPool viewMempool(&active_chainstate.CoinsTip(), *this);
518+
CCoinsViewMemPool view_mempool(&active_chainstate.CoinsTip(), *this);
519519
if (!CheckFinalTx(active_chainstate.m_chain.Tip(), tx, flags)
520-
|| !CheckSequenceLocks(active_chainstate.m_chain.Tip(), viewMempool, tx, flags, &lp, validLP)) {
520+
|| !CheckSequenceLocks(active_chainstate.m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) {
521521
// Note if CheckSequenceLocks fails the LockPoints may still be invalid
522522
// So it's critical that we remove the tx and not depend on the LockPoints.
523523
txToRemove.insert(it);

src/txmempool.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,8 @@ class CCoinsViewMemPool : public CCoinsViewBacked
874874
public:
875875
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
876876
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
877-
/** Add the coins created by this transaction. */
877+
/** Add the coins created by this transaction. These coins are only temporarily stored in
878+
* m_temp_added and cannot be flushed to the back end. Only used for package validation. */
878879
void PackageAddTransaction(const CTransactionRef& tx);
879880
};
880881

src/validation.cpp

Lines changed: 21 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,10 @@ class MemPoolAccept
472472
*/
473473
std::vector<COutPoint>& m_coins_to_uncache;
474474
const bool m_test_accept;
475-
/** Disable BIP125 RBFing; disallow all conflicts with mempool transactions. */
476-
const bool disallow_mempool_conflicts;
475+
/** Whether we allow transactions to replace mempool transactions by BIP125 rules. If false,
476+
* any transaction spending the same inputs as a transaction in the mempool is considered
477+
* a conflict. */
478+
const bool m_allow_bip125_replacement{true};
477479
};
478480

479481
// Single transaction acceptance
@@ -482,7 +484,7 @@ class MemPoolAccept
482484
/**
483485
* Multiple transaction acceptance. Transactions may or may not be interdependent,
484486
* but must not conflict with each other. Parents must come before children if any
485-
* dependencies exist, otherwise a TX_MISSING_INPUTS error will be returned.
487+
* dependencies exist.
486488
*/
487489
PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
488490

@@ -619,6 +621,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
619621
{
620622
const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout);
621623
if (ptxConflicting) {
624+
if (!args.m_allow_bip125_replacement) {
625+
// Transaction conflicts with a mempool tx, but we're not allowing replacements.
626+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
627+
}
622628
if (!setConflicts.count(ptxConflicting->GetHash()))
623629
{
624630
// Allow opt-out of transaction replacement by setting
@@ -645,7 +651,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
645651
break;
646652
}
647653
}
648-
if (fReplacementOptOut || args.disallow_mempool_conflicts) {
654+
if (fReplacementOptOut) {
649655
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
650656
}
651657

@@ -1080,65 +1086,15 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
10801086
{
10811087
AssertLockHeld(cs_main);
10821088

1089+
// These context-free package limits can be done before taking the mempool lock.
10831090
PackageValidationState package_state;
1084-
const unsigned int package_count = txns.size();
1085-
1086-
// These context-free package limits can be checked before taking the mempool lock.
1087-
if (package_count > MAX_PACKAGE_COUNT) {
1088-
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-many-transactions");
1089-
return PackageMempoolAcceptResult(package_state, {});
1090-
}
1091-
1092-
const int64_t total_size = std::accumulate(txns.cbegin(), txns.cend(), 0,
1093-
[](int64_t sum, const auto& tx) { return sum + GetVirtualTransactionSize(*tx); });
1094-
// If the package only contains 1 tx, it's better to report the policy violation on individual tx size.
1095-
if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) {
1096-
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-large");
1097-
return PackageMempoolAcceptResult(package_state, {});
1098-
}
1091+
if (!CheckPackage(txns, package_state)) return PackageMempoolAcceptResult(package_state, {});
10991092

1100-
// Construct workspaces and check package policies.
11011093
std::vector<Workspace> workspaces{};
1102-
workspaces.reserve(package_count);
1103-
{
1104-
std::unordered_set<uint256, SaltedTxidHasher> later_txids;
1105-
std::transform(txns.cbegin(), txns.cend(), std::inserter(later_txids, later_txids.end()),
1106-
[](const auto& tx) { return tx->GetHash(); });
1107-
// Require the package to be sorted in order of dependency, i.e. parents appear before children.
1108-
// An unsorted package will fail anyway on missing-inputs, but it's better to quit earlier and
1109-
// fail on something less ambiguous (missing-inputs could also be an orphan or trying to
1110-
// spend nonexistent coins).
1111-
for (const auto& tx : txns) {
1112-
for (const auto& input : tx->vin) {
1113-
if (later_txids.find(input.prevout.hash) != later_txids.end()) {
1114-
// The parent is a subsequent transaction in the package.
1115-
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-sorted");
1116-
return PackageMempoolAcceptResult(package_state, {});
1117-
}
1118-
}
1119-
later_txids.erase(tx->GetHash());
1120-
workspaces.emplace_back(Workspace(tx));
1121-
}
1122-
}
1094+
workspaces.reserve(txns.size());
1095+
std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces),
1096+
[](const auto& tx) { return Workspace(tx); });
11231097
std::map<const uint256, const MempoolAcceptResult> results;
1124-
{
1125-
// Don't allow any conflicting transactions, i.e. spending the same inputs, in a package.
1126-
std::unordered_set<COutPoint, SaltedOutpointHasher> inputs_seen;
1127-
for (const auto& tx : txns) {
1128-
for (const auto& input : tx->vin) {
1129-
if (inputs_seen.find(input.prevout) != inputs_seen.end()) {
1130-
// This input is also present in another tx in the package.
1131-
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package");
1132-
return PackageMempoolAcceptResult(package_state, {});
1133-
}
1134-
}
1135-
// Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could
1136-
// catch duplicate inputs within a single tx. This is a more severe, consensus error,
1137-
// and we want to report that from CheckTransaction instead.
1138-
std::transform(tx->vin.cbegin(), tx->vin.cend(), std::inserter(inputs_seen, inputs_seen.end()),
1139-
[](const auto& input) { return input.prevout; });
1140-
}
1141-
}
11421098

11431099
LOCK(m_pool.cs);
11441100

@@ -1151,10 +1107,10 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
11511107
return PackageMempoolAcceptResult(package_state, std::move(results));
11521108
}
11531109
// Make the coins created by this transaction available for subsequent transactions in the
1154-
// package to spend. Since we already checked conflicts in the package and RBFs are
1155-
// impossible, we don't need to track the coins spent. Note that this logic will need to be
1156-
// updated if RBFs in packages are allowed in the future.
1157-
assert(args.disallow_mempool_conflicts);
1110+
// package to spend. Since we already checked conflicts in the package and we don't allow
1111+
// replacements, we don't need to track the coins spent. Note that this logic will need to be
1112+
// updated if package replace-by-fee is allowed in the future.
1113+
assert(!args.m_allow_bip125_replacement);
11581114
m_viewmempool.PackageAddTransaction(ws.m_ptx);
11591115
}
11601116

@@ -1188,7 +1144,7 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp
11881144
{
11891145
std::vector<COutPoint> coins_to_uncache;
11901146
MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache,
1191-
test_accept, /* disallow_mempool_conflicts */ false };
1147+
test_accept, /* m_allow_bip125_replacement */ true };
11921148

11931149
assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
11941150
const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args);
@@ -1225,12 +1181,11 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
12251181
std::vector<COutPoint> coins_to_uncache;
12261182
const CChainParams& chainparams = Params();
12271183
MemPoolAccept::ATMPArgs args { chainparams, GetTime(), /* bypass_limits */ false, coins_to_uncache,
1228-
test_accept, /* disallow_mempool_conflicts */ true };
1184+
test_accept, /* m_allow_bip125_replacement */ false };
12291185
assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
12301186
const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
12311187

12321188
// Uncache coins pertaining to transactions that were not submitted to the mempool.
1233-
// Ensure the cache is still within its size limits.
12341189
for (const COutPoint& hashTx : coins_to_uncache) {
12351190
active_chainstate.CoinsTip().Uncache(hashTx);
12361191
}

0 commit comments

Comments
 (0)