Line data Source code
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 <key_io.h>
7 : #include <policy/packages.h>
8 : #include <policy/policy.h>
9 : #include <policy/settings.h>
10 : #include <primitives/transaction.h>
11 : #include <script/script.h>
12 : #include <script/standard.h>
13 : #include <test/util/random.h>
14 : #include <test/util/setup_common.h>
15 : #include <validation.h>
16 :
17 : #include <boost/test/unit_test.hpp>
18 :
19 : struct TestChain100NoDIP0001Setup : public TestChain100Setup {
20 4 : TestChain100NoDIP0001Setup()
21 4 : : TestChain100Setup{CBaseChainParams::REGTEST, {"-testactivationheight=dip0001@2000"}} {}
22 : };
23 :
24 146 : BOOST_AUTO_TEST_SUITE(txpackage_tests)
25 : // A fee amount that is above 1sat/vB but below 5sat/vB for most transactions created within these
26 : // unit tests. Dash transactions are larger than Bitcoin's (no SegWit discount), so this needs to
27 : // be higher than Bitcoin's 200 sat to ensure it exceeds minRelayTxFee for ~160-byte P2PKH txns.
28 : static const CAmount low_fee_amt{500};
29 :
30 : // Create placeholder transactions that have no meaning.
31 28 : inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outputs)
32 : {
33 28 : CMutableTransaction mtx = CMutableTransaction();
34 28 : mtx.vin.resize(num_inputs);
35 28 : mtx.vout.resize(num_outputs);
36 28 : auto random_script = CScript() << ToByteVector(InsecureRand256()) << ToByteVector(InsecureRand256());
37 1203 : for (size_t i{0}; i < num_inputs; ++i) {
38 1175 : mtx.vin[i].prevout.hash = InsecureRand256();
39 1175 : mtx.vin[i].prevout.n = 0;
40 1175 : mtx.vin[i].scriptSig = random_script;
41 1175 : }
42 1203 : for (size_t o{0}; o < num_outputs; ++o) {
43 1175 : mtx.vout[o].nValue = 1 * CENT;
44 1175 : mtx.vout[o].scriptPubKey = random_script;
45 1175 : }
46 28 : return MakeTransactionRef(mtx);
47 28 : }
48 :
49 148 : BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100NoDIP0001Setup)
50 : {
51 : // Packages can't have more than 25 transactions.
52 1 : Package package_too_many;
53 1 : package_too_many.reserve(MAX_PACKAGE_COUNT + 1);
54 27 : for (size_t i{0}; i < MAX_PACKAGE_COUNT + 1; ++i) {
55 26 : package_too_many.emplace_back(create_placeholder_tx(1, 1));
56 26 : }
57 1 : PackageValidationState state_too_many;
58 1 : BOOST_CHECK(!CheckPackage(package_too_many, state_too_many));
59 1 : BOOST_CHECK_EQUAL(state_too_many.GetResult(), PackageValidationResult::PCKG_POLICY);
60 1 : BOOST_CHECK_EQUAL(state_too_many.GetRejectReason(), "package-too-many-transactions");
61 :
62 : // Packages can't have a total size of more than 101KvB.
63 1 : CTransactionRef large_ptx = create_placeholder_tx(150, 150);
64 1 : Package package_too_large;
65 1 : auto size_large = GetVirtualTransactionSize(*large_ptx);
66 1 : size_t total_size{0};
67 5 : while (total_size <= MAX_PACKAGE_SIZE * 1000) {
68 4 : package_too_large.push_back(large_ptx);
69 4 : total_size += size_large;
70 : }
71 1 : BOOST_CHECK(package_too_large.size() <= MAX_PACKAGE_COUNT);
72 1 : PackageValidationState state_too_large;
73 1 : BOOST_CHECK(!CheckPackage(package_too_large, state_too_large));
74 1 : BOOST_CHECK_EQUAL(state_too_large.GetResult(), PackageValidationResult::PCKG_POLICY);
75 1 : BOOST_CHECK_EQUAL(state_too_large.GetRejectReason(), "package-too-large");
76 1 : }
77 :
78 148 : BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100NoDIP0001Setup)
79 : {
80 1 : LOCK(cs_main);
81 1 : unsigned int initialPoolSize = m_node.mempool->size();
82 :
83 : // Parent and Child Package
84 1 : CKey parent_key = GenerateRandomKey();
85 1 : CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
86 2 : auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
87 1 : /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
88 1 : /*output_destination=*/parent_locking_script,
89 : /*output_amount=*/CAmount(499 * COIN), /*submit=*/false);
90 1 : CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
91 :
92 1 : CKey child_key = GenerateRandomKey();
93 1 : CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
94 2 : auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
95 1 : /*input_height=*/101, /*input_signing_key=*/parent_key,
96 1 : /*output_destination=*/child_locking_script,
97 : /*output_amount=*/CAmount(498 * COIN), /*submit=*/false);
98 1 : CTransactionRef tx_child = MakeTransactionRef(mtx_child);
99 1 : const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /*test_accept=*/true);
100 1 : BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(),
101 : "Package validation unexpectedly failed: " << result_parent_child.m_state.GetRejectReason());
102 1 : auto it_parent = result_parent_child.m_tx_results.find(tx_parent->GetHash());
103 1 : auto it_child = result_parent_child.m_tx_results.find(tx_child->GetHash());
104 1 : BOOST_CHECK(it_parent != result_parent_child.m_tx_results.end());
105 1 : BOOST_CHECK_MESSAGE(it_parent->second.m_state.IsValid(),
106 : "Package validation unexpectedly failed: " << it_parent->second.m_state.GetRejectReason());
107 1 : BOOST_CHECK(it_child != result_parent_child.m_tx_results.end());
108 1 : BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(),
109 : "Package validation unexpectedly failed: " << it_child->second.m_state.GetRejectReason());
110 1 : BOOST_CHECK(result_parent_child.m_package_feerate.has_value());
111 1 : BOOST_CHECK(result_parent_child.m_package_feerate.value() ==
112 : CFeeRate(2 * COIN, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child)));
113 :
114 : // A single, giant transaction submitted through ProcessNewPackage fails on single tx policy.
115 1 : CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
116 1 : BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > MAX_PACKAGE_SIZE * 1000);
117 1 : auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {giant_ptx}, /*test_accept=*/true);
118 1 : BOOST_CHECK(result_single_large.m_state.IsInvalid());
119 1 : BOOST_CHECK_EQUAL(result_single_large.m_state.GetResult(), PackageValidationResult::PCKG_TX);
120 1 : BOOST_CHECK_EQUAL(result_single_large.m_state.GetRejectReason(), "transaction failed");
121 1 : auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetHash());
122 1 : BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end());
123 1 : BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size");
124 1 : BOOST_CHECK(result_single_large.m_package_feerate == std::nullopt);
125 :
126 : // Check that mempool size hasn't changed.
127 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
128 1 : }
129 :
130 148 : BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100NoDIP0001Setup)
131 : {
132 : // The signatures won't be verified so we can just use a placeholder
133 1 : CKey placeholder_key = GenerateRandomKey();
134 1 : CScript spk = GetScriptForDestination(PKHash(placeholder_key.GetPubKey()));
135 1 : CKey placeholder_key_2 = GenerateRandomKey();
136 1 : CScript spk2 = GetScriptForDestination(PKHash(placeholder_key_2.GetPubKey()));
137 :
138 : // Parent and Child Package
139 : {
140 1 : auto mtx_parent = CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, spk,
141 : CAmount(49 * COIN), /*submit=*/false);
142 1 : CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
143 :
144 1 : auto mtx_child = CreateValidMempoolTransaction(tx_parent, 0, 101, placeholder_key, spk2,
145 : CAmount(48 * COIN), /*submit=*/false);
146 1 : CTransactionRef tx_child = MakeTransactionRef(mtx_child);
147 :
148 1 : PackageValidationState state;
149 1 : BOOST_CHECK(CheckPackage({tx_parent, tx_child}, state));
150 1 : BOOST_CHECK(!CheckPackage({tx_child, tx_parent}, state));
151 1 : BOOST_CHECK_EQUAL(state.GetResult(), PackageValidationResult::PCKG_POLICY);
152 1 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
153 1 : BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
154 1 : }
155 :
156 : // 24 Parents and 1 Child
157 : {
158 1 : Package package;
159 1 : CMutableTransaction child;
160 25 : for (int i{0}; i < 24; ++i) {
161 48 : auto parent = MakeTransactionRef(CreateValidMempoolTransaction(m_coinbase_txns[i + 1],
162 24 : 0, 0, coinbaseKey, spk, CAmount(48 * COIN), false));
163 24 : package.emplace_back(parent);
164 24 : child.vin.emplace_back(COutPoint(parent->GetHash(), 0));
165 24 : }
166 1 : child.vout.emplace_back(47 * COIN, spk2);
167 :
168 : // The child must be in the package.
169 1 : BOOST_CHECK(!IsChildWithParents(package));
170 :
171 : // The parents can be in any order.
172 1 : FastRandomContext rng;
173 1 : Shuffle(package.begin(), package.end(), rng);
174 1 : package.push_back(MakeTransactionRef(child));
175 :
176 1 : PackageValidationState state;
177 1 : BOOST_CHECK(CheckPackage(package, state));
178 1 : BOOST_CHECK(IsChildWithParents(package));
179 :
180 1 : package.erase(package.begin());
181 1 : BOOST_CHECK(IsChildWithParents(package));
182 :
183 : // The package cannot have unrelated transactions.
184 1 : package.insert(package.begin(), m_coinbase_txns[0]);
185 1 : BOOST_CHECK(!IsChildWithParents(package));
186 1 : }
187 :
188 : // 2 Parents and 1 Child where one parent depends on the other.
189 : {
190 1 : CMutableTransaction mtx_parent;
191 1 : mtx_parent.vin.emplace_back(COutPoint(m_coinbase_txns[0]->GetHash(), 0));
192 1 : mtx_parent.vout.emplace_back(20 * COIN, spk);
193 1 : mtx_parent.vout.emplace_back(20 * COIN, spk2);
194 1 : CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
195 :
196 1 : CMutableTransaction mtx_parent_also_child;
197 1 : mtx_parent_also_child.vin.emplace_back(COutPoint(tx_parent->GetHash(), 0));
198 1 : mtx_parent_also_child.vout.emplace_back(20 * COIN, spk);
199 1 : CTransactionRef tx_parent_also_child = MakeTransactionRef(mtx_parent_also_child);
200 :
201 1 : CMutableTransaction mtx_child;
202 1 : mtx_child.vin.emplace_back(COutPoint(tx_parent->GetHash(), 1));
203 1 : mtx_child.vin.emplace_back(COutPoint(tx_parent_also_child->GetHash(), 0));
204 1 : mtx_child.vout.emplace_back(39 * COIN, spk);
205 1 : CTransactionRef tx_child = MakeTransactionRef(mtx_child);
206 :
207 1 : PackageValidationState state;
208 1 : BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child}));
209 1 : BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
210 1 : BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child, tx_child}));
211 : // IsChildWithParents does not detect unsorted parents.
212 1 : BOOST_CHECK(IsChildWithParents({tx_parent_also_child, tx_parent, tx_child}));
213 1 : BOOST_CHECK(CheckPackage({tx_parent, tx_parent_also_child, tx_child}, state));
214 1 : BOOST_CHECK(!CheckPackage({tx_parent_also_child, tx_parent, tx_child}, state));
215 1 : BOOST_CHECK_EQUAL(state.GetResult(), PackageValidationResult::PCKG_POLICY);
216 1 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
217 1 : }
218 1 : }
219 :
220 148 : BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100NoDIP0001Setup)
221 : {
222 1 : LOCK(cs_main);
223 1 : unsigned int expected_pool_size = m_node.mempool->size();
224 1 : CKey parent_key = GenerateRandomKey();
225 1 : CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
226 :
227 : // Unrelated transactions are not allowed in package submission.
228 1 : Package package_unrelated;
229 11 : for (size_t i{0}; i < 10; ++i) {
230 20 : auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[i + 25], /*input_vout=*/0,
231 10 : /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
232 10 : /*output_destination=*/parent_locking_script,
233 : /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
234 10 : package_unrelated.emplace_back(MakeTransactionRef(mtx));
235 10 : }
236 1 : auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
237 : package_unrelated, /*test_accept=*/false);
238 1 : BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
239 1 : BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
240 1 : BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
241 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
242 1 : BOOST_CHECK(result_unrelated_submit.m_package_feerate == std::nullopt);
243 :
244 : // Parent and Child (and Grandchild) Package
245 1 : Package package_parent_child;
246 1 : Package package_3gen;
247 2 : auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
248 1 : /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
249 1 : /*output_destination=*/parent_locking_script,
250 : /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
251 1 : CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
252 1 : package_parent_child.push_back(tx_parent);
253 1 : package_3gen.push_back(tx_parent);
254 :
255 1 : CKey child_key = GenerateRandomKey();
256 1 : CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
257 2 : auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
258 1 : /*input_height=*/101, /*input_signing_key=*/parent_key,
259 1 : /*output_destination=*/child_locking_script,
260 : /*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
261 1 : CTransactionRef tx_child = MakeTransactionRef(mtx_child);
262 1 : package_parent_child.push_back(tx_child);
263 1 : package_3gen.push_back(tx_child);
264 :
265 1 : CKey grandchild_key = GenerateRandomKey();
266 1 : CScript grandchild_locking_script = GetScriptForDestination(PKHash(grandchild_key.GetPubKey()));
267 2 : auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/tx_child, /*input_vout=*/0,
268 1 : /*input_height=*/101, /*input_signing_key=*/child_key,
269 1 : /*output_destination=*/grandchild_locking_script,
270 : /*output_amount=*/CAmount(47 * COIN), /*submit=*/false);
271 1 : CTransactionRef tx_grandchild = MakeTransactionRef(mtx_grandchild);
272 1 : package_3gen.push_back(tx_grandchild);
273 :
274 : // 3 Generations is not allowed.
275 : {
276 1 : auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
277 : package_3gen, /*test_accept=*/false);
278 1 : BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
279 1 : BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
280 1 : BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
281 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
282 1 : BOOST_CHECK(result_3gen_submit.m_package_feerate == std::nullopt);
283 1 : }
284 :
285 : // Child with missing parent.
286 1 : mtx_child.vin.emplace_back(COutPoint(package_unrelated[0]->GetHash(), 0));
287 1 : Package package_missing_parent;
288 1 : package_missing_parent.push_back(tx_parent);
289 1 : package_missing_parent.push_back(MakeTransactionRef(mtx_child));
290 : {
291 1 : const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
292 : package_missing_parent, /*test_accept=*/false);
293 1 : BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
294 1 : BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
295 1 : BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
296 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
297 :
298 1 : BOOST_CHECK(result_missing_parent.m_package_feerate == std::nullopt);
299 1 : }
300 :
301 : // Submit package with parent + child.
302 : {
303 1 : const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
304 : package_parent_child, /*test_accept=*/false);
305 1 : expected_pool_size += 2;
306 1 : BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
307 : "Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
308 1 : auto it_parent = submit_parent_child.m_tx_results.find(tx_parent->GetHash());
309 1 : auto it_child = submit_parent_child.m_tx_results.find(tx_child->GetHash());
310 1 : BOOST_CHECK(it_parent != submit_parent_child.m_tx_results.end());
311 1 : BOOST_CHECK(it_parent->second.m_state.IsValid());
312 1 : BOOST_CHECK(it_child != submit_parent_child.m_tx_results.end());
313 1 : BOOST_CHECK(it_child->second.m_state.IsValid());
314 :
315 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
316 1 : BOOST_CHECK(m_node.mempool->exists(tx_parent->GetHash()));
317 1 : BOOST_CHECK(m_node.mempool->exists(tx_child->GetHash()));
318 :
319 : // Since both transactions have high feerates, they each passed validation individually.
320 : // Package validation was unnecessary, so there is no package feerate.
321 1 : BOOST_CHECK(submit_parent_child.m_package_feerate == std::nullopt);
322 1 : }
323 :
324 : // Already-in-mempool transactions should be detected and de-duplicated.
325 : {
326 1 : const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
327 : package_parent_child, /*test_accept=*/false);
328 1 : BOOST_CHECK_MESSAGE(submit_deduped.m_state.IsValid(),
329 : "Package validation unexpectedly failed: " << submit_deduped.m_state.GetRejectReason());
330 1 : auto it_parent_deduped = submit_deduped.m_tx_results.find(tx_parent->GetHash());
331 1 : auto it_child_deduped = submit_deduped.m_tx_results.find(tx_child->GetHash());
332 1 : BOOST_CHECK(it_parent_deduped != submit_deduped.m_tx_results.end());
333 1 : BOOST_CHECK(it_parent_deduped->second.m_state.IsValid());
334 1 : BOOST_CHECK(it_parent_deduped->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
335 1 : BOOST_CHECK(it_child_deduped != submit_deduped.m_tx_results.end());
336 1 : BOOST_CHECK(it_child_deduped->second.m_state.IsValid());
337 1 : BOOST_CHECK(it_child_deduped->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
338 :
339 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
340 1 : BOOST_CHECK(m_node.mempool->exists(tx_parent->GetHash()));
341 1 : BOOST_CHECK(m_node.mempool->exists(tx_child->GetHash()));
342 :
343 1 : BOOST_CHECK(submit_deduped.m_package_feerate == std::nullopt);
344 1 : }
345 1 : }
346 :
347 149 : BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
348 : {
349 1 : mineBlocks(5);
350 1 : MockMempoolMinFee(CFeeRate(5000));
351 1 : LOCK(::cs_main);
352 1 : size_t expected_pool_size = m_node.mempool->size();
353 1 : CKey child_key = GenerateRandomKey();
354 1 : CScript parent_spk = GetScriptForDestination(PKHash(child_key.GetPubKey()));
355 1 : CKey grandchild_key = GenerateRandomKey();
356 1 : CScript child_spk = GetScriptForDestination(PKHash(grandchild_key.GetPubKey()));
357 :
358 : // low-fee parent and high-fee child package
359 1 : const CAmount coinbase_value{500 * COIN};
360 1 : const CAmount parent_value{coinbase_value - low_fee_amt};
361 1 : const CAmount child_value{parent_value - COIN};
362 :
363 1 : Package package_cpfp;
364 2 : auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
365 1 : /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
366 1 : /*output_destination=*/parent_spk,
367 : /*output_amount=*/parent_value, /*submit=*/false);
368 1 : CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
369 1 : package_cpfp.push_back(tx_parent);
370 :
371 2 : auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
372 1 : /*input_height=*/101, /*input_signing_key=*/child_key,
373 1 : /*output_destination=*/child_spk,
374 : /*output_amount=*/child_value, /*submit=*/false);
375 1 : CTransactionRef tx_child = MakeTransactionRef(mtx_child);
376 1 : package_cpfp.push_back(tx_child);
377 :
378 : // Verify that the low-fee parent individually meets the min relay fee requirement.
379 : // This is important because Dash transactions are larger than Bitcoin's (no SegWit),
380 : // so we need a higher low_fee_amt to ensure the parent's fee exceeds minRelayTxFee.
381 1 : BOOST_CHECK_MESSAGE(::minRelayTxFee.GetFee(GetVirtualTransactionSize(*tx_parent)) <= low_fee_amt,
382 : strprintf("low_fee_amt %d is below minRelayTxFee %d for parent vsize %d",
383 : low_fee_amt, ::minRelayTxFee.GetFee(GetVirtualTransactionSize(*tx_parent)),
384 : GetVirtualTransactionSize(*tx_parent)));
385 : // But the parent's fee should be below the mempool minimum feerate.
386 1 : BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_parent)) > low_fee_amt);
387 :
388 : // Package feerate is calculated using modified fees, and prioritisetransaction accepts negative
389 : // fee deltas. This should be taken into account. De-prioritise the parent transaction
390 : // to bring the package feerate to 0.
391 1 : m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), child_value - coinbase_value);
392 : {
393 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
394 1 : const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
395 : package_cpfp, /*test_accept=*/ false);
396 1 : BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_TX);
397 1 : BOOST_CHECK(submit_cpfp_deprio.m_state.IsInvalid());
398 1 : BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetHash())->second.m_state.GetResult(),
399 : TxValidationResult::TX_MEMPOOL_POLICY);
400 1 : BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_child->GetHash()) == submit_cpfp_deprio.m_tx_results.end());
401 1 : BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetHash())->second.m_state.GetRejectReason() == "min relay fee not met");
402 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
403 1 : }
404 :
405 : // Clear the prioritisation of the parent transaction.
406 2 : WITH_LOCK(m_node.mempool->cs, m_node.mempool->ClearPrioritisation(tx_parent->GetHash()));
407 :
408 : // Package CPFP: Even though the parent's feerate is below the mempool minimum feerate, the
409 : // child pays enough for the package feerate to meet the threshold.
410 : {
411 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
412 1 : const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
413 : package_cpfp, /*test_accept=*/ false);
414 1 : expected_pool_size += 2;
415 1 : BOOST_CHECK_MESSAGE(submit_cpfp.m_state.IsValid(),
416 : "Package validation unexpectedly failed: " << submit_cpfp.m_state.GetRejectReason());
417 1 : BOOST_CHECK_EQUAL(submit_cpfp.m_tx_results.size(), package_cpfp.size());
418 1 : auto it_parent = submit_cpfp.m_tx_results.find(tx_parent->GetHash());
419 1 : auto it_child = submit_cpfp.m_tx_results.find(tx_child->GetHash());
420 1 : BOOST_CHECK(it_parent != submit_cpfp.m_tx_results.end());
421 1 : BOOST_CHECK_MESSAGE(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID,
422 : strprintf("Parent tx failed: %s", it_parent->second.m_state.GetRejectReason()));
423 1 : BOOST_CHECK(it_parent->second.m_base_fees.value() == coinbase_value - parent_value);
424 1 : BOOST_CHECK(it_child != submit_cpfp.m_tx_results.end());
425 1 : BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
426 1 : BOOST_CHECK(it_child->second.m_base_fees.value() == COIN);
427 :
428 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
429 1 : BOOST_CHECK(m_node.mempool->exists(tx_parent->GetHash()));
430 1 : BOOST_CHECK(m_node.mempool->exists(tx_child->GetHash()));
431 :
432 2 : const CFeeRate expected_feerate(coinbase_value - child_value,
433 1 : GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child));
434 1 : BOOST_CHECK(expected_feerate.GetFeePerK() > 1000);
435 1 : BOOST_CHECK(submit_cpfp.m_package_feerate.has_value());
436 1 : BOOST_CHECK_MESSAGE(submit_cpfp.m_package_feerate.value() == expected_feerate,
437 : strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
438 : submit_cpfp.m_package_feerate.value().ToString()));
439 1 : }
440 :
441 : // Just because we allow low-fee parents doesn't mean we allow low-feerate packages.
442 : // The mempool minimum feerate is 5sat/vB, but this package just pays 1700 satoshis total.
443 : // The child fees would be able to pay for itself, but isn't enough for the entire package.
444 : // Note: Dash transactions are larger than Bitcoin's (no SegWit discount, ~225 bytes for P2PKH),
445 : // so fees are higher than Bitcoin's test values to ensure they exceed minRelayTxFee.
446 1 : Package package_still_too_low;
447 1 : const CAmount parent_fee{500};
448 1 : const CAmount child_fee{1200};
449 2 : auto mtx_parent_cheap = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
450 1 : /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
451 1 : /*output_destination=*/parent_spk,
452 : /*output_amount=*/coinbase_value - parent_fee, /*submit=*/false);
453 1 : CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap);
454 1 : package_still_too_low.push_back(tx_parent_cheap);
455 1 : BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) > parent_fee);
456 1 : BOOST_CHECK(::minRelayTxFee.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee);
457 :
458 2 : auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0,
459 1 : /*input_height=*/101, /*input_signing_key=*/child_key,
460 1 : /*output_destination=*/child_spk,
461 : /*output_amount=*/coinbase_value - parent_fee - child_fee, /*submit=*/false);
462 1 : CTransactionRef tx_child_cheap = MakeTransactionRef(mtx_child_cheap);
463 1 : package_still_too_low.push_back(tx_child_cheap);
464 1 : BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_child_cheap)) <= child_fee);
465 1 : BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)) > parent_fee + child_fee);
466 :
467 : // Cheap package should fail with package-fee-too-low.
468 : {
469 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
470 1 : const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
471 : package_still_too_low, /*test_accept=*/false);
472 1 : BOOST_CHECK_MESSAGE(submit_package_too_low.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
473 1 : BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
474 1 : BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low");
475 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
476 2 : const CFeeRate expected_feerate(parent_fee + child_fee,
477 1 : GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
478 1 : BOOST_CHECK(submit_package_too_low.m_package_feerate.has_value());
479 1 : BOOST_CHECK_MESSAGE(submit_package_too_low.m_package_feerate.value() == expected_feerate,
480 : strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
481 : submit_package_too_low.m_package_feerate.value().ToString()));
482 1 : }
483 :
484 : // Package feerate includes the modified fees of the transactions.
485 : // This means a child with its fee delta from prioritisetransaction can pay for a parent.
486 1 : m_node.mempool->PrioritiseTransaction(tx_child_cheap->GetHash(), 1 * COIN);
487 : // Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed.
488 : {
489 1 : const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
490 : package_still_too_low, /*test_accept=*/false);
491 1 : expected_pool_size += 2;
492 1 : BOOST_CHECK_MESSAGE(submit_prioritised_package.m_state.IsValid(),
493 : "Package validation unexpectedly failed" << submit_prioritised_package.m_state.GetRejectReason());
494 2 : const CFeeRate expected_feerate(1 * COIN + parent_fee + child_fee,
495 1 : GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
496 1 : BOOST_CHECK(submit_prioritised_package.m_package_feerate.has_value());
497 1 : BOOST_CHECK_MESSAGE(submit_prioritised_package.m_package_feerate.value() == expected_feerate,
498 : strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
499 : submit_prioritised_package.m_package_feerate.value().ToString()));
500 1 : BOOST_CHECK_EQUAL(submit_prioritised_package.m_tx_results.size(), package_still_too_low.size());
501 1 : auto it_parent = submit_prioritised_package.m_tx_results.find(tx_parent_cheap->GetHash());
502 1 : auto it_child = submit_prioritised_package.m_tx_results.find(tx_child_cheap->GetHash());
503 1 : BOOST_CHECK(it_parent != submit_prioritised_package.m_tx_results.end());
504 1 : BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
505 1 : BOOST_CHECK(it_parent->second.m_base_fees.value() == parent_fee);
506 1 : BOOST_CHECK(it_child != submit_prioritised_package.m_tx_results.end());
507 1 : BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
508 1 : BOOST_CHECK(it_child->second.m_base_fees.value() == child_fee);
509 1 : }
510 :
511 : // Package feerate is calculated without topology in mind; it's just aggregating fees and sizes.
512 : // However, this should not allow parents to pay for children. Each transaction should be
513 : // validated individually first, eliminating sufficient-feerate parents before they are unfairly
514 : // included in the package feerate. It's also important that the low-fee child doesn't prevent
515 : // the parent from being accepted.
516 1 : Package package_rich_parent;
517 1 : const CAmount high_parent_fee{1 * COIN};
518 2 : auto mtx_parent_rich = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0,
519 1 : /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
520 1 : /*output_destination=*/parent_spk,
521 : /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false);
522 1 : CTransactionRef tx_parent_rich = MakeTransactionRef(mtx_parent_rich);
523 1 : package_rich_parent.push_back(tx_parent_rich);
524 :
525 2 : auto mtx_child_poor = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_rich, /*input_vout=*/0,
526 1 : /*input_height=*/101, /*input_signing_key=*/child_key,
527 1 : /*output_destination=*/child_spk,
528 : /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false);
529 1 : CTransactionRef tx_child_poor = MakeTransactionRef(mtx_child_poor);
530 1 : package_rich_parent.push_back(tx_child_poor);
531 :
532 : // Parent pays 1 BTC and child pays none. The parent should be accepted without the child.
533 : {
534 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
535 1 : const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
536 : package_rich_parent, /*test_accept=*/false);
537 1 : expected_pool_size += 1;
538 1 : BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
539 :
540 : // The child would have been validated on its own and failed, then submitted as a "package" of 1.
541 1 : BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
542 1 : BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "transaction failed");
543 :
544 1 : auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetHash());
545 1 : BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
546 1 : BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
547 1 : BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == "");
548 1 : BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
549 : strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
550 1 : auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetHash());
551 1 : BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
552 1 : BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
553 1 : BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);
554 1 : BOOST_CHECK(it_child->second.m_state.GetRejectReason() == "min relay fee not met");
555 :
556 1 : BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
557 1 : BOOST_CHECK(m_node.mempool->exists(tx_parent_rich->GetHash()));
558 1 : BOOST_CHECK(!m_node.mempool->exists(tx_child_poor->GetHash()));
559 1 : }
560 1 : }
561 146 : BOOST_AUTO_TEST_SUITE_END()
|