Skip to content

Commit ee862d6

Browse files
glozowariard
andcommitted
MOVEONLY: context-free package policies
Co-authored-by: ariard <[email protected]>
1 parent 5cac95c commit ee862d6

File tree

4 files changed

+76
-55
lines changed

4 files changed

+76
-55
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ libbitcoin_server_a_SOURCES = \
348348
node/ui_interface.cpp \
349349
noui.cpp \
350350
policy/fees.cpp \
351+
policy/packages.cpp \
351352
policy/rbf.cpp \
352353
policy/settings.cpp \
353354
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,12 @@ using Package = std::vector<CTransactionRef>;
3333

3434
class PackageValidationState : public ValidationState<PackageValidationResult> {};
3535

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+
3644
#endif // BITCOIN_POLICY_PACKAGES_H

src/validation.cpp

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,65 +1083,15 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
10831083
{
10841084
AssertLockHeld(cs_main);
10851085

1086+
// These context-free package limits can be done before taking the mempool lock.
10861087
PackageValidationState package_state;
1087-
const unsigned int package_count = txns.size();
1088+
if (!CheckPackage(txns, package_state)) return PackageMempoolAcceptResult(package_state, {});
10881089

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

11461096
LOCK(m_pool.cs);
11471097

0 commit comments

Comments
 (0)