Skip to content

Commit 7d7f7a1

Browse files
committed
[policy] check for duplicate txids in package
Duplicates of normal transactions would be found by looking for conflicting inputs, but this doesn't catch identical empty transactions. These wouldn't be valid but exiting early is good and AcceptPackage's result sanity checks assume non-duplicate transactions.
1 parent b2ec032 commit 7d7f7a1

File tree

3 files changed

+22
-4
lines changed

3 files changed

+22
-4
lines changed

src/policy/packages.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ bool CheckPackage(const Package& txns, PackageValidationState& state)
3737
std::unordered_set<uint256, SaltedTxidHasher> later_txids;
3838
std::transform(txns.cbegin(), txns.cend(), std::inserter(later_txids, later_txids.end()),
3939
[](const auto& tx) { return tx->GetHash(); });
40+
41+
// Package must not contain any duplicate transactions, which is checked by txid. This also
42+
// includes transactions with duplicate wtxids and same-txid-different-witness transactions.
43+
if (later_txids.size() != txns.size()) {
44+
return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-contains-duplicates");
45+
}
46+
4047
for (const auto& tx : txns) {
4148
for (const auto& input : tx->vin) {
4249
if (later_txids.find(input.prevout.hash) != later_txids.end()) {

src/test/txpackage_tests.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100Setup)
6666
BOOST_CHECK(!CheckPackage(package_too_large, state_too_large));
6767
BOOST_CHECK_EQUAL(state_too_large.GetResult(), PackageValidationResult::PCKG_POLICY);
6868
BOOST_CHECK_EQUAL(state_too_large.GetRejectReason(), "package-too-large");
69+
70+
// Packages can't contain transactions with the same txid.
71+
Package package_duplicate_txids_empty;
72+
for (auto i{0}; i < 3; ++i) {
73+
CMutableTransaction empty_tx;
74+
package_duplicate_txids_empty.emplace_back(MakeTransactionRef(empty_tx));
75+
}
76+
PackageValidationState state_duplicates;
77+
BOOST_CHECK(!CheckPackage(package_duplicate_txids_empty, state_duplicates));
78+
BOOST_CHECK_EQUAL(state_duplicates.GetResult(), PackageValidationResult::PCKG_POLICY);
79+
BOOST_CHECK_EQUAL(state_duplicates.GetRejectReason(), "package-contains-duplicates");
6980
}
7081

7182
BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)

test/functional/rpc_packages.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ def test_conflicting(self):
212212
coin = self.wallet.get_utxo()
213213

214214
# tx1 and tx2 share the same inputs
215-
tx1 = self.wallet.create_self_transfer(utxo_to_spend=coin)
216-
tx2 = self.wallet.create_self_transfer(utxo_to_spend=coin)
215+
tx1 = self.wallet.create_self_transfer(utxo_to_spend=coin, fee_rate=DEFAULT_FEE)
216+
tx2 = self.wallet.create_self_transfer(utxo_to_spend=coin, fee_rate=2*DEFAULT_FEE)
217217

218218
# Ensure tx1 and tx2 are valid by themselves
219219
assert node.testmempoolaccept([tx1["hex"]])[0]["allowed"]
@@ -222,8 +222,8 @@ def test_conflicting(self):
222222
self.log.info("Test duplicate transactions in the same package")
223223
testres = node.testmempoolaccept([tx1["hex"], tx1["hex"]])
224224
assert_equal(testres, [
225-
{"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"},
226-
{"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"}
225+
{"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "package-contains-duplicates"},
226+
{"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "package-contains-duplicates"}
227227
])
228228

229229
self.log.info("Test conflicting transactions in the same package")

0 commit comments

Comments
 (0)