txvalidation_tests.cpp raw
1 // Copyright (c) 2017-2021 The Limenka 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/ephemeral_policy.h>
10 #include <policy/truc_policy.h>
11 #include <primitives/transaction.h>
12 #include <random.h>
13 #include <script/script.h>
14 #include <test/util/setup_common.h>
15 #include <test/util/transaction_utils.h>
16 #include <test/util/txmempool.h>
17 #include <validation.h>
18
19 #include <boost/test/unit_test.hpp>
20
21
22 BOOST_AUTO_TEST_SUITE(txvalidation_tests)
23
24 std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx, const CTxMemPool::setEntries& mempool_ancestors, const std::set<Txid>& direct_conflicts, int64_t vsize)
25 {
26 std::string dummy;
27 return SingleTRUCChecks(ptx, dummy, dummy, empty_ignore_rejects, mempool_ancestors, direct_conflicts, vsize);
28 }
29
30 std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize, const Package& package, const CTxMemPool::setEntries& mempool_ancestors)
31 {
32 std::string dummy;
33 return PackageTRUCChecks(ptx, vsize, dummy, dummy, empty_ignore_rejects, package, mempool_ancestors);
34 }
35
36 /**
37 * Ensure that the mempool won't accept coinbase transactions.
38 */
39 BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
40 {
41 CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
42 CMutableTransaction coinbaseTx;
43
44 coinbaseTx.version = 1;
45 coinbaseTx.vin.resize(1);
46 coinbaseTx.vout.resize(1);
47 coinbaseTx.vin[0].scriptSig = CScript() << OP_11 << OP_EQUAL;
48 coinbaseTx.vout[0].nValue = 1 * CENT;
49 coinbaseTx.vout[0].scriptPubKey = scriptPubKey;
50
51 BOOST_CHECK(CTransaction(coinbaseTx).IsCoinBase());
52
53 LOCK(cs_main);
54
55 unsigned int initialPoolSize = m_node.mempool->size();
56 const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(coinbaseTx));
57
58 BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID);
59
60 // Check that the transaction hasn't been added to mempool.
61 BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
62
63 // Check that the validation state reflects the unsuccessful attempt.
64 BOOST_CHECK(result.m_state.IsInvalid());
65 BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");
66 BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);
67 }
68
69 // Generate a number of random, nonexistent outpoints.
70 static inline std::vector<COutPoint> random_outpoints(size_t num_outpoints) {
71 std::vector<COutPoint> outpoints;
72 for (size_t i{0}; i < num_outpoints; ++i) {
73 outpoints.emplace_back(Txid::FromUint256(GetRandHash()), 0);
74 }
75 return outpoints;
76 }
77
78 static inline std::vector<CPubKey> random_keys(size_t num_keys) {
79 std::vector<CPubKey> keys;
80 keys.reserve(num_keys);
81 for (size_t i{0}; i < num_keys; ++i) {
82 CKey key;
83 key.MakeNewKey(true);
84 keys.emplace_back(key.GetPubKey());
85 }
86 return keys;
87 }
88
89 // Creates a placeholder tx (not valid) with 25 outputs. Specify the version and the inputs.
90 static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, int32_t version)
91 {
92 CMutableTransaction mtx = CMutableTransaction{};
93 mtx.version = version;
94 mtx.vin.resize(inputs.size());
95 mtx.vout.resize(25);
96 for (size_t i{0}; i < inputs.size(); ++i) {
97 mtx.vin[i].prevout = inputs[i];
98 }
99 for (auto i{0}; i < 25; ++i) {
100 mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;
101 mtx.vout[i].nValue = 10000;
102 }
103 return MakeTransactionRef(mtx);
104 }
105
106 static constexpr auto NUM_EPHEMERAL_TX_OUTPUTS = 3;
107 static constexpr auto EPHEMERAL_DUST_INDEX = NUM_EPHEMERAL_TX_OUTPUTS - 1;
108
109 // Same as make_tx but adds 2 normal outputs and 0-value dust to end of vout
110 static inline CTransactionRef make_ephemeral_tx(const std::vector<COutPoint>& inputs, int32_t version)
111 {
112 CMutableTransaction mtx = CMutableTransaction{};
113 mtx.version = version;
114 mtx.vin.resize(inputs.size());
115 for (size_t i{0}; i < inputs.size(); ++i) {
116 mtx.vin[i].prevout = inputs[i];
117 }
118 mtx.vout.resize(NUM_EPHEMERAL_TX_OUTPUTS);
119 for (auto i{0}; i < NUM_EPHEMERAL_TX_OUTPUTS; ++i) {
120 mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;
121 mtx.vout[i].nValue = (i == EPHEMERAL_DUST_INDEX) ? 0 : 10000;
122 }
123 return MakeTransactionRef(mtx);
124 }
125
126 BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
127 {
128 CTxMemPool& pool = *Assert(m_node.mempool);
129 LOCK2(cs_main, pool.cs);
130 TestMemPoolEntryHelper entry;
131 CTxMemPool::setEntries empty_ancestors;
132
133 TxValidationState child_state;
134 Wtxid child_wtxid;
135
136 // Arbitrary non-0 feerate for these tests
137 CFeeRate dustrelay(DUST_RELAY_TX_FEE);
138
139 // Basic transaction with dust
140 auto grandparent_tx_1 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);
141 const auto dust_txid = grandparent_tx_1->GetHash();
142
143 // Child transaction spending dust
144 auto dust_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
145
146 // We first start with nothing "in the mempool", using package checks
147
148 // Trivial single transaction with no dust
149 BOOST_CHECK(CheckEphemeralSpends({dust_spend}, dustrelay, pool, child_state, child_wtxid));
150 BOOST_CHECK(child_state.IsValid());
151 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
152
153 // Now with dust, ok because the tx has no dusty parents
154 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));
155 BOOST_CHECK(child_state.IsValid());
156 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
157
158 // Dust checks pass
159 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, CFeeRate(0), pool, child_state, child_wtxid));
160 BOOST_CHECK(child_state.IsValid());
161 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
162 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, dustrelay, pool, child_state, child_wtxid));
163 BOOST_CHECK(child_state.IsValid());
164 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
165
166 auto dust_non_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);
167
168 // Child spending non-dust only from parent should be disallowed even if dust otherwise spent
169 const auto dust_non_spend_wtxid{dust_non_spend->GetWitnessHash()};
170 BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend, dust_spend}, dustrelay, pool, child_state, child_wtxid));
171 BOOST_CHECK(!child_state.IsValid());
172 BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
173 child_state = TxValidationState();
174 child_wtxid = Wtxid();
175
176 BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_spend, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));
177 BOOST_CHECK(!child_state.IsValid());
178 BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
179 child_state = TxValidationState();
180 child_wtxid = Wtxid();
181
182 BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));
183 BOOST_CHECK(!child_state.IsValid());
184 BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
185 child_state = TxValidationState();
186 child_wtxid = Wtxid();
187
188 auto grandparent_tx_2 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);
189 const auto dust_txid_2 = grandparent_tx_2->GetHash();
190
191 // Spend dust from one but not another is ok, as long as second grandparent has no child
192 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend}, dustrelay, pool, child_state, child_wtxid));
193 BOOST_CHECK(child_state.IsValid());
194 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
195
196 auto dust_non_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);
197 // But if we spend from the parent, it must spend dust
198 BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
199 BOOST_CHECK(!child_state.IsValid());
200 BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());
201 child_state = TxValidationState();
202 child_wtxid = Wtxid();
203
204 auto dust_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
205 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
206 BOOST_CHECK(child_state.IsValid());
207 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
208
209 // Spending other outputs is also correct, as long as the dusty one is spent
210 const std::vector<COutPoint> all_outpoints{COutPoint(dust_txid, 0), COutPoint(dust_txid, 1), COutPoint(dust_txid, 2),
211 COutPoint(dust_txid_2, 0), COutPoint(dust_txid_2, 1), COutPoint(dust_txid_2, 2)};
212 auto dust_spend_all_outpoints = make_tx(all_outpoints, /*version=*/2);
213 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_all_outpoints}, dustrelay, pool, child_state, child_wtxid));
214 BOOST_CHECK(child_state.IsValid());
215 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
216
217 // 2 grandparents with dust <- 1 dust-spending parent with dust <- child with no dust
218 auto parent_with_dust = make_ephemeral_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
219 // Ok for parent to have dust
220 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
221 BOOST_CHECK(child_state.IsValid());
222 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
223 auto child_no_dust = make_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);
224 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_no_dust}, dustrelay, pool, child_state, child_wtxid));
225 BOOST_CHECK(child_state.IsValid());
226 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
227
228 // 2 grandparents with dust <- 1 dust-spending parent with dust <- child with dust
229 auto child_with_dust = make_ephemeral_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);
230 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_with_dust}, dustrelay, pool, child_state, child_wtxid));
231 BOOST_CHECK(child_state.IsValid());
232 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
233
234 // Tests with parents in mempool
235
236 // Nothing in mempool, this should pass for any transaction
237 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));
238 BOOST_CHECK(child_state.IsValid());
239 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
240
241 // Add first grandparent to mempool and fetch entry
242 AddToMempool(pool, entry.FromTx(grandparent_tx_1));
243
244 // Ignores ancestors that aren't direct parents
245 BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));
246 BOOST_CHECK(child_state.IsValid());
247 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
248
249 // Valid spend of dust with grandparent in mempool
250 BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
251 BOOST_CHECK(child_state.IsValid());
252 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
253
254 // Second grandparent in same package
255 BOOST_CHECK(CheckEphemeralSpends({parent_with_dust, grandparent_tx_2}, dustrelay, pool, child_state, child_wtxid));
256 BOOST_CHECK(child_state.IsValid());
257 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
258
259 // Order in package doesn't matter
260 BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
261 BOOST_CHECK(child_state.IsValid());
262 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
263
264 // Add second grandparent to mempool
265 AddToMempool(pool, entry.FromTx(grandparent_tx_2));
266
267 // Only spends single dust out of two direct parents
268 BOOST_CHECK(!CheckEphemeralSpends({dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
269 BOOST_CHECK(!child_state.IsValid());
270 BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());
271 child_state = TxValidationState();
272 child_wtxid = Wtxid();
273
274 // Spends both parents' dust
275 BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
276 BOOST_CHECK(child_state.IsValid());
277 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
278
279 // Now add dusty parent to mempool
280 AddToMempool(pool, entry.FromTx(parent_with_dust));
281
282 // Passes dust checks even with non-parent ancestors
283 BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));
284 BOOST_CHECK(child_state.IsValid());
285 BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
286 }
287
288 BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
289 {
290 // Test TRUC policy helper functions
291 CTxMemPool& pool = *Assert(m_node.mempool);
292 LOCK2(cs_main, pool.cs);
293 TestMemPoolEntryHelper entry;
294 std::set<Txid> empty_conflicts_set;
295 CTxMemPool::setEntries empty_ancestors;
296
297 auto mempool_tx_v3 = make_tx(random_outpoints(1), /*version=*/3);
298 AddToMempool(pool, entry.FromTx(mempool_tx_v3));
299 auto mempool_tx_v2 = make_tx(random_outpoints(1), /*version=*/2);
300 AddToMempool(pool, entry.FromTx(mempool_tx_v2));
301 // Default values.
302 CTxMemPool::Limits m_limits{};
303
304 // Cannot spend from an unconfirmed TRUC transaction unless this tx is also TRUC.
305 {
306 // mempool_tx_v3
307 // ^
308 // tx_v2_from_v3
309 auto tx_v2_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/2);
310 auto ancestors_v2_from_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v3), m_limits)};
311 const auto expected_error_str{strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
312 tx_v2_from_v3->GetHash().ToString(), tx_v2_from_v3->GetWitnessHash().ToString(),
313 mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
314 auto result_v2_from_v3{SingleTRUCChecks(tx_v2_from_v3, *ancestors_v2_from_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v3))};
315 BOOST_CHECK_EQUAL(result_v2_from_v3->first, expected_error_str);
316 BOOST_CHECK_EQUAL(result_v2_from_v3->second, nullptr);
317
318 Package package_v3_v2{mempool_tx_v3, tx_v2_from_v3};
319 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), package_v3_v2, empty_ancestors), expected_error_str);
320 CTxMemPool::setEntries entries_mempool_v3{pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value()};
321 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), {tx_v2_from_v3}, entries_mempool_v3), expected_error_str);
322
323 // mempool_tx_v3 mempool_tx_v2
324 // ^ ^
325 // tx_v2_from_v2_and_v3
326 auto tx_v2_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
327 auto ancestors_v2_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2_and_v3), m_limits)};
328 const auto expected_error_str_2{strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
329 tx_v2_from_v2_and_v3->GetHash().ToString(), tx_v2_from_v2_and_v3->GetWitnessHash().ToString(),
330 mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
331 auto result_v2_from_both{SingleTRUCChecks(tx_v2_from_v2_and_v3, *ancestors_v2_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3))};
332 BOOST_CHECK_EQUAL(result_v2_from_both->first, expected_error_str_2);
333 BOOST_CHECK_EQUAL(result_v2_from_both->second, nullptr);
334
335 Package package_v3_v2_v2{mempool_tx_v3, mempool_tx_v2, tx_v2_from_v2_and_v3};
336 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v2_from_v2_and_v3, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3), package_v3_v2_v2, empty_ancestors), expected_error_str_2);
337 }
338
339 // TRUC cannot spend from an unconfirmed non-TRUC transaction.
340 {
341 // mempool_tx_v2
342 // ^
343 // tx_v3_from_v2
344 auto tx_v3_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
345 auto ancestors_v3_from_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2), m_limits)};
346 const auto expected_error_str{strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
347 tx_v3_from_v2->GetHash().ToString(), tx_v3_from_v2->GetWitnessHash().ToString(),
348 mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
349 auto result_v3_from_v2{SingleTRUCChecks(tx_v3_from_v2, *ancestors_v3_from_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2))};
350 BOOST_CHECK_EQUAL(result_v3_from_v2->first, expected_error_str);
351 BOOST_CHECK_EQUAL(result_v3_from_v2->second, nullptr);
352
353 Package package_v2_v3{mempool_tx_v2, tx_v3_from_v2};
354 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), package_v2_v3, empty_ancestors), expected_error_str);
355 CTxMemPool::setEntries entries_mempool_v2{pool.GetIter(mempool_tx_v2->GetHash().ToUint256()).value()};
356 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), {tx_v3_from_v2}, entries_mempool_v2), expected_error_str);
357
358 // mempool_tx_v3 mempool_tx_v2
359 // ^ ^
360 // tx_v3_from_v2_and_v3
361 auto tx_v3_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
362 auto ancestors_v3_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2_and_v3), m_limits)};
363 const auto expected_error_str_2{strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
364 tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString(),
365 mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
366 auto result_v3_from_both{SingleTRUCChecks(tx_v3_from_v2_and_v3, *ancestors_v3_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3))};
367 BOOST_CHECK_EQUAL(result_v3_from_both->first, expected_error_str_2);
368 BOOST_CHECK_EQUAL(result_v3_from_both->second, nullptr);
369
370 // tx_v3_from_v2_and_v3 also violates TRUC_ANCESTOR_LIMIT.
371 const auto expected_error_str_3{strprintf("tx %s (wtxid=%s) would have too many ancestors",
372 tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString())};
373 Package package_v3_v2_v3{mempool_tx_v3, mempool_tx_v2, tx_v3_from_v2_and_v3};
374 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_from_v2_and_v3, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3), package_v3_v2_v3, empty_ancestors), expected_error_str_3);
375 }
376 // V3 from V3 is ok, and non-V3 from non-V3 is ok.
377 {
378 // mempool_tx_v3
379 // ^
380 // tx_v3_from_v3
381 auto tx_v3_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
382 auto ancestors_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v3), m_limits)};
383 BOOST_CHECK(SingleTRUCChecks(tx_v3_from_v3, *ancestors_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v3))
384 == std::nullopt);
385
386 Package package_v3_v3{mempool_tx_v3, tx_v3_from_v3};
387 BOOST_CHECK(PackageTRUCChecks(tx_v3_from_v3, GetVirtualTransactionSize(*tx_v3_from_v3), package_v3_v3, empty_ancestors) == std::nullopt);
388
389 // mempool_tx_v2
390 // ^
391 // tx_v2_from_v2
392 auto tx_v2_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
393 auto ancestors_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2), m_limits)};
394 BOOST_CHECK(SingleTRUCChecks(tx_v2_from_v2, *ancestors_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2))
395 == std::nullopt);
396
397 Package package_v2_v2{mempool_tx_v2, tx_v2_from_v2};
398 BOOST_CHECK(PackageTRUCChecks(tx_v2_from_v2, GetVirtualTransactionSize(*tx_v2_from_v2), package_v2_v2, empty_ancestors) == std::nullopt);
399 }
400
401 // Tx spending TRUC cannot have too many mempool ancestors
402 // Configuration where the tx has multiple direct parents.
403 {
404 Package package_multi_parents;
405 std::vector<COutPoint> mempool_outpoints;
406 mempool_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
407 package_multi_parents.emplace_back(mempool_tx_v3);
408 for (size_t i{0}; i < 2; ++i) {
409 auto mempool_tx = make_tx(random_outpoints(i + 1), /*version=*/3);
410 AddToMempool(pool, entry.FromTx(mempool_tx));
411 mempool_outpoints.emplace_back(mempool_tx->GetHash(), 0);
412 package_multi_parents.emplace_back(mempool_tx);
413 }
414 auto tx_v3_multi_parent = make_tx(mempool_outpoints, /*version=*/3);
415 package_multi_parents.emplace_back(tx_v3_multi_parent);
416 auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_parent), m_limits)};
417 BOOST_CHECK_EQUAL(ancestors->size(), 3);
418 const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
419 tx_v3_multi_parent->GetHash().ToString(), tx_v3_multi_parent->GetWitnessHash().ToString())};
420 auto result{SingleTRUCChecks(tx_v3_multi_parent, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_parent))};
421 BOOST_CHECK_EQUAL(result->first, expected_error_str);
422 BOOST_CHECK_EQUAL(result->second, nullptr);
423
424 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_multi_parent, GetVirtualTransactionSize(*tx_v3_multi_parent), package_multi_parents, empty_ancestors),
425 expected_error_str);
426 }
427
428 // Configuration where the tx is in a multi-generation chain.
429 {
430 Package package_multi_gen;
431 CTransactionRef middle_tx;
432 auto last_outpoint{random_outpoints(1)[0]};
433 for (size_t i{0}; i < 2; ++i) {
434 auto mempool_tx = make_tx({last_outpoint}, /*version=*/3);
435 AddToMempool(pool, entry.FromTx(mempool_tx));
436 last_outpoint = COutPoint{mempool_tx->GetHash(), 0};
437 package_multi_gen.emplace_back(mempool_tx);
438 if (i == 1) middle_tx = mempool_tx;
439 }
440 auto tx_v3_multi_gen = make_tx({last_outpoint}, /*version=*/3);
441 package_multi_gen.emplace_back(tx_v3_multi_gen);
442 auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_gen), m_limits)};
443 const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
444 tx_v3_multi_gen->GetHash().ToString(), tx_v3_multi_gen->GetWitnessHash().ToString())};
445 auto result{SingleTRUCChecks(tx_v3_multi_gen, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_gen))};
446 BOOST_CHECK_EQUAL(result->first, expected_error_str);
447 BOOST_CHECK_EQUAL(result->second, nullptr);
448
449 // Middle tx is what triggers a failure for the grandchild:
450 BOOST_CHECK_EQUAL(*PackageTRUCChecks(middle_tx, GetVirtualTransactionSize(*middle_tx), package_multi_gen, empty_ancestors), expected_error_str);
451 BOOST_CHECK(PackageTRUCChecks(tx_v3_multi_gen, GetVirtualTransactionSize(*tx_v3_multi_gen), package_multi_gen, empty_ancestors) == std::nullopt);
452 }
453
454 // Tx spending TRUC cannot be too large in virtual size.
455 auto many_inputs{random_outpoints(100)};
456 many_inputs.emplace_back(mempool_tx_v3->GetHash(), 0);
457 {
458 auto tx_v3_child_big = make_tx(many_inputs, /*version=*/3);
459 const auto vsize{GetVirtualTransactionSize(*tx_v3_child_big)};
460 auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child_big), m_limits)};
461 const auto expected_error_str{strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
462 tx_v3_child_big->GetHash().ToString(), tx_v3_child_big->GetWitnessHash().ToString(), vsize, TRUC_CHILD_MAX_VSIZE)};
463 auto result{SingleTRUCChecks(tx_v3_child_big, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child_big))};
464 BOOST_CHECK_EQUAL(result->first, expected_error_str);
465 BOOST_CHECK_EQUAL(result->second, nullptr);
466
467 Package package_child_big{mempool_tx_v3, tx_v3_child_big};
468 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_child_big, GetVirtualTransactionSize(*tx_v3_child_big), package_child_big, empty_ancestors),
469 expected_error_str);
470 }
471
472 // Tx spending TRUC cannot have too many sigops.
473 // This child has 10 P2WSH multisig inputs.
474 auto multisig_outpoints{random_outpoints(10)};
475 multisig_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
476 auto keys{random_keys(2)};
477 CScript script_multisig;
478 script_multisig << OP_1;
479 for (const auto& key : keys) {
480 script_multisig << ToByteVector(key);
481 }
482 script_multisig << OP_2 << OP_CHECKMULTISIG;
483 {
484 CMutableTransaction mtx_many_sigops = CMutableTransaction{};
485 mtx_many_sigops.version = TRUC_VERSION;
486 for (const auto& outpoint : multisig_outpoints) {
487 mtx_many_sigops.vin.emplace_back(outpoint);
488 mtx_many_sigops.vin.back().scriptWitness.stack.emplace_back(script_multisig.begin(), script_multisig.end());
489 }
490 mtx_many_sigops.vout.resize(1);
491 mtx_many_sigops.vout.back().scriptPubKey = CScript() << OP_TRUE;
492 mtx_many_sigops.vout.back().nValue = 10000;
493 auto tx_many_sigops{MakeTransactionRef(mtx_many_sigops)};
494
495 auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_many_sigops), m_limits)};
496 // legacy uses fAccurate = false, and the maximum number of multisig keys is used
497 const int64_t total_sigops{static_cast<int64_t>(tx_many_sigops->vin.size()) * static_cast<int64_t>(script_multisig.GetSigOpCount(/*fAccurate=*/false))};
498 BOOST_CHECK_EQUAL(total_sigops, tx_many_sigops->vin.size() * MAX_PUBKEYS_PER_MULTISIG);
499 const int64_t bip141_vsize{GetVirtualTransactionSize(*tx_many_sigops)};
500 // Weight limit is not reached...
501 BOOST_CHECK(SingleTRUCChecks(tx_many_sigops, *ancestors, empty_conflicts_set, bip141_vsize) == std::nullopt);
502 // ...but sigop limit is.
503 const auto expected_error_str{strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
504 tx_many_sigops->GetHash().ToString(), tx_many_sigops->GetWitnessHash().ToString(),
505 total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, TRUC_CHILD_MAX_VSIZE)};
506 auto result{SingleTRUCChecks(tx_many_sigops, *ancestors, empty_conflicts_set,
507 GetVirtualTransactionSize(*tx_many_sigops, /*nSigOpCost=*/total_sigops, /*bytes_per_sigop=*/ DEFAULT_BYTES_PER_SIGOP))};
508 BOOST_CHECK_EQUAL(result->first, expected_error_str);
509 BOOST_CHECK_EQUAL(result->second, nullptr);
510
511 Package package_child_sigops{mempool_tx_v3, tx_many_sigops};
512 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_many_sigops, total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, package_child_sigops, empty_ancestors),
513 expected_error_str);
514 }
515
516 // Parent + child with TRUC in the mempool. Child is allowed as long as it is under TRUC_CHILD_MAX_VSIZE.
517 auto tx_mempool_v3_child = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
518 {
519 BOOST_CHECK(GetTransactionWeight(*tx_mempool_v3_child) <= TRUC_CHILD_MAX_VSIZE * WITNESS_SCALE_FACTOR);
520 auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_mempool_v3_child), m_limits)};
521 BOOST_CHECK(SingleTRUCChecks(tx_mempool_v3_child, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_mempool_v3_child)) == std::nullopt);
522 AddToMempool(pool, entry.FromTx(tx_mempool_v3_child));
523
524 Package package_v3_1p1c{mempool_tx_v3, tx_mempool_v3_child};
525 BOOST_CHECK(PackageTRUCChecks(tx_mempool_v3_child, GetVirtualTransactionSize(*tx_mempool_v3_child), package_v3_1p1c, empty_ancestors) == std::nullopt);
526 }
527
528 // A TRUC transaction cannot have more than 1 descendant. Sibling is returned when exactly 1 exists.
529 {
530 auto tx_v3_child2 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 1}}, /*version=*/3);
531
532 // Configuration where parent already has 1 other child in mempool
533 auto ancestors_1sibling{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child2), m_limits)};
534 const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
535 mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
536 auto result_with_sibling_eviction{SingleTRUCChecks(tx_v3_child2, *ancestors_1sibling, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child2))};
537 BOOST_CHECK_EQUAL(result_with_sibling_eviction->first, expected_error_str);
538 // The other mempool child is returned to allow for sibling eviction.
539 BOOST_CHECK_EQUAL(result_with_sibling_eviction->second, tx_mempool_v3_child);
540
541 // If directly replacing the child, make sure there is no double-counting.
542 BOOST_CHECK(SingleTRUCChecks(tx_v3_child2, *ancestors_1sibling, {tx_mempool_v3_child->GetHash()}, GetVirtualTransactionSize(*tx_v3_child2))
543 == std::nullopt);
544
545 Package package_v3_1p2c{mempool_tx_v3, tx_mempool_v3_child, tx_v3_child2};
546 BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_child2, GetVirtualTransactionSize(*tx_v3_child2), package_v3_1p2c, empty_ancestors),
547 expected_error_str);
548
549 // Configuration where parent already has 2 other children in mempool (no sibling eviction allowed). This may happen as the result of a reorg.
550 AddToMempool(pool, entry.FromTx(tx_v3_child2));
551 auto tx_v3_child3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 24}}, /*version=*/3);
552 auto entry_mempool_parent = pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value();
553 BOOST_CHECK_EQUAL(entry_mempool_parent->GetCountWithDescendants(), 3);
554 auto ancestors_2siblings{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child3), m_limits)};
555
556 auto result_2children{SingleTRUCChecks(tx_v3_child3, *ancestors_2siblings, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child3))};
557 BOOST_CHECK_EQUAL(result_2children->first, expected_error_str);
558 // The other mempool child is not returned because sibling eviction is not allowed.
559 BOOST_CHECK_EQUAL(result_2children->second, nullptr);
560 }
561
562 // Sibling eviction: parent already has 1 other child, which also has its own child (no sibling eviction allowed). This may happen as the result of a reorg.
563 {
564 auto tx_mempool_grandparent = make_tx(random_outpoints(1), /*version=*/3);
565 auto tx_mempool_sibling = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 0}}, /*version=*/3);
566 auto tx_mempool_nibling = make_tx({COutPoint{tx_mempool_sibling->GetHash(), 0}}, /*version=*/3);
567 auto tx_to_submit = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 1}}, /*version=*/3);
568
569 AddToMempool(pool, entry.FromTx(tx_mempool_grandparent));
570 AddToMempool(pool, entry.FromTx(tx_mempool_sibling));
571 AddToMempool(pool, entry.FromTx(tx_mempool_nibling));
572
573 auto ancestors_3gen{pool.CalculateMemPoolAncestors(entry.FromTx(tx_to_submit), m_limits)};
574 const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
575 tx_mempool_grandparent->GetHash().ToString(), tx_mempool_grandparent->GetWitnessHash().ToString())};
576 auto result_3gen{SingleTRUCChecks(tx_to_submit, *ancestors_3gen, empty_conflicts_set, GetVirtualTransactionSize(*tx_to_submit))};
577 BOOST_CHECK_EQUAL(result_3gen->first, expected_error_str);
578 // The other mempool child is not returned because sibling eviction is not allowed.
579 BOOST_CHECK_EQUAL(result_3gen->second, nullptr);
580 }
581
582 // Configuration where tx has multiple generations of descendants is not tested because that is
583 // equivalent to the tx with multiple generations of ancestors.
584 }
585
586 BOOST_AUTO_TEST_SUITE_END()
587