Branch data 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 : : #include <node/mini_miner.h>
5 : : #include <random.h>
6 : : #include <txmempool.h>
7 : : #include <util/time.h>
8 : :
9 : : #include <test/util/setup_common.h>
10 : : #include <test/util/txmempool.h>
11 : :
12 : : #include <boost/test/unit_test.hpp>
13 : : #include <optional>
14 : : #include <vector>
15 : :
16 : : BOOST_FIXTURE_TEST_SUITE(miniminer_tests, TestingSetup)
17 : :
18 : : const CAmount low_fee{CENT/2000}; // 500 ṩ
19 : : const CAmount med_fee{CENT/200}; // 5000 ṩ
20 : : const CAmount high_fee{CENT/10}; // 100_000 ṩ
21 : :
22 : :
23 : 624 : static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, size_t num_outputs)
24 : : {
25 : 624 : CMutableTransaction tx = CMutableTransaction();
26 [ + - ]: 624 : tx.vin.resize(inputs.size());
27 [ + - ]: 624 : tx.vout.resize(num_outputs);
28 [ + + ]: 1301 : for (size_t i = 0; i < inputs.size(); ++i) {
29 : 677 : tx.vin[i].prevout = inputs[i];
30 : : }
31 [ + + ]: 1312 : for (size_t i = 0; i < num_outputs; ++i) {
32 [ + - + - ]: 688 : tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
33 : : // The actual input and output values of these transactions don't really
34 : : // matter, since all accounting will use the entries' cached fees.
35 : 688 : tx.vout[i].nValue = COIN;
36 : : }
37 [ + - ]: 1248 : return MakeTransactionRef(tx);
38 : 624 : }
39 : :
40 : 51 : static inline bool sanity_check(const std::vector<CTransactionRef>& transactions,
41 : : const std::map<COutPoint, CAmount>& bumpfees)
42 : : {
43 : : // No negative bumpfees.
44 [ + - + + ]: 345 : for (const auto& [outpoint, fee] : bumpfees) {
45 [ + - ]: 294 : if (fee < 0) return false;
46 [ + + ]: 294 : if (fee == 0) continue;
47 : 66 : auto outpoint_ = outpoint; // structured bindings can't be captured in C++17, so we need to use a variable
48 [ + + + - ]: 357 : const bool found = std::any_of(transactions.cbegin(), transactions.cend(), [&](const auto& tx) {
49 [ + + - + ]: 291 : return outpoint_.hash == tx->GetHash() && outpoint_.n < tx->vout.size();
50 : : });
51 [ + - ]: 66 : if (!found) return false;
52 : : }
53 [ + + ]: 459 : for (const auto& tx : transactions) {
54 : : // If tx has multiple outputs, they must all have the same bumpfee (if they exist).
55 [ + + ]: 408 : if (tx->vout.size() > 1) {
56 : 216 : std::set<CAmount> distinct_bumpfees;
57 [ + + ]: 654 : for (size_t i{0}; i < tx->vout.size(); ++i) {
58 : 438 : const auto bumpfee = bumpfees.find(COutPoint{tx->GetHash(), static_cast<uint32_t>(i)});
59 [ + + + - ]: 438 : if (bumpfee != bumpfees.end()) distinct_bumpfees.insert(bumpfee->second);
60 : : }
61 [ - + ]: 216 : if (distinct_bumpfees.size() > 1) return false;
62 : 216 : }
63 : : }
64 : : return true;
65 : : }
66 : :
67 : : template <typename Key, typename Value>
68 : 140 : Value Find(const std::map<Key, Value>& map, const Key& key)
69 : : {
70 : 140 : auto it = map.find(key);
71 [ + - + - ]: 280 : BOOST_CHECK_MESSAGE(it != map.end(), strprintf("Cannot find %s", key.ToString()));
72 : 140 : return it->second;
73 : : }
74 : :
75 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(miniminer_negative, TestChain100Setup)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
76 : : {
77 : 1 : CTxMemPool& pool = *Assert(m_node.mempool);
78 [ + - ]: 1 : LOCK2(::cs_main, pool.cs);
79 : 1 : TestMemPoolEntryHelper entry;
80 : :
81 : : // Create a transaction that will be prioritised to have a negative modified fee.
82 : 1 : const CAmount positive_base_fee{1000};
83 : 1 : const CAmount negative_fee_delta{-50000};
84 : 1 : const CAmount negative_modified_fees{positive_base_fee + negative_fee_delta};
85 [ + - + - : 2 : BOOST_CHECK(negative_modified_fees < 0);
+ - ]
86 [ + - + - : 2 : const auto tx_mod_negative = make_tx({COutPoint{m_coinbase_txns[4]->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
87 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(positive_base_fee).FromTx(tx_mod_negative));
88 [ + - ]: 1 : pool.PrioritiseTransaction(tx_mod_negative->GetHash(), negative_fee_delta);
89 [ + - ]: 1 : const COutPoint only_outpoint{tx_mod_negative->GetHash(), 0};
90 : :
91 : : // When target feerate is 0, transactions with negative fees are not selected.
92 [ + - + - : 2 : node::MiniMiner mini_miner_target0(pool, {only_outpoint});
+ - ]
93 [ + - + - : 2 : BOOST_CHECK(mini_miner_target0.IsReadyToCalculate());
+ - ]
94 [ + - ]: 1 : const CFeeRate feerate_zero(0);
95 [ + - ]: 1 : mini_miner_target0.BuildMockTemplate(feerate_zero);
96 : : // Check the quit condition:
97 [ + - + - : 2 : BOOST_CHECK(negative_modified_fees < feerate_zero.GetFee(Assert(pool.GetEntry(tx_mod_negative->GetHash()))->GetTxSize()));
+ - + - +
- + - +
- ]
98 [ + - + - : 3 : BOOST_CHECK(mini_miner_target0.GetMockTemplateTxids().empty());
+ - + - ]
99 : :
100 : : // With no target feerate, the template includes all transactions, even negative feerate ones.
101 [ + - + - : 2 : node::MiniMiner mini_miner_no_target(pool, {only_outpoint});
+ - ]
102 [ + - + - : 2 : BOOST_CHECK(mini_miner_no_target.IsReadyToCalculate());
+ - ]
103 [ + - ]: 1 : mini_miner_no_target.BuildMockTemplate(std::nullopt);
104 [ + - ]: 1 : const auto template_txids{mini_miner_no_target.GetMockTemplateTxids()};
105 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(template_txids.size(), 1);
106 [ + - + - ]: 2 : BOOST_CHECK(template_txids.count(tx_mod_negative->GetHash().ToUint256()) > 0);
107 [ + - + - : 4 : }
+ - ]
108 : :
109 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(miniminer_1p1c, TestChain100Setup)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
110 : : {
111 : 1 : CTxMemPool& pool = *Assert(m_node.mempool);
112 [ + - ]: 1 : LOCK2(::cs_main, pool.cs);
113 : 1 : TestMemPoolEntryHelper entry;
114 : :
115 : : // Create a parent tx0 and child tx1 with normal fees:
116 [ + - + - : 2 : const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
117 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(med_fee).FromTx(tx0));
118 [ + - + - : 2 : const auto tx1 = make_tx({COutPoint{tx0->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
119 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
120 : :
121 : : // Create a low-feerate parent tx2 and high-feerate child tx3 (cpfp)
122 [ + - + - : 2 : const auto tx2 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
123 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(low_fee).FromTx(tx2));
124 [ + - + - : 2 : const auto tx3 = make_tx({COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
125 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
126 : :
127 : : // Create a parent tx4 and child tx5 where both have low fees
128 [ + - + - : 2 : const auto tx4 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
129 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(low_fee).FromTx(tx4));
130 [ + - + - : 2 : const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
131 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
132 : 1 : const CAmount tx5_delta{CENT/100};
133 : : // Make tx5's modified fee much higher than its base fee. This should cause it to pass
134 : : // the fee-related checks despite being low-feerate.
135 [ + - ]: 1 : pool.PrioritiseTransaction(tx5->GetHash(), tx5_delta);
136 : 1 : const CAmount tx5_mod_fee{low_fee + tx5_delta};
137 : :
138 : : // Create a high-feerate parent tx6, low-feerate child tx7
139 [ + - + - : 2 : const auto tx6 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
140 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(high_fee).FromTx(tx6));
141 [ + - + - : 2 : const auto tx7 = make_tx({COutPoint{tx6->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
142 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(low_fee).FromTx(tx7));
143 : :
144 [ + - ]: 1 : std::vector<COutPoint> all_unspent_outpoints({
145 : : COutPoint{tx0->GetHash(), 1},
146 : : COutPoint{tx1->GetHash(), 0},
147 : : COutPoint{tx2->GetHash(), 1},
148 : : COutPoint{tx3->GetHash(), 0},
149 : : COutPoint{tx4->GetHash(), 1},
150 : : COutPoint{tx5->GetHash(), 0},
151 : : COutPoint{tx6->GetHash(), 1},
152 : : COutPoint{tx7->GetHash(), 0}
153 [ + - ]: 1 : });
154 [ + - + - : 9 : for (const auto& outpoint : all_unspent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
+ - + + ]
155 : :
156 [ + - ]: 1 : std::vector<COutPoint> all_spent_outpoints({
157 : : COutPoint{tx0->GetHash(), 0},
158 : : COutPoint{tx2->GetHash(), 0},
159 : : COutPoint{tx4->GetHash(), 0},
160 : : COutPoint{tx6->GetHash(), 0}
161 [ + - ]: 1 : });
162 [ + - + - : 5 : for (const auto& outpoint : all_spent_outpoints) BOOST_CHECK(pool.GetConflictTx(outpoint) != nullptr);
+ - + + ]
163 : :
164 [ + - ]: 1 : std::vector<COutPoint> all_parent_outputs({
165 : : COutPoint{tx0->GetHash(), 0},
166 : : COutPoint{tx0->GetHash(), 1},
167 : : COutPoint{tx2->GetHash(), 0},
168 : : COutPoint{tx2->GetHash(), 1},
169 : : COutPoint{tx4->GetHash(), 0},
170 : : COutPoint{tx4->GetHash(), 1},
171 : : COutPoint{tx6->GetHash(), 0},
172 : : COutPoint{tx6->GetHash(), 1}
173 [ + - ]: 1 : });
174 : :
175 : :
176 [ + + + - : 9 : std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
- - - - ]
177 : 1 : struct TxDimensions {
178 : : int32_t vsize; CAmount mod_fee; CFeeRate feerate;
179 : : };
180 : 1 : std::map<uint256, TxDimensions> tx_dims;
181 [ + + ]: 9 : for (const auto& tx : all_transactions) {
182 [ + - + - ]: 8 : const auto& entry{*Assert(pool.GetEntry(tx->GetHash()))};
183 [ + - + - : 8 : tx_dims.emplace(tx->GetHash(), TxDimensions{entry.GetTxSize(), entry.GetModifiedFee(),
+ - + - ]
184 [ + - + - ]: 8 : CFeeRate(entry.GetModifiedFee(), entry.GetTxSize())});
185 : : }
186 : :
187 [ + - ]: 1 : const std::vector<CFeeRate> various_normal_feerates({CFeeRate(0), CFeeRate(500), CFeeRate(999),
188 : : CFeeRate(1000), CFeeRate(2000), CFeeRate(2500),
189 : : CFeeRate(3333), CFeeRate(7800), CFeeRate(11199),
190 [ + - ]: 1 : CFeeRate(23330), CFeeRate(50000), CFeeRate(5*CENT)});
191 : :
192 : : // All nonexistent entries have a bumpfee of zero, regardless of feerate
193 [ + - ]: 1 : std::vector<COutPoint> nonexistent_outpoints({ COutPoint{Txid::FromUint256(GetRandHash()), 0}, COutPoint{Txid::FromUint256(GetRandHash()), 3} });
194 [ + - + - : 3 : for (const auto& outpoint : nonexistent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
+ - + + ]
195 [ + + ]: 13 : for (const auto& feerate : various_normal_feerates) {
196 [ + - ]: 12 : node::MiniMiner mini_miner(pool, nonexistent_outpoints);
197 [ + - + - : 24 : BOOST_CHECK(mini_miner.IsReadyToCalculate());
+ - ]
198 [ + - ]: 12 : auto bump_fees = mini_miner.CalculateBumpFees(feerate);
199 [ + - + - : 24 : BOOST_CHECK(!mini_miner.IsReadyToCalculate());
+ - ]
200 [ + - + - : 24 : BOOST_CHECK(sanity_check(all_transactions, bump_fees));
+ - + - ]
201 [ + - + - ]: 24 : BOOST_CHECK(bump_fees.size() == nonexistent_outpoints.size());
202 [ + + ]: 36 : for (const auto& outpoint: nonexistent_outpoints) {
203 : 24 : auto it = bump_fees.find(outpoint);
204 [ + - + - : 48 : BOOST_CHECK(it != bump_fees.end());
+ - ]
205 [ + - + - ]: 24 : BOOST_CHECK_EQUAL(it->second, 0);
206 : : }
207 : 12 : }
208 : :
209 : : // Gather bump fees for all available UTXOs.
210 [ + + ]: 13 : for (const auto& target_feerate : various_normal_feerates) {
211 [ + - ]: 12 : node::MiniMiner mini_miner(pool, all_unspent_outpoints);
212 [ + - + - : 24 : BOOST_CHECK(mini_miner.IsReadyToCalculate());
+ - ]
213 [ + - ]: 12 : auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
214 [ + - + - : 24 : BOOST_CHECK(!mini_miner.IsReadyToCalculate());
+ - ]
215 [ + - + - : 24 : BOOST_CHECK(sanity_check(all_transactions, bump_fees));
+ - + - ]
216 [ + - + - ]: 12 : BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
217 : :
218 : : // Check tx0 bumpfee: no other bumper.
219 [ + - ]: 12 : const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
220 [ + - ]: 12 : CAmount bumpfee0 = Find(bump_fees, COutPoint{tx0->GetHash(), 1});
221 [ + + ]: 12 : if (target_feerate <= tx0_dimensions.feerate) {
222 [ + - + - ]: 11 : BOOST_CHECK_EQUAL(bumpfee0, 0);
223 : : } else {
224 : : // Difference is fee to bump tx0 from current to target feerate.
225 [ + - + - : 1 : BOOST_CHECK_EQUAL(bumpfee0, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
+ - ]
226 : : }
227 : :
228 : : // Check tx2 bumpfee: assisted by tx3.
229 : 12 : const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
230 [ + - ]: 12 : const TxDimensions& tx3_dimensions = tx_dims.find(tx3->GetHash())->second;
231 [ + - ]: 12 : const CFeeRate tx2_feerate = CFeeRate(tx2_dimensions.mod_fee + tx3_dimensions.mod_fee, tx2_dimensions.vsize + tx3_dimensions.vsize);
232 [ + - ]: 12 : CAmount bumpfee2 = Find(bump_fees, COutPoint{tx2->GetHash(), 1});
233 [ + + ]: 12 : if (target_feerate <= tx2_feerate) {
234 : : // As long as target feerate is below tx3's ancestor feerate, there is no bump fee.
235 [ + - + - ]: 11 : BOOST_CHECK_EQUAL(bumpfee2, 0);
236 : : } else {
237 : : // Difference is fee to bump tx2 from current to target feerate, without tx3.
238 [ + - + - : 1 : BOOST_CHECK_EQUAL(bumpfee2, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
+ - ]
239 : : }
240 : :
241 : : // If tx5’s modified fees are sufficient for tx4 and tx5 to be picked
242 : : // into the block, our prospective new transaction would not need to
243 : : // bump tx4 when using tx4’s second output. If however even tx5’s
244 : : // modified fee (which essentially indicates "effective feerate") is
245 : : // not sufficient to bump tx4, using the second output of tx4 would
246 : : // require our transaction to bump tx4 from scratch since we evaluate
247 : : // transaction packages per ancestor sets and do not consider multiple
248 : : // children’s fees.
249 : 12 : const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
250 [ + - ]: 12 : const TxDimensions& tx5_dimensions = tx_dims.find(tx5->GetHash())->second;
251 [ + - ]: 12 : const CFeeRate tx4_feerate = CFeeRate(tx4_dimensions.mod_fee + tx5_dimensions.mod_fee, tx4_dimensions.vsize + tx5_dimensions.vsize);
252 [ + - ]: 12 : CAmount bumpfee4 = Find(bump_fees, COutPoint{tx4->GetHash(), 1});
253 [ + + ]: 12 : if (target_feerate <= tx4_feerate) {
254 : : // As long as target feerate is below tx5's ancestor feerate, there is no bump fee.
255 [ + - + - ]: 11 : BOOST_CHECK_EQUAL(bumpfee4, 0);
256 : : } else {
257 : : // Difference is fee to bump tx4 from current to target feerate, without tx5.
258 [ + - + - : 1 : BOOST_CHECK_EQUAL(bumpfee4, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
+ - ]
259 : : }
260 : 12 : }
261 : : // Spent outpoints should usually not be requested as they would not be
262 : : // considered available. However, when they are explicitly requested, we
263 : : // can calculate their bumpfee to facilitate RBF-replacements
264 [ + + ]: 13 : for (const auto& target_feerate : various_normal_feerates) {
265 [ + - ]: 12 : node::MiniMiner mini_miner_all_spent(pool, all_spent_outpoints);
266 [ + - + - : 24 : BOOST_CHECK(mini_miner_all_spent.IsReadyToCalculate());
+ - ]
267 [ + - ]: 12 : auto bump_fees_all_spent = mini_miner_all_spent.CalculateBumpFees(target_feerate);
268 [ + - + - : 24 : BOOST_CHECK(!mini_miner_all_spent.IsReadyToCalculate());
+ - ]
269 [ + - + - ]: 12 : BOOST_CHECK_EQUAL(bump_fees_all_spent.size(), all_spent_outpoints.size());
270 [ + - ]: 12 : node::MiniMiner mini_miner_all_parents(pool, all_parent_outputs);
271 [ + - + - : 24 : BOOST_CHECK(mini_miner_all_parents.IsReadyToCalculate());
+ - ]
272 [ + - ]: 12 : auto bump_fees_all_parents = mini_miner_all_parents.CalculateBumpFees(target_feerate);
273 [ + - + - : 24 : BOOST_CHECK(!mini_miner_all_parents.IsReadyToCalculate());
+ - ]
274 [ + - + - ]: 12 : BOOST_CHECK_EQUAL(bump_fees_all_parents.size(), all_parent_outputs.size());
275 [ + - + - : 60 : for (auto& bump_fees : {bump_fees_all_parents, bump_fees_all_spent}) {
+ + - - ]
276 : : // For all_parents case, both outputs from the parent should have the same bump fee,
277 : : // even though only one of them is in a to-be-replaced transaction.
278 [ + - + - : 48 : BOOST_CHECK(sanity_check(all_transactions, bump_fees));
+ - ]
279 : :
280 : : // Check tx0 bumpfee: no other bumper.
281 [ + - ]: 24 : const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
282 [ + - ]: 24 : CAmount it0_spent = Find(bump_fees, COutPoint{tx0->GetHash(), 0});
283 [ + + ]: 24 : if (target_feerate <= tx0_dimensions.feerate) {
284 [ + - + - ]: 22 : BOOST_CHECK_EQUAL(it0_spent, 0);
285 : : } else {
286 : : // Difference is fee to bump tx0 from current to target feerate.
287 [ + - + - : 2 : BOOST_CHECK_EQUAL(it0_spent, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
+ - ]
288 : : }
289 : :
290 : : // Check tx2 bumpfee: no other bumper, because tx3 is to-be-replaced.
291 [ + - ]: 24 : const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
292 : 24 : const CFeeRate tx2_feerate_unbumped = tx2_dimensions.feerate;
293 [ + - ]: 24 : auto it2_spent = Find(bump_fees, COutPoint{tx2->GetHash(), 0});
294 [ + + ]: 24 : if (target_feerate <= tx2_feerate_unbumped) {
295 [ + - + - ]: 14 : BOOST_CHECK_EQUAL(it2_spent, 0);
296 : : } else {
297 : : // Difference is fee to bump tx2 from current to target feerate, without tx3.
298 [ + - + - : 10 : BOOST_CHECK_EQUAL(it2_spent, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
+ - ]
299 : : }
300 : :
301 : : // Check tx4 bumpfee: no other bumper, because tx5 is to-be-replaced.
302 [ + - ]: 24 : const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
303 : 24 : const CFeeRate tx4_feerate_unbumped = tx4_dimensions.feerate;
304 [ + - ]: 24 : auto it4_spent = Find(bump_fees, COutPoint{tx4->GetHash(), 0});
305 [ + + ]: 24 : if (target_feerate <= tx4_feerate_unbumped) {
306 [ + - + - ]: 14 : BOOST_CHECK_EQUAL(it4_spent, 0);
307 : : } else {
308 : : // Difference is fee to bump tx4 from current to target feerate, without tx5.
309 [ + - + - : 10 : BOOST_CHECK_EQUAL(it4_spent, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
+ - ]
310 : : }
311 [ + + - - ]: 36 : }
312 : 12 : }
313 : :
314 : : // Check m_inclusion_order for equivalent mempool- and manually-constructed MiniMiners.
315 : : // (We cannot check bump fees in manually-constructed MiniMiners because it doesn't know what
316 : : // outpoints are requested).
317 : 1 : std::vector<node::MiniMinerMempoolEntry> miniminer_info;
318 : 1 : {
319 [ + - ]: 1 : const int32_t tx0_vsize{tx_dims.at(tx0->GetHash()).vsize};
320 [ + - ]: 1 : const int32_t tx1_vsize{tx_dims.at(tx1->GetHash()).vsize};
321 [ + - ]: 1 : const int32_t tx2_vsize{tx_dims.at(tx2->GetHash()).vsize};
322 [ + - ]: 1 : const int32_t tx3_vsize{tx_dims.at(tx3->GetHash()).vsize};
323 [ + - ]: 1 : const int32_t tx4_vsize{tx_dims.at(tx4->GetHash()).vsize};
324 [ + - ]: 1 : const int32_t tx5_vsize{tx_dims.at(tx5->GetHash()).vsize};
325 [ + - ]: 1 : const int32_t tx6_vsize{tx_dims.at(tx6->GetHash()).vsize};
326 [ + - ]: 1 : const int32_t tx7_vsize{tx_dims.at(tx7->GetHash()).vsize};
327 : :
328 [ + - ]: 1 : miniminer_info.emplace_back(tx0,/*vsize_self=*/tx0_vsize,/*vsize_ancestor=*/tx0_vsize,/*fee_self=*/med_fee,/*fee_ancestor=*/med_fee);
329 [ + - ]: 1 : miniminer_info.emplace_back(tx1, tx1_vsize, tx0_vsize + tx1_vsize, med_fee, 2*med_fee);
330 [ + - ]: 1 : miniminer_info.emplace_back(tx2, tx2_vsize, tx2_vsize, low_fee, low_fee);
331 [ + - ]: 1 : miniminer_info.emplace_back(tx3, tx3_vsize, tx2_vsize + tx3_vsize, high_fee, low_fee + high_fee);
332 [ + - ]: 1 : miniminer_info.emplace_back(tx4, tx4_vsize, tx4_vsize, low_fee, low_fee);
333 [ + - ]: 1 : miniminer_info.emplace_back(tx5, tx5_vsize, tx4_vsize + tx5_vsize, tx5_mod_fee, low_fee + tx5_mod_fee);
334 [ + - ]: 1 : miniminer_info.emplace_back(tx6, tx6_vsize, tx6_vsize, high_fee, high_fee);
335 [ + - ]: 1 : miniminer_info.emplace_back(tx7, tx7_vsize, tx6_vsize + tx7_vsize, low_fee, high_fee + low_fee);
336 : : }
337 [ + - ]: 1 : std::map<Txid, std::set<Txid>> descendant_caches;
338 [ + - + - ]: 1 : descendant_caches.emplace(tx0->GetHash(), std::set<Txid>{tx0->GetHash(), tx1->GetHash()});
339 [ + - + - ]: 1 : descendant_caches.emplace(tx1->GetHash(), std::set<Txid>{tx1->GetHash()});
340 [ + - + - ]: 1 : descendant_caches.emplace(tx2->GetHash(), std::set<Txid>{tx2->GetHash(), tx3->GetHash()});
341 [ + - + - ]: 1 : descendant_caches.emplace(tx3->GetHash(), std::set<Txid>{tx3->GetHash()});
342 [ + - + - ]: 1 : descendant_caches.emplace(tx4->GetHash(), std::set<Txid>{tx4->GetHash(), tx5->GetHash()});
343 [ + - + - : 1 : descendant_caches.emplace(tx5->GetHash(), std::set<Txid>{tx5->GetHash()});
+ - ]
344 [ + - + - ]: 1 : descendant_caches.emplace(tx6->GetHash(), std::set<Txid>{tx6->GetHash(), tx7->GetHash()});
345 [ + - + - ]: 1 : descendant_caches.emplace(tx7->GetHash(), std::set<Txid>{tx7->GetHash()});
346 : :
347 [ + - ]: 1 : node::MiniMiner miniminer_manual(miniminer_info, descendant_caches);
348 : : // Use unspent outpoints to avoid entries being omitted.
349 [ + - ]: 1 : node::MiniMiner miniminer_pool(pool, all_unspent_outpoints);
350 [ + - + - : 2 : BOOST_CHECK(miniminer_manual.IsReadyToCalculate());
+ - ]
351 [ + - + - : 2 : BOOST_CHECK(miniminer_pool.IsReadyToCalculate());
+ - ]
352 [ + - + - : 5 : for (const auto& sequences : {miniminer_manual.Linearize(), miniminer_pool.Linearize()}) {
+ + - - ]
353 : : // tx6 is selected first: high feerate with no parents to bump
354 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx6->GetHash()), 0);
+ - ]
355 : :
356 : : // tx2 + tx3 CPFP are selected next
357 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx2->GetHash()), 1);
+ - ]
358 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx3->GetHash()), 1);
+ - ]
359 : :
360 : : // tx4 + prioritised tx5 CPFP
361 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx4->GetHash()), 2);
+ - ]
362 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx5->GetHash()), 2);
+ - ]
363 : :
364 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx0->GetHash()), 3);
+ - ]
365 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx1->GetHash()), 3);
+ - ]
366 : :
367 : :
368 : : // tx7 is selected last: low feerate with no children
369 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx7->GetHash()), 4);
+ - ]
370 [ + + - - ]: 3 : }
371 [ + - + - : 20 : }
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - + -
+ - + - ]
372 : :
373 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(miniminer_overlap, TestChain100Setup)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
374 : : {
375 : : /* Tx graph for `miniminer_overlap` unit test:
376 : : *
377 : : * coinbase_tx [mined] ... block-chain
378 : : * -------------------------------------------------
379 : : * / | \ \ ... mempool
380 : : * / | \ |
381 : : * tx0 tx1 tx2 tx4
382 : : * [low] [med] [high] [high]
383 : : * \ | / |
384 : : * \ | / tx5
385 : : * \ | / [low]
386 : : * tx3 / \
387 : : * [high] tx6 tx7
388 : : * [med] [high]
389 : : *
390 : : * NOTE:
391 : : * -> "low"/"med"/"high" denote the _absolute_ fee of each tx
392 : : * -> tx3 has 3 inputs and 3 outputs, all other txs have 1 input and 2 outputs
393 : : * -> tx3's feerate is lower than tx2's, as tx3 has more weight (due to having more inputs and outputs)
394 : : *
395 : : * -> tx2_FR = high / tx2_vsize
396 : : * -> tx3_FR = high / tx3_vsize
397 : : * -> tx3_ASFR = (low+med+high+high) / (tx0_vsize + tx1_vsize + tx2_vsize + tx3_vsize)
398 : : * -> tx4_FR = high / tx4_vsize
399 : : * -> tx6_ASFR = (high+low+med) / (tx4_vsize + tx5_vsize + tx6_vsize)
400 : : * -> tx7_ASFR = (high+low+high) / (tx4_vsize + tx5_vsize + tx7_vsize) */
401 : :
402 : 1 : CTxMemPool& pool = *Assert(m_node.mempool);
403 [ + - ]: 1 : LOCK2(::cs_main, pool.cs);
404 : 1 : TestMemPoolEntryHelper entry;
405 : :
406 : : // Create 3 parents of different feerates, and 1 child spending outputs from all 3 parents.
407 [ + - + - : 2 : const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
408 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(low_fee).FromTx(tx0));
409 [ + - + - : 2 : const auto tx1 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
410 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
411 [ + - + - : 2 : const auto tx2 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
412 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(high_fee).FromTx(tx2));
413 [ + - + - : 2 : const auto tx3 = make_tx({COutPoint{tx0->GetHash(), 0}, COutPoint{tx1->GetHash(), 0}, COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/3);
+ - ]
414 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
415 : :
416 : : // Create 1 grandparent and 1 parent, then 2 children.
417 [ + - + - : 2 : const auto tx4 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
418 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
419 [ + - + - : 2 : const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/3);
+ - ]
420 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
421 [ + - + - : 2 : const auto tx6 = make_tx({COutPoint{tx5->GetHash(), 0}}, /*num_outputs=*/2);
+ - ]
422 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(med_fee).FromTx(tx6));
423 [ + - + - : 2 : const auto tx7 = make_tx({COutPoint{tx5->GetHash(), 1}}, /*num_outputs=*/2);
+ - ]
424 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
425 : :
426 [ + + + - : 9 : std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
- - - - ]
427 : 1 : std::vector<int64_t> tx_vsizes;
428 [ + - ]: 1 : tx_vsizes.reserve(all_transactions.size());
429 [ + - + - : 9 : for (const auto& tx : all_transactions) tx_vsizes.push_back(GetVirtualTransactionSize(*tx));
+ + ]
430 : :
431 [ + - ]: 1 : std::vector<COutPoint> all_unspent_outpoints({
432 : : COutPoint{tx0->GetHash(), 1},
433 : : COutPoint{tx1->GetHash(), 1},
434 : : COutPoint{tx2->GetHash(), 1},
435 : : COutPoint{tx3->GetHash(), 0},
436 : : COutPoint{tx3->GetHash(), 1},
437 : : COutPoint{tx3->GetHash(), 2},
438 : : COutPoint{tx4->GetHash(), 1},
439 : : COutPoint{tx5->GetHash(), 2},
440 : : COutPoint{tx6->GetHash(), 0},
441 : : COutPoint{tx7->GetHash(), 0}
442 [ + - ]: 1 : });
443 [ + - + - : 11 : for (const auto& outpoint : all_unspent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
+ - + + ]
444 : :
445 [ + - ]: 1 : const auto tx2_feerate = CFeeRate(high_fee, tx_vsizes[2]);
446 [ + - ]: 1 : const auto tx3_feerate = CFeeRate(high_fee, tx_vsizes[3]);
447 : : // tx3's feerate is lower than tx2's. same fee, different weight.
448 [ + - + - : 2 : BOOST_CHECK(tx2_feerate > tx3_feerate);
+ - ]
449 [ + - ]: 1 : const auto tx3_anc_feerate = CFeeRate(low_fee + med_fee + high_fee + high_fee, tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]);
450 [ + - + - ]: 1 : const auto& tx3_entry{*Assert(pool.GetEntry(tx3->GetHash()))};
451 [ + - + - : 2 : BOOST_CHECK(tx3_anc_feerate == CFeeRate(tx3_entry.GetModFeesWithAncestors(), tx3_entry.GetSizeWithAncestors()));
+ - + - ]
452 [ + - ]: 1 : const auto tx4_feerate = CFeeRate(high_fee, tx_vsizes[4]);
453 [ + - ]: 1 : const auto tx6_anc_feerate = CFeeRate(high_fee + low_fee + med_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]);
454 [ + - + - ]: 1 : const auto& tx6_entry{*Assert(pool.GetEntry(tx6->GetHash()))};
455 [ + - + - : 2 : BOOST_CHECK(tx6_anc_feerate == CFeeRate(tx6_entry.GetModFeesWithAncestors(), tx6_entry.GetSizeWithAncestors()));
+ - + - ]
456 [ + - ]: 1 : const auto tx7_anc_feerate = CFeeRate(high_fee + low_fee + high_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[7]);
457 [ + - + - ]: 1 : const auto& tx7_entry{*Assert(pool.GetEntry(tx7->GetHash()))};
458 [ + - + - : 2 : BOOST_CHECK(tx7_anc_feerate == CFeeRate(tx7_entry.GetModFeesWithAncestors(), tx7_entry.GetSizeWithAncestors()));
+ - + - ]
459 [ + - + - : 2 : BOOST_CHECK(tx4_feerate > tx6_anc_feerate);
+ - ]
460 [ + - + - : 2 : BOOST_CHECK(tx4_feerate > tx7_anc_feerate);
+ - ]
461 : :
462 : : // Extremely high feerate: everybody's bumpfee is from their full ancestor set.
463 : 1 : {
464 [ + - ]: 1 : node::MiniMiner mini_miner(pool, all_unspent_outpoints);
465 [ + - ]: 1 : const CFeeRate very_high_feerate(COIN);
466 [ + - + - : 2 : BOOST_CHECK(tx3_anc_feerate < very_high_feerate);
+ - ]
467 [ + - + - : 2 : BOOST_CHECK(mini_miner.IsReadyToCalculate());
+ - ]
468 [ + - ]: 1 : auto bump_fees = mini_miner.CalculateBumpFees(very_high_feerate);
469 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
470 [ + - + - : 2 : BOOST_CHECK(!mini_miner.IsReadyToCalculate());
+ - ]
471 [ + - + - : 2 : BOOST_CHECK(sanity_check(all_transactions, bump_fees));
+ - ]
472 : 1 : const auto tx0_bumpfee = bump_fees.find(COutPoint{tx0->GetHash(), 1});
473 [ + - + - : 2 : BOOST_CHECK(tx0_bumpfee != bump_fees.end());
+ - ]
474 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx0_bumpfee->second, very_high_feerate.GetFee(tx_vsizes[0]) - low_fee);
+ - ]
475 : 1 : const auto tx3_bumpfee = bump_fees.find(COutPoint{tx3->GetHash(), 0});
476 [ + - + - : 2 : BOOST_CHECK(tx3_bumpfee != bump_fees.end());
+ - ]
477 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx3_bumpfee->second,
+ - ]
478 : : very_high_feerate.GetFee(tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]) - (low_fee + med_fee + high_fee + high_fee));
479 : 1 : const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
480 [ + - + - : 2 : BOOST_CHECK(tx6_bumpfee != bump_fees.end());
+ - ]
481 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx6_bumpfee->second,
+ - ]
482 : : very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]) - (high_fee + low_fee + med_fee));
483 : 1 : const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
484 [ + - + - : 2 : BOOST_CHECK(tx7_bumpfee != bump_fees.end());
+ - ]
485 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx7_bumpfee->second,
+ - ]
486 : : very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[7]) - (high_fee + low_fee + high_fee));
487 : : // Total fees: if spending multiple outputs from tx3 don't double-count fees.
488 [ + - + - : 2 : node::MiniMiner mini_miner_total_tx3(pool, {COutPoint{tx3->GetHash(), 0}, COutPoint{tx3->GetHash(), 1}});
+ - ]
489 [ + - + - : 2 : BOOST_CHECK(mini_miner_total_tx3.IsReadyToCalculate());
+ - ]
490 [ + - ]: 1 : const auto tx3_bump_fee = mini_miner_total_tx3.CalculateTotalBumpFees(very_high_feerate);
491 [ + - + - : 2 : BOOST_CHECK(!mini_miner_total_tx3.IsReadyToCalculate());
+ - ]
492 [ + - + - : 2 : BOOST_CHECK(tx3_bump_fee.has_value());
+ - ]
493 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx3_bump_fee.value(),
+ - + - ]
494 : : very_high_feerate.GetFee(tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]) - (low_fee + med_fee + high_fee + high_fee));
495 : : // Total fees: if spending both tx6 and tx7, don't double-count fees.
496 [ + - + - : 2 : node::MiniMiner mini_miner_tx6_tx7(pool, {COutPoint{tx6->GetHash(), 0}, COutPoint{tx7->GetHash(), 0}});
+ - ]
497 [ + - + - : 2 : BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
+ - ]
498 [ + - ]: 1 : const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(very_high_feerate);
499 [ + - + - : 2 : BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
+ - ]
500 [ + - + - : 2 : BOOST_CHECK(tx6_tx7_bumpfee.has_value());
+ - ]
501 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx6_tx7_bumpfee.value(),
+ - + - ]
502 : : very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6] + tx_vsizes[7]) - (high_fee + low_fee + med_fee + high_fee));
503 : 1 : }
504 : : // Feerate just below tx4: tx6 and tx7 have different bump fees.
505 : 1 : {
506 [ + - ]: 1 : const auto just_below_tx4 = CFeeRate(tx4_feerate.GetFeePerK() - 5);
507 [ + - ]: 1 : node::MiniMiner mini_miner(pool, all_unspent_outpoints);
508 [ + - + - : 2 : BOOST_CHECK(mini_miner.IsReadyToCalculate());
+ - ]
509 [ + - ]: 1 : auto bump_fees = mini_miner.CalculateBumpFees(just_below_tx4);
510 [ + - + - : 2 : BOOST_CHECK(!mini_miner.IsReadyToCalculate());
+ - ]
511 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
512 [ + - + - : 2 : BOOST_CHECK(sanity_check(all_transactions, bump_fees));
+ - ]
513 : 1 : const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
514 [ + - + - : 2 : BOOST_CHECK(tx6_bumpfee != bump_fees.end());
+ - ]
515 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx6_bumpfee->second, just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[6]) - (low_fee + med_fee));
+ - ]
516 : 1 : const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
517 [ + - + - : 2 : BOOST_CHECK(tx7_bumpfee != bump_fees.end());
+ - ]
518 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx7_bumpfee->second, just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[7]) - (low_fee + high_fee));
+ - ]
519 : : // Total fees: if spending both tx6 and tx7, don't double-count fees.
520 [ + - + - : 2 : node::MiniMiner mini_miner_tx6_tx7(pool, {COutPoint{tx6->GetHash(), 0}, COutPoint{tx7->GetHash(), 0}});
+ - ]
521 [ + - + - : 2 : BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
+ - ]
522 [ + - ]: 1 : const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(just_below_tx4);
523 [ + - + - : 2 : BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
+ - ]
524 [ + - + - : 2 : BOOST_CHECK(tx6_tx7_bumpfee.has_value());
+ - ]
525 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx6_tx7_bumpfee.value(), just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[6]) - (low_fee + med_fee));
+ - + - ]
526 : 1 : }
527 : : // Feerate between tx6 and tx7's ancestor feerates: don't need to bump tx5 because tx7 already does.
528 : 1 : {
529 [ + - ]: 1 : const auto just_above_tx6 = CFeeRate(med_fee + 10, tx_vsizes[6]);
530 [ + - + - : 2 : BOOST_CHECK(just_above_tx6 <= CFeeRate(low_fee + high_fee, tx_vsizes[5] + tx_vsizes[7]));
+ - + - ]
531 [ + - ]: 1 : node::MiniMiner mini_miner(pool, all_unspent_outpoints);
532 [ + - + - : 2 : BOOST_CHECK(mini_miner.IsReadyToCalculate());
+ - ]
533 [ + - ]: 1 : auto bump_fees = mini_miner.CalculateBumpFees(just_above_tx6);
534 [ + - + - : 2 : BOOST_CHECK(!mini_miner.IsReadyToCalculate());
+ - ]
535 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
536 [ + - + - : 2 : BOOST_CHECK(sanity_check(all_transactions, bump_fees));
+ - ]
537 : 1 : const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
538 [ + - + - : 2 : BOOST_CHECK(tx6_bumpfee != bump_fees.end());
+ - ]
539 [ + - + - : 1 : BOOST_CHECK_EQUAL(tx6_bumpfee->second, just_above_tx6.GetFee(tx_vsizes[6]) - (med_fee));
+ - ]
540 : 1 : const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
541 [ + - + - : 2 : BOOST_CHECK(tx7_bumpfee != bump_fees.end());
+ - ]
542 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(tx7_bumpfee->second, 0);
543 : 1 : }
544 : : // Check linearization order
545 : 1 : std::vector<node::MiniMinerMempoolEntry> miniminer_info;
546 [ + - ]: 1 : miniminer_info.emplace_back(tx0,/*vsize_self=*/tx_vsizes[0], /*vsize_ancestor=*/tx_vsizes[0], /*fee_self=*/low_fee, /*fee_ancestor=*/low_fee);
547 [ + - ]: 1 : miniminer_info.emplace_back(tx1, tx_vsizes[1], tx_vsizes[1], med_fee, med_fee);
548 [ + - ]: 1 : miniminer_info.emplace_back(tx2, tx_vsizes[2], tx_vsizes[2], high_fee, high_fee);
549 [ + - ]: 1 : miniminer_info.emplace_back(tx3, tx_vsizes[3], tx_vsizes[0]+tx_vsizes[1]+tx_vsizes[2]+tx_vsizes[3], high_fee, low_fee+med_fee+2*high_fee);
550 [ + - ]: 1 : miniminer_info.emplace_back(tx4, tx_vsizes[4], tx_vsizes[4], high_fee, high_fee);
551 [ + - ]: 1 : miniminer_info.emplace_back(tx5, tx_vsizes[5], tx_vsizes[4]+tx_vsizes[5], low_fee, low_fee + high_fee);
552 [ + - ]: 1 : miniminer_info.emplace_back(tx6, tx_vsizes[6], tx_vsizes[4]+tx_vsizes[5]+tx_vsizes[6], med_fee, high_fee+low_fee+med_fee);
553 [ + - ]: 1 : miniminer_info.emplace_back(tx7, tx_vsizes[7], tx_vsizes[4]+tx_vsizes[5]+tx_vsizes[7], high_fee, high_fee+low_fee+high_fee);
554 : :
555 [ + - ]: 1 : std::map<Txid, std::set<Txid>> descendant_caches;
556 [ + - + - ]: 1 : descendant_caches.emplace(tx0->GetHash(), std::set<Txid>{tx0->GetHash(), tx3->GetHash()});
557 [ + - + - ]: 1 : descendant_caches.emplace(tx1->GetHash(), std::set<Txid>{tx1->GetHash(), tx3->GetHash()});
558 [ + - + - ]: 1 : descendant_caches.emplace(tx2->GetHash(), std::set<Txid>{tx2->GetHash(), tx3->GetHash()});
559 [ + - + - ]: 1 : descendant_caches.emplace(tx3->GetHash(), std::set<Txid>{tx3->GetHash()});
560 [ + - + - ]: 1 : descendant_caches.emplace(tx4->GetHash(), std::set<Txid>{tx4->GetHash(), tx5->GetHash(), tx6->GetHash(), tx7->GetHash()});
561 [ + - + - ]: 1 : descendant_caches.emplace(tx5->GetHash(), std::set<Txid>{tx5->GetHash(), tx6->GetHash(), tx7->GetHash()});
562 [ + - + - ]: 1 : descendant_caches.emplace(tx6->GetHash(), std::set<Txid>{tx6->GetHash()});
563 [ + - + - ]: 1 : descendant_caches.emplace(tx7->GetHash(), std::set<Txid>{tx7->GetHash()});
564 : :
565 [ + - ]: 1 : node::MiniMiner miniminer_manual(miniminer_info, descendant_caches);
566 : : // Use unspent outpoints to avoid entries being omitted.
567 [ + - ]: 1 : node::MiniMiner miniminer_pool(pool, all_unspent_outpoints);
568 [ + - + - : 2 : BOOST_CHECK(miniminer_manual.IsReadyToCalculate());
+ - ]
569 [ + - + - : 2 : BOOST_CHECK(miniminer_pool.IsReadyToCalculate());
+ - ]
570 [ + - + - : 5 : for (const auto& sequences : {miniminer_manual.Linearize(), miniminer_pool.Linearize()}) {
+ + - - ]
571 : : // tx2 and tx4 selected first: high feerate with nothing to bump
572 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx4->GetHash()), 0);
+ - ]
573 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx2->GetHash()), 1);
+ - ]
574 : :
575 : : // tx5 + tx7 CPFP
576 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx5->GetHash()), 2);
+ - ]
577 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx7->GetHash()), 2);
+ - ]
578 : :
579 : : // tx0 and tx1 CPFP'd by tx3
580 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx0->GetHash()), 3);
+ - ]
581 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx1->GetHash()), 3);
+ - ]
582 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx3->GetHash()), 3);
+ - ]
583 : :
584 : : // tx6 at medium feerate
585 [ + - + - : 2 : BOOST_CHECK_EQUAL(Find(sequences, tx6->GetHash()), 4);
+ - ]
586 [ + + - - ]: 3 : }
587 [ + - + - : 20 : }
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - + -
+ - + - ]
588 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(calculate_cluster, TestChain100Setup)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
589 : : {
590 : 1 : CTxMemPool& pool = *Assert(m_node.mempool);
591 [ + - ]: 1 : LOCK2(cs_main, pool.cs);
592 : :
593 : : // TODO this can be removed once the mempool interface uses Txid, Wtxid
594 : 3 : auto convert_to_uint256_vec = [](const std::vector<Txid>& vec) -> std::vector<uint256> {
595 : 2 : std::vector<uint256> out;
596 [ + - ]: 2 : std::transform(vec.begin(), vec.end(), std::back_inserter(out),
597 : 599 : [](const Txid& txid) { return txid.ToUint256(); });
598 : 2 : return out;
599 : 0 : };
600 : :
601 : : // Add chain of size 500
602 : 1 : TestMemPoolEntryHelper entry;
603 : 1 : std::vector<Txid> chain_txids;
604 : 1 : auto& lasttx = m_coinbase_txns[0];
605 [ + + ]: 501 : for (auto i{0}; i < 500; ++i) {
606 [ + - + - : 1000 : const auto tx = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
607 [ + - + - ]: 500 : pool.addUnchecked(entry.Fee(CENT).FromTx(tx));
608 [ + - ]: 500 : chain_txids.push_back(tx->GetHash());
609 [ + - ]: 500 : lasttx = tx;
610 : 500 : }
611 [ + - + - : 2 : const auto cluster_500tx = pool.GatherClusters({lasttx->GetHash()});
+ - ]
612 [ + - ]: 1 : CTxMemPool::setEntries cluster_500tx_set{cluster_500tx.begin(), cluster_500tx.end()};
613 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cluster_500tx.size(), cluster_500tx_set.size());
614 [ + - + - ]: 1 : const auto vec_iters_500 = pool.GetIterVec(convert_to_uint256_vec(chain_txids));
615 [ + - + - : 501 : for (const auto& iter : vec_iters_500) BOOST_CHECK(cluster_500tx_set.count(iter));
+ + ]
616 : :
617 : : // GatherClusters stops at 500 transactions.
618 [ + - + - : 2 : const auto tx_501 = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
+ - ]
619 [ + - + - ]: 1 : pool.addUnchecked(entry.Fee(CENT).FromTx(tx_501));
620 [ + - + - : 2 : const auto cluster_501 = pool.GatherClusters({tx_501->GetHash()});
+ - ]
621 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cluster_501.size(), 0);
622 : :
623 : : /* Zig Zag cluster:
624 : : * txp0 txp1 txp2 ... txp48 txp49
625 : : * \ / \ / \ \ /
626 : : * txc0 txc1 txc2 ... txc48
627 : : * Note that each transaction's ancestor size is 1 or 3, and each descendant size is 1, 2 or 3.
628 : : * However, all of these transactions are in the same cluster. */
629 : 1 : std::vector<Txid> zigzag_txids;
630 [ + + ]: 51 : for (auto p{0}; p < 50; ++p) {
631 [ + - + - : 100 : const auto txp = make_tx({COutPoint{Txid::FromUint256(GetRandHash()), 0}}, /*num_outputs=*/2);
+ - ]
632 [ + - + - ]: 50 : pool.addUnchecked(entry.Fee(CENT).FromTx(txp));
633 [ + - ]: 50 : zigzag_txids.push_back(txp->GetHash());
634 : 50 : }
635 [ + + ]: 50 : for (auto c{0}; c < 49; ++c) {
636 [ + - + - : 98 : const auto txc = make_tx({COutPoint{zigzag_txids[c], 1}, COutPoint{zigzag_txids[c+1], 0}}, /*num_outputs=*/1);
+ - ]
637 [ + - + - ]: 49 : pool.addUnchecked(entry.Fee(CENT).FromTx(txc));
638 [ + - ]: 49 : zigzag_txids.push_back(txc->GetHash());
639 : 49 : }
640 [ + - + - ]: 1 : const auto vec_iters_zigzag = pool.GetIterVec(convert_to_uint256_vec(zigzag_txids));
641 : : // It doesn't matter which tx we calculate cluster for, everybody is in it.
642 [ + - ]: 1 : const std::vector<size_t> indices{0, 22, 72, zigzag_txids.size() - 1};
643 [ + + ]: 5 : for (const auto index : indices) {
644 [ + - + - : 8 : const auto cluster = pool.GatherClusters({zigzag_txids[index]});
+ - ]
645 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(cluster.size(), zigzag_txids.size());
646 [ + - ]: 4 : CTxMemPool::setEntries clusterset{cluster.begin(), cluster.end()};
647 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(cluster.size(), clusterset.size());
648 [ + - + - : 400 : for (const auto& iter : vec_iters_zigzag) BOOST_CHECK(clusterset.count(iter));
+ + ]
649 : 4 : }
650 [ + - + - : 4 : }
+ - ]
651 : :
652 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(manual_ctor, TestChain100Setup)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
653 : : {
654 : 1 : CTxMemPool& pool = *Assert(m_node.mempool);
655 [ + - ]: 1 : LOCK2(cs_main, pool.cs);
656 : 1 : {
657 : : // 3 pairs of grandparent + fee-bumping parent, plus 1 low-feerate child.
658 : : // 0 fee + high fee
659 [ + - + - : 2 : auto grandparent_zero_fee = make_tx({{m_coinbase_txns.at(0)->GetHash(), 0}}, 1);
+ - + - ]
660 [ + - + - : 2 : auto parent_high_feerate = make_tx({{grandparent_zero_fee->GetHash(), 0}}, 1);
+ - ]
661 : : // double low fee + med fee
662 [ + - + - : 2 : auto grandparent_double_low_feerate = make_tx({{m_coinbase_txns.at(2)->GetHash(), 0}}, 1);
+ - + - ]
663 [ + - + - : 2 : auto parent_med_feerate = make_tx({{grandparent_double_low_feerate->GetHash(), 0}}, 1);
+ - ]
664 : : // low fee + double low fee
665 [ + - + - : 2 : auto grandparent_low_feerate = make_tx({{m_coinbase_txns.at(1)->GetHash(), 0}}, 1);
+ - + - ]
666 [ + - + - : 2 : auto parent_double_low_feerate = make_tx({{grandparent_low_feerate->GetHash(), 0}}, 1);
+ - ]
667 : : // child is below the cpfp package feerates because it is larger than everything else
668 [ + - + - : 2 : auto child = make_tx({{parent_high_feerate->GetHash(), 0}, {parent_double_low_feerate->GetHash(), 0}, {parent_med_feerate->GetHash(), 0}}, 1);
+ - ]
669 : :
670 : : // We artificially record each transaction (except the child) with a uniform vsize of 100vB.
671 : 1 : const int64_t tx_vsize{100};
672 : 1 : const int64_t child_vsize{1000};
673 : :
674 : 1 : std::vector<node::MiniMinerMempoolEntry> miniminer_info;
675 [ + - ]: 1 : miniminer_info.emplace_back(grandparent_zero_fee, /*vsize_self=*/tx_vsize,/*vsize_ancestor=*/tx_vsize, /*fee_self=*/0,/*fee_ancestor=*/0);
676 [ + - ]: 1 : miniminer_info.emplace_back(parent_high_feerate, tx_vsize, 2*tx_vsize, high_fee, high_fee);
677 [ + - ]: 1 : miniminer_info.emplace_back(grandparent_double_low_feerate, tx_vsize, tx_vsize, 2*low_fee, 2*low_fee);
678 [ + - ]: 1 : miniminer_info.emplace_back(parent_med_feerate, tx_vsize, 2*tx_vsize, med_fee, 2*low_fee+med_fee);
679 [ + - ]: 1 : miniminer_info.emplace_back(grandparent_low_feerate, tx_vsize, tx_vsize, low_fee, low_fee);
680 [ + - ]: 1 : miniminer_info.emplace_back(parent_double_low_feerate, tx_vsize, 2*tx_vsize, 2*low_fee, 3*low_fee);
681 [ + - ]: 1 : miniminer_info.emplace_back(child, child_vsize, 6*tx_vsize+child_vsize, low_fee, high_fee+med_fee+6*low_fee);
682 [ + - ]: 1 : std::map<Txid, std::set<Txid>> descendant_caches;
683 [ + - + - ]: 1 : descendant_caches.emplace(grandparent_zero_fee->GetHash(), std::set<Txid>{grandparent_zero_fee->GetHash(), parent_high_feerate->GetHash(), child->GetHash()});
684 [ + - + - ]: 1 : descendant_caches.emplace(grandparent_low_feerate->GetHash(), std::set<Txid>{grandparent_low_feerate->GetHash(), parent_double_low_feerate->GetHash(), child->GetHash()});
685 [ + - + - ]: 1 : descendant_caches.emplace(grandparent_double_low_feerate->GetHash(), std::set<Txid>{grandparent_double_low_feerate->GetHash(), parent_med_feerate->GetHash(), child->GetHash()});
686 [ + - + - ]: 1 : descendant_caches.emplace(parent_high_feerate->GetHash(), std::set<Txid>{parent_high_feerate->GetHash(), child->GetHash()});
687 [ + - + - ]: 1 : descendant_caches.emplace(parent_med_feerate->GetHash(), std::set<Txid>{parent_med_feerate->GetHash(), child->GetHash()});
688 [ + - + - ]: 1 : descendant_caches.emplace(parent_double_low_feerate->GetHash(), std::set<Txid>{parent_double_low_feerate->GetHash(), child->GetHash()});
689 [ + - + - ]: 1 : descendant_caches.emplace(child->GetHash(), std::set<Txid>{child->GetHash()});
690 : :
691 [ + - ]: 1 : node::MiniMiner miniminer_manual(miniminer_info, descendant_caches);
692 [ + - + - : 2 : BOOST_CHECK(miniminer_manual.IsReadyToCalculate());
+ - ]
693 [ + - ]: 1 : const auto sequences{miniminer_manual.Linearize()};
694 : :
695 : : // CPFP zero + high
696 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(grandparent_zero_fee->GetHash()), 0);
+ - ]
697 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(parent_high_feerate->GetHash()), 0);
+ - ]
698 : :
699 : : // CPFP double low + med
700 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(grandparent_double_low_feerate->GetHash()), 1);
+ - ]
701 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(parent_med_feerate->GetHash()), 1);
+ - ]
702 : :
703 : : // CPFP low + double low
704 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(grandparent_low_feerate->GetHash()), 2);
+ - ]
705 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(parent_double_low_feerate->GetHash()), 2);
+ - ]
706 : :
707 : : // Child at the end
708 [ + - + - : 1 : BOOST_CHECK_EQUAL(sequences.at(child->GetHash()), 3);
+ - ]
709 [ + - + - : 8 : }
+ - + - +
- + - + -
+ - ]
710 [ + - ]: 2 : }
711 : :
712 : : BOOST_AUTO_TEST_SUITE_END()
|