/root/bitcoin/src/node/mini_miner.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2023 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 <node/mini_miner.h> |
6 | | |
7 | | #include <boost/multi_index/detail/hash_index_iterator.hpp> |
8 | | #include <boost/operators.hpp> |
9 | | #include <consensus/amount.h> |
10 | | #include <policy/feerate.h> |
11 | | #include <primitives/transaction.h> |
12 | | #include <sync.h> |
13 | | #include <txmempool.h> |
14 | | #include <uint256.h> |
15 | | #include <util/check.h> |
16 | | |
17 | | #include <algorithm> |
18 | | #include <numeric> |
19 | | #include <utility> |
20 | | |
21 | | namespace node { |
22 | | |
23 | | MiniMiner::MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& outpoints) |
24 | 0 | { |
25 | 0 | LOCK(mempool.cs); |
26 | | // Find which outpoints to calculate bump fees for. |
27 | | // Anything that's spent by the mempool is to-be-replaced |
28 | | // Anything otherwise unavailable just has a bump fee of 0 |
29 | 0 | for (const auto& outpoint : outpoints) { Branch (29:31): [True: 0, False: 0]
|
30 | 0 | if (!mempool.exists(GenTxid::Txid(outpoint.hash))) { Branch (30:13): [True: 0, False: 0]
|
31 | | // This UTXO is either confirmed or not yet submitted to mempool. |
32 | | // If it's confirmed, no bump fee is required. |
33 | | // If it's not yet submitted, we have no information, so return 0. |
34 | 0 | m_bump_fees.emplace(outpoint, 0); |
35 | 0 | continue; |
36 | 0 | } |
37 | | |
38 | | // UXTO is created by transaction in mempool, add to map. |
39 | | // Note: This will either create a missing entry or add the outpoint to an existing entry |
40 | 0 | m_requested_outpoints_by_txid[outpoint.hash].push_back(outpoint); |
41 | |
|
42 | 0 | if (const auto ptx{mempool.GetConflictTx(outpoint)}) { Branch (42:24): [True: 0, False: 0]
|
43 | | // This outpoint is already being spent by another transaction in the mempool. We |
44 | | // assume that the caller wants to replace this transaction and its descendants. It |
45 | | // would be unusual for the transaction to have descendants as the wallet won’t normally |
46 | | // attempt to replace transactions with descendants. If the outpoint is from a mempool |
47 | | // transaction, we still need to calculate its ancestors bump fees (added to |
48 | | // m_requested_outpoints_by_txid below), but after removing the to-be-replaced entries. |
49 | | // |
50 | | // Note that the descendants of a transaction include the transaction itself. Also note, |
51 | | // that this is only calculating bump fees. RBF fee rules should be handled separately. |
52 | 0 | CTxMemPool::setEntries descendants; |
53 | 0 | mempool.CalculateDescendants(mempool.GetIter(ptx->GetHash()).value(), descendants); |
54 | 0 | for (const auto& desc_txiter : descendants) { Branch (54:42): [True: 0, False: 0]
|
55 | 0 | m_to_be_replaced.insert(desc_txiter->GetTx().GetHash()); |
56 | 0 | } |
57 | 0 | } |
58 | 0 | } |
59 | | |
60 | | // No unconfirmed UTXOs, so nothing mempool-related needs to be calculated. |
61 | 0 | if (m_requested_outpoints_by_txid.empty()) return; Branch (61:9): [True: 0, False: 0]
|
62 | | |
63 | | // Calculate the cluster and construct the entry map. |
64 | 0 | std::vector<uint256> txids_needed; |
65 | 0 | txids_needed.reserve(m_requested_outpoints_by_txid.size()); |
66 | 0 | for (const auto& [txid, _]: m_requested_outpoints_by_txid) { Branch (66:31): [True: 0, False: 0]
|
67 | 0 | txids_needed.push_back(txid); |
68 | 0 | } |
69 | 0 | const auto cluster = mempool.GatherClusters(txids_needed); |
70 | 0 | if (cluster.empty()) { Branch (70:9): [True: 0, False: 0]
|
71 | | // An empty cluster means that at least one of the transactions is missing from the mempool |
72 | | // (should not be possible given processing above) or DoS limit was hit. |
73 | 0 | m_ready_to_calculate = false; |
74 | 0 | return; |
75 | 0 | } |
76 | | |
77 | | // Add every entry to m_entries_by_txid and m_entries, except the ones that will be replaced. |
78 | 0 | for (const auto& txiter : cluster) { Branch (78:29): [True: 0, False: 0]
|
79 | 0 | if (!m_to_be_replaced.count(txiter->GetTx().GetHash())) { Branch (79:13): [True: 0, False: 0]
|
80 | 0 | auto [mapiter, success] = m_entries_by_txid.emplace(txiter->GetTx().GetHash(), |
81 | 0 | MiniMinerMempoolEntry{/*tx_in=*/txiter->GetSharedTx(), |
82 | 0 | /*vsize_self=*/txiter->GetTxSize(), |
83 | 0 | /*vsize_ancestor=*/txiter->GetSizeWithAncestors(), |
84 | 0 | /*fee_self=*/txiter->GetModifiedFee(), |
85 | 0 | /*fee_ancestor=*/txiter->GetModFeesWithAncestors()}); |
86 | 0 | m_entries.push_back(mapiter); |
87 | 0 | } else { |
88 | 0 | auto outpoints_it = m_requested_outpoints_by_txid.find(txiter->GetTx().GetHash()); |
89 | 0 | if (outpoints_it != m_requested_outpoints_by_txid.end()) { Branch (89:17): [True: 0, False: 0]
|
90 | | // This UTXO is the output of a to-be-replaced transaction. Bump fee is 0; spending |
91 | | // this UTXO is impossible as it will no longer exist after the replacement. |
92 | 0 | for (const auto& outpoint : outpoints_it->second) { Branch (92:43): [True: 0, False: 0]
|
93 | 0 | m_bump_fees.emplace(outpoint, 0); |
94 | 0 | } |
95 | 0 | m_requested_outpoints_by_txid.erase(outpoints_it); |
96 | 0 | } |
97 | 0 | } |
98 | 0 | } |
99 | | |
100 | | // Build the m_descendant_set_by_txid cache. |
101 | 0 | for (const auto& txiter : cluster) { Branch (101:29): [True: 0, False: 0]
|
102 | 0 | const auto& txid = txiter->GetTx().GetHash(); |
103 | | // Cache descendants for future use. Unlike the real mempool, a descendant MiniMinerMempoolEntry |
104 | | // will not exist without its ancestor MiniMinerMempoolEntry, so these sets won't be invalidated. |
105 | 0 | std::vector<MockEntryMap::iterator> cached_descendants; |
106 | 0 | const bool remove{m_to_be_replaced.count(txid) > 0}; |
107 | 0 | CTxMemPool::setEntries descendants; |
108 | 0 | mempool.CalculateDescendants(txiter, descendants); |
109 | 0 | Assume(descendants.count(txiter) > 0); |
110 | 0 | for (const auto& desc_txiter : descendants) { Branch (110:38): [True: 0, False: 0]
|
111 | 0 | const auto txid_desc = desc_txiter->GetTx().GetHash(); |
112 | 0 | const bool remove_desc{m_to_be_replaced.count(txid_desc) > 0}; |
113 | 0 | auto desc_it{m_entries_by_txid.find(txid_desc)}; |
114 | 0 | Assume((desc_it == m_entries_by_txid.end()) == remove_desc); |
115 | 0 | if (remove) Assume(remove_desc); Branch (115:17): [True: 0, False: 0]
|
116 | | // It's possible that remove=false but remove_desc=true. |
117 | 0 | if (!remove && !remove_desc) { Branch (117:17): [True: 0, False: 0]
Branch (117:28): [True: 0, False: 0]
|
118 | 0 | cached_descendants.push_back(desc_it); |
119 | 0 | } |
120 | 0 | } |
121 | 0 | if (remove) { Branch (121:13): [True: 0, False: 0]
|
122 | 0 | Assume(cached_descendants.empty()); |
123 | 0 | } else { |
124 | 0 | m_descendant_set_by_txid.emplace(txid, cached_descendants); |
125 | 0 | } |
126 | 0 | } |
127 | | |
128 | | // Release the mempool lock; we now have all the information we need for a subset of the entries |
129 | | // we care about. We will solely operate on the MiniMinerMempoolEntry map from now on. |
130 | 0 | Assume(m_in_block.empty()); |
131 | 0 | Assume(m_requested_outpoints_by_txid.size() <= outpoints.size()); |
132 | 0 | SanityCheck(); |
133 | 0 | } |
134 | | |
135 | | MiniMiner::MiniMiner(const std::vector<MiniMinerMempoolEntry>& manual_entries, |
136 | | const std::map<Txid, std::set<Txid>>& descendant_caches) |
137 | 0 | { |
138 | 0 | for (const auto& entry : manual_entries) { Branch (138:28): [True: 0, False: 0]
|
139 | 0 | const auto& txid = entry.GetTx().GetHash(); |
140 | | // We need to know the descendant set of every transaction. |
141 | 0 | if (!Assume(descendant_caches.count(txid) > 0)) { Branch (141:13): [True: 0, False: 0]
|
142 | 0 | m_ready_to_calculate = false; |
143 | 0 | return; |
144 | 0 | } |
145 | | // Just forward these args onto MiniMinerMempoolEntry |
146 | 0 | auto [mapiter, success] = m_entries_by_txid.emplace(txid, entry); |
147 | | // Txids must be unique; this txid shouldn't already be an entry in m_entries_by_txid |
148 | 0 | if (Assume(success)) m_entries.push_back(mapiter); |
149 | 0 | } |
150 | | // Descendant cache is already built, but we need to translate them to m_entries_by_txid iters. |
151 | 0 | for (const auto& [txid, desc_txids] : descendant_caches) { Branch (151:41): [True: 0, False: 0]
|
152 | | // Descendant cache should include at least the tx itself. |
153 | 0 | if (!Assume(!desc_txids.empty())) { Branch (153:13): [True: 0, False: 0]
|
154 | 0 | m_ready_to_calculate = false; |
155 | 0 | return; |
156 | 0 | } |
157 | 0 | std::vector<MockEntryMap::iterator> descendants; |
158 | 0 | for (const auto& desc_txid : desc_txids) { Branch (158:36): [True: 0, False: 0]
|
159 | 0 | auto desc_it{m_entries_by_txid.find(desc_txid)}; |
160 | | // Descendants should only include transactions with corresponding entries. |
161 | 0 | if (!Assume(desc_it != m_entries_by_txid.end())) { Branch (161:17): [True: 0, False: 0]
|
162 | 0 | m_ready_to_calculate = false; |
163 | 0 | return; |
164 | 0 | } else { |
165 | 0 | descendants.emplace_back(desc_it); |
166 | 0 | } |
167 | 0 | } |
168 | 0 | m_descendant_set_by_txid.emplace(txid, descendants); |
169 | 0 | } |
170 | 0 | Assume(m_to_be_replaced.empty()); |
171 | 0 | Assume(m_requested_outpoints_by_txid.empty()); |
172 | 0 | Assume(m_bump_fees.empty()); |
173 | 0 | Assume(m_inclusion_order.empty()); |
174 | 0 | SanityCheck(); |
175 | 0 | } |
176 | | |
177 | | // Compare by min(ancestor feerate, individual feerate), then txid |
178 | | // |
179 | | // Under the ancestor-based mining approach, high-feerate children can pay for parents, but high-feerate |
180 | | // parents do not incentive inclusion of their children. Therefore the mining algorithm only considers |
181 | | // transactions for inclusion on basis of the minimum of their own feerate or their ancestor feerate. |
182 | | struct AncestorFeerateComparator |
183 | | { |
184 | | template<typename I> |
185 | 0 | bool operator()(const I& a, const I& b) const { |
186 | 0 | auto min_feerate = [](const MiniMinerMempoolEntry& e) -> FeeFrac { |
187 | 0 | FeeFrac self_feerate(e.GetModifiedFee(), e.GetTxSize()); |
188 | 0 | FeeFrac ancestor_feerate(e.GetModFeesWithAncestors(), e.GetSizeWithAncestors()); |
189 | 0 | return std::min(ancestor_feerate, self_feerate); |
190 | 0 | }; |
191 | 0 | FeeFrac a_feerate{min_feerate(a->second)}; |
192 | 0 | FeeFrac b_feerate{min_feerate(b->second)}; |
193 | 0 | if (a_feerate != b_feerate) { Branch (193:13): [True: 0, False: 0]
|
194 | 0 | return a_feerate > b_feerate; |
195 | 0 | } |
196 | | // Use txid as tiebreaker for stable sorting |
197 | 0 | return a->first < b->first; |
198 | 0 | } |
199 | | }; |
200 | | |
201 | | void MiniMiner::DeleteAncestorPackage(const std::set<MockEntryMap::iterator, IteratorComparator>& ancestors) |
202 | 0 | { |
203 | 0 | Assume(ancestors.size() >= 1); |
204 | | // "Mine" all transactions in this ancestor set. |
205 | 0 | for (auto& anc : ancestors) { Branch (205:20): [True: 0, False: 0]
|
206 | 0 | Assume(m_in_block.count(anc->first) == 0); |
207 | 0 | m_in_block.insert(anc->first); |
208 | 0 | m_total_fees += anc->second.GetModifiedFee(); |
209 | 0 | m_total_vsize += anc->second.GetTxSize(); |
210 | 0 | auto it = m_descendant_set_by_txid.find(anc->first); |
211 | | // Each entry’s descendant set includes itself |
212 | 0 | Assume(it != m_descendant_set_by_txid.end()); |
213 | 0 | for (auto& descendant : it->second) { Branch (213:31): [True: 0, False: 0]
|
214 | | // If these fail, we must be double-deducting. |
215 | 0 | Assume(descendant->second.GetModFeesWithAncestors() >= anc->second.GetModifiedFee()); |
216 | 0 | Assume(descendant->second.GetSizeWithAncestors() >= anc->second.GetTxSize()); |
217 | 0 | descendant->second.UpdateAncestorState(-anc->second.GetTxSize(), -anc->second.GetModifiedFee()); |
218 | 0 | } |
219 | 0 | } |
220 | | // Delete these entries. |
221 | 0 | for (const auto& anc : ancestors) { Branch (221:26): [True: 0, False: 0]
|
222 | 0 | m_descendant_set_by_txid.erase(anc->first); |
223 | | // The above loop should have deducted each ancestor's size and fees from each of their |
224 | | // respective descendants exactly once. |
225 | 0 | Assume(anc->second.GetModFeesWithAncestors() == 0); |
226 | 0 | Assume(anc->second.GetSizeWithAncestors() == 0); |
227 | 0 | auto vec_it = std::find(m_entries.begin(), m_entries.end(), anc); |
228 | 0 | Assume(vec_it != m_entries.end()); |
229 | 0 | m_entries.erase(vec_it); |
230 | 0 | m_entries_by_txid.erase(anc); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | void MiniMiner::SanityCheck() const |
235 | 0 | { |
236 | | // m_entries, m_entries_by_txid, and m_descendant_set_by_txid all same size |
237 | 0 | Assume(m_entries.size() == m_entries_by_txid.size()); |
238 | 0 | Assume(m_entries.size() == m_descendant_set_by_txid.size()); |
239 | | // Cached ancestor values should be at least as large as the transaction's own fee and size |
240 | 0 | Assume(std::all_of(m_entries.begin(), m_entries.end(), [](const auto& entry) { |
241 | 0 | return entry->second.GetSizeWithAncestors() >= entry->second.GetTxSize() && |
242 | 0 | entry->second.GetModFeesWithAncestors() >= entry->second.GetModifiedFee();})); |
243 | | // None of the entries should be to-be-replaced transactions |
244 | 0 | Assume(std::all_of(m_to_be_replaced.begin(), m_to_be_replaced.end(), |
245 | 0 | [&](const auto& txid){return m_entries_by_txid.find(txid) == m_entries_by_txid.end();})); |
246 | 0 | } |
247 | | |
248 | | void MiniMiner::BuildMockTemplate(std::optional<CFeeRate> target_feerate) |
249 | 0 | { |
250 | 0 | const auto num_txns{m_entries_by_txid.size()}; |
251 | 0 | uint32_t sequence_num{0}; |
252 | 0 | while (!m_entries_by_txid.empty()) { Branch (252:12): [True: 0, False: 0]
|
253 | | // Sort again, since transaction removal may change some m_entries' ancestor feerates. |
254 | 0 | std::sort(m_entries.begin(), m_entries.end(), AncestorFeerateComparator()); |
255 | | |
256 | | // Pick highest ancestor feerate entry. |
257 | 0 | auto best_iter = m_entries.begin(); |
258 | 0 | Assume(best_iter != m_entries.end()); |
259 | 0 | const auto ancestor_package_size = (*best_iter)->second.GetSizeWithAncestors(); |
260 | 0 | const auto ancestor_package_fee = (*best_iter)->second.GetModFeesWithAncestors(); |
261 | | // Stop here. Everything that didn't "make it into the block" has bumpfee. |
262 | 0 | if (target_feerate.has_value() && Branch (262:13): [True: 0, False: 0]
|
263 | 0 | ancestor_package_fee < target_feerate->GetFee(ancestor_package_size)) { Branch (263:13): [True: 0, False: 0]
|
264 | 0 | break; |
265 | 0 | } |
266 | | |
267 | | // Calculate ancestors on the fly. This lookup should be fairly cheap, and ancestor sets |
268 | | // change at every iteration, so this is more efficient than maintaining a cache. |
269 | 0 | std::set<MockEntryMap::iterator, IteratorComparator> ancestors; |
270 | 0 | { |
271 | 0 | std::set<MockEntryMap::iterator, IteratorComparator> to_process; |
272 | 0 | to_process.insert(*best_iter); |
273 | 0 | while (!to_process.empty()) { Branch (273:20): [True: 0, False: 0]
|
274 | 0 | auto iter = to_process.begin(); |
275 | 0 | Assume(iter != to_process.end()); |
276 | 0 | ancestors.insert(*iter); |
277 | 0 | for (const auto& input : (*iter)->second.GetTx().vin) { Branch (277:40): [True: 0, False: 0]
|
278 | 0 | if (auto parent_it{m_entries_by_txid.find(input.prevout.hash)}; parent_it != m_entries_by_txid.end()) { Branch (278:85): [True: 0, False: 0]
|
279 | 0 | if (ancestors.count(parent_it) == 0) { Branch (279:29): [True: 0, False: 0]
|
280 | 0 | to_process.insert(parent_it); |
281 | 0 | } |
282 | 0 | } |
283 | 0 | } |
284 | 0 | to_process.erase(iter); |
285 | 0 | } |
286 | 0 | } |
287 | | // Track the order in which transactions were selected. |
288 | 0 | for (const auto& ancestor : ancestors) { Branch (288:35): [True: 0, False: 0]
|
289 | 0 | m_inclusion_order.emplace(Txid::FromUint256(ancestor->first), sequence_num); |
290 | 0 | } |
291 | 0 | DeleteAncestorPackage(ancestors); |
292 | 0 | SanityCheck(); |
293 | 0 | ++sequence_num; |
294 | 0 | } |
295 | 0 | if (!target_feerate.has_value()) { Branch (295:9): [True: 0, False: 0]
|
296 | 0 | Assume(m_in_block.size() == num_txns); |
297 | 0 | } else { |
298 | 0 | Assume(m_in_block.empty() || m_total_fees >= target_feerate->GetFee(m_total_vsize)); |
299 | 0 | } |
300 | 0 | Assume(m_in_block.empty() || sequence_num > 0); |
301 | 0 | Assume(m_in_block.size() == m_inclusion_order.size()); |
302 | | // Do not try to continue building the block template with a different feerate. |
303 | 0 | m_ready_to_calculate = false; |
304 | 0 | } |
305 | | |
306 | | |
307 | | std::map<Txid, uint32_t> MiniMiner::Linearize() |
308 | 0 | { |
309 | 0 | BuildMockTemplate(std::nullopt); |
310 | 0 | return m_inclusion_order; |
311 | 0 | } |
312 | | |
313 | | std::map<COutPoint, CAmount> MiniMiner::CalculateBumpFees(const CFeeRate& target_feerate) |
314 | 0 | { |
315 | 0 | if (!m_ready_to_calculate) return {}; Branch (315:9): [True: 0, False: 0]
|
316 | | // Build a block template until the target feerate is hit. |
317 | 0 | BuildMockTemplate(target_feerate); |
318 | | |
319 | | // Each transaction that "made it into the block" has a bumpfee of 0, i.e. they are part of an |
320 | | // ancestor package with at least the target feerate and don't need to be bumped. |
321 | 0 | for (const auto& txid : m_in_block) { Branch (321:27): [True: 0, False: 0]
|
322 | | // Not all of the block transactions were necessarily requested. |
323 | 0 | auto it = m_requested_outpoints_by_txid.find(txid); |
324 | 0 | if (it != m_requested_outpoints_by_txid.end()) { Branch (324:13): [True: 0, False: 0]
|
325 | 0 | for (const auto& outpoint : it->second) { Branch (325:39): [True: 0, False: 0]
|
326 | 0 | m_bump_fees.emplace(outpoint, 0); |
327 | 0 | } |
328 | 0 | m_requested_outpoints_by_txid.erase(it); |
329 | 0 | } |
330 | 0 | } |
331 | | |
332 | | // A transactions and its ancestors will only be picked into a block when |
333 | | // both the ancestor set feerate and the individual feerate meet the target |
334 | | // feerate. |
335 | | // |
336 | | // We had to convince ourselves that after running the mini miner and |
337 | | // picking all eligible transactions into our MockBlockTemplate, there |
338 | | // could still be transactions remaining that have a lower individual |
339 | | // feerate than their ancestor feerate. So here is an example: |
340 | | // |
341 | | // ┌─────────────────┐ |
342 | | // │ │ |
343 | | // │ Grandparent │ |
344 | | // │ 1700 vB │ |
345 | | // │ 1700 sats │ Target feerate: 10 s/vB |
346 | | // │ 1 s/vB │ GP Ancestor Set Feerate (ASFR): 1 s/vB |
347 | | // │ │ P1_ASFR: 9.84 s/vB |
348 | | // └──────▲───▲──────┘ P2_ASFR: 2.47 s/vB |
349 | | // │ │ C_ASFR: 10.27 s/vB |
350 | | // ┌───────────────┐ │ │ ┌──────────────┐ |
351 | | // │ ├────┘ └────┤ │ ⇒ C_FR < TFR < C_ASFR |
352 | | // │ Parent 1 │ │ Parent 2 │ |
353 | | // │ 200 vB │ │ 200 vB │ |
354 | | // │ 17000 sats │ │ 3000 sats │ |
355 | | // │ 85 s/vB │ │ 15 s/vB │ |
356 | | // │ │ │ │ |
357 | | // └───────────▲───┘ └───▲──────────┘ |
358 | | // │ │ |
359 | | // │ ┌───────────┐ │ |
360 | | // └────┤ ├────┘ |
361 | | // │ Child │ |
362 | | // │ 100 vB │ |
363 | | // │ 900 sats │ |
364 | | // │ 9 s/vB │ |
365 | | // │ │ |
366 | | // └───────────┘ |
367 | | // |
368 | | // We therefore calculate both the bump fee that is necessary to elevate |
369 | | // the individual transaction to the target feerate: |
370 | | // target_feerate × tx_size - tx_fees |
371 | | // and the bump fee that is necessary to bump the entire ancestor set to |
372 | | // the target feerate: |
373 | | // target_feerate × ancestor_set_size - ancestor_set_fees |
374 | | // By picking the maximum from the two, we ensure that a transaction meets |
375 | | // both criteria. |
376 | 0 | for (const auto& [txid, outpoints] : m_requested_outpoints_by_txid) { Branch (376:40): [True: 0, False: 0]
|
377 | 0 | auto it = m_entries_by_txid.find(txid); |
378 | 0 | Assume(it != m_entries_by_txid.end()); |
379 | 0 | if (it != m_entries_by_txid.end()) { Branch (379:13): [True: 0, False: 0]
|
380 | 0 | Assume(target_feerate.GetFee(it->second.GetSizeWithAncestors()) > std::min(it->second.GetModifiedFee(), it->second.GetModFeesWithAncestors())); |
381 | 0 | CAmount bump_fee_with_ancestors = target_feerate.GetFee(it->second.GetSizeWithAncestors()) - it->second.GetModFeesWithAncestors(); |
382 | 0 | CAmount bump_fee_individual = target_feerate.GetFee(it->second.GetTxSize()) - it->second.GetModifiedFee(); |
383 | 0 | const CAmount bump_fee{std::max(bump_fee_with_ancestors, bump_fee_individual)}; |
384 | 0 | Assume(bump_fee >= 0); |
385 | 0 | for (const auto& outpoint : outpoints) { Branch (385:39): [True: 0, False: 0]
|
386 | 0 | m_bump_fees.emplace(outpoint, bump_fee); |
387 | 0 | } |
388 | 0 | } |
389 | 0 | } |
390 | 0 | return m_bump_fees; |
391 | 0 | } |
392 | | |
393 | | std::optional<CAmount> MiniMiner::CalculateTotalBumpFees(const CFeeRate& target_feerate) |
394 | 0 | { |
395 | 0 | if (!m_ready_to_calculate) return std::nullopt; Branch (395:9): [True: 0, False: 0]
|
396 | | // Build a block template until the target feerate is hit. |
397 | 0 | BuildMockTemplate(target_feerate); |
398 | | |
399 | | // All remaining ancestors that are not part of m_in_block must be bumped, but no other relatives |
400 | 0 | std::set<MockEntryMap::iterator, IteratorComparator> ancestors; |
401 | 0 | std::set<MockEntryMap::iterator, IteratorComparator> to_process; |
402 | 0 | for (const auto& [txid, outpoints] : m_requested_outpoints_by_txid) { Branch (402:40): [True: 0, False: 0]
|
403 | | // Skip any ancestors that already have a miner score higher than the target feerate |
404 | | // (already "made it" into the block) |
405 | 0 | if (m_in_block.count(txid)) continue; Branch (405:13): [True: 0, False: 0]
|
406 | 0 | auto iter = m_entries_by_txid.find(txid); |
407 | 0 | if (iter == m_entries_by_txid.end()) continue; Branch (407:13): [True: 0, False: 0]
|
408 | 0 | to_process.insert(iter); |
409 | 0 | ancestors.insert(iter); |
410 | 0 | } |
411 | |
|
412 | 0 | std::set<uint256> has_been_processed; |
413 | 0 | while (!to_process.empty()) { Branch (413:12): [True: 0, False: 0]
|
414 | 0 | auto iter = to_process.begin(); |
415 | 0 | const CTransaction& tx = (*iter)->second.GetTx(); |
416 | 0 | for (const auto& input : tx.vin) { Branch (416:32): [True: 0, False: 0]
|
417 | 0 | if (auto parent_it{m_entries_by_txid.find(input.prevout.hash)}; parent_it != m_entries_by_txid.end()) { Branch (417:77): [True: 0, False: 0]
|
418 | 0 | if (!has_been_processed.count(input.prevout.hash)) { Branch (418:21): [True: 0, False: 0]
|
419 | 0 | to_process.insert(parent_it); |
420 | 0 | } |
421 | 0 | ancestors.insert(parent_it); |
422 | 0 | } |
423 | 0 | } |
424 | 0 | has_been_processed.insert(tx.GetHash()); |
425 | 0 | to_process.erase(iter); |
426 | 0 | } |
427 | 0 | const auto ancestor_package_size = std::accumulate(ancestors.cbegin(), ancestors.cend(), int64_t{0}, |
428 | 0 | [](int64_t sum, const auto it) {return sum + it->second.GetTxSize();}); |
429 | 0 | const auto ancestor_package_fee = std::accumulate(ancestors.cbegin(), ancestors.cend(), CAmount{0}, |
430 | 0 | [](CAmount sum, const auto it) {return sum + it->second.GetModifiedFee();}); |
431 | 0 | return target_feerate.GetFee(ancestor_package_size) - ancestor_package_fee; |
432 | 0 | } |
433 | | } // namespace node |