I think I agree with instagibbs that adding complexity on top should be avoided if we can, and it seems to me like modifying IsChildWithParents()
to better align with the Package
definition (i.e. child and all unconfirmed parents, as per AcceptPackage()
) is preferable for long-term robustness, over special-casing a transaction that has 0 unconfirmed parents as is currently the case.
I looked at the callstacks that make use of IsChildWithParents()
, and since the scope is fairly limited, I feel quite confident that this new behaviour is safe? But of course, my knowledge on this part of the codebase comes nowhere near what you both have.
My summary of the call hierarchy of IsChildWithParents()
and how this PR affects it:
IsChildWithParentsTree()
, which is a RPC-only function and looks unaffected by this PR
MemPoolAccept::PackageMempoolChecks()
, which is unaffected by this PR because the callsite guarantees the package is of size 2.
MemPoolAccept::AcceptPackage()
, which looks safe, but affects the return value for callsites:
ProcessNewPackage()
, which looks safe, but affects the return value for callsites:
net_processing.cpp
(2x), which is unaffected by this PR, because ProcessNewPackage()
is guaranteed to be called with a package of size 2 (from Find1P1CPackage()
)
testmempoolaccept()
, which is unaffected by this PR because ProcessNewPackage()
is only called when txns.size() > 1
submitpackage()
, which is the goal of this PR, and seems safe to me.
Potentially, we could increase test coverage for a single-tx package? There are probably better ways to write these, but e.g. these tests on ProcessNewPackage()
(and as a bonus, but perhaps not needed here on IsWellFormedPackage()
) seem like they could be useful?
0diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
1index 9954ad6b7c..3a90a81ea2 100644
2--- a/src/test/txpackage_tests.cpp
3+++ b/src/test/txpackage_tests.cpp
4@@ -357,6 +357,17 @@ BOOST_AUTO_TEST_CASE(noncontextual_package_tests)
5 BOOST_CHECK_EQUAL(state.GetResult(), PackageValidationResult::PCKG_POLICY);
6 BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
7 }
8+
9+ // Transaction without unconfirmed parents
10+ {
11+ CMutableTransaction mtx;
12+ mtx.vin.emplace_back(COutPoint(m_coinbase_txns[0]->GetHash(), 0));
13+ mtx.vout.emplace_back(20 * COIN, spk);
14+ CTransactionRef tx = MakeTransactionRef(mtx);
15+
16+ PackageValidationState state;
17+ BOOST_CHECK(IsWellFormedPackage({tx}, state, /*require_sorted=*/true));
18+ }
19 }
20
21 BOOST_AUTO_TEST_CASE(package_submission_tests)
22@@ -500,6 +511,29 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
23 }
24 }
25
26+// Tests for packages containing a single transaction that doesn't have
27+// any unconfirmed parents.
28+BOOST_AUTO_TEST_CASE(package_single_tx)
29+{
30+ LOCK(cs_main);
31+ auto expected_pool_size{m_node.mempool->size()};
32+
33+ CKey single_key = GenerateRandomKey();
34+ CScript single_locking_script = GetScriptForDestination(PKHash(single_key.GetPubKey()));
35+ auto mtx_single = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
36+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
37+ /*output_destination=*/single_locking_script,
38+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
39+ CTransactionRef tx_single = MakeTransactionRef(mtx_single);
40+ Package package_tx_single{tx_single};
41+ const auto result_single_tx = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
42+ package_tx_single, /*test_accept=*/false, /*client_maxfeerate=*/{});
43+ expected_pool_size += 1;
44+ BOOST_CHECK_MESSAGE(result_single_tx.m_state.IsValid(),
45+ "Package validation unexpectedly failed: " << result_single_tx.m_state.GetRejectReason());
46+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
47+}
48+
49 // Tests for packages containing transactions that have same-txid-different-witness equivalents in
50 // the mempool.
51 BOOST_AUTO_TEST_CASE(package_witness_swap_tests)