diff --git a/src/node/mini_miner.cpp b/src/node/mini_miner.cpp index ea702ab7343..3d24a3f58eb 100644 --- a/src/node/mini_miner.cpp +++ b/src/node/mini_miner.cpp @@ -170,6 +170,7 @@ MiniMiner::MiniMiner(const std::vector& manual_entries, Assume(m_to_be_replaced.empty()); Assume(m_requested_outpoints_by_txid.empty()); Assume(m_bump_fees.empty()); + Assume(m_inclusion_order.empty()); SanityCheck(); } @@ -255,6 +256,7 @@ void MiniMiner::SanityCheck() const void MiniMiner::BuildMockTemplate(std::optional target_feerate) { const auto num_txns{m_entries_by_txid.size()}; + uint32_t sequence_num{0}; while (!m_entries_by_txid.empty()) { // Sort again, since transaction removal may change some m_entries' ancestor feerates. std::sort(m_entries.begin(), m_entries.end(), AncestorFeerateComparator()); @@ -290,18 +292,32 @@ void MiniMiner::BuildMockTemplate(std::optional target_feerate) to_process.erase(iter); } } + // Track the order in which transactions were selected. + for (const auto& ancestor : ancestors) { + m_inclusion_order.emplace(Txid::FromUint256(ancestor->first), sequence_num); + } DeleteAncestorPackage(ancestors); SanityCheck(); + ++sequence_num; } if (!target_feerate.has_value()) { Assume(m_in_block.size() == num_txns); } else { Assume(m_in_block.empty() || m_total_fees >= target_feerate->GetFee(m_total_vsize)); } + Assume(m_in_block.empty() || sequence_num > 0); + Assume(m_in_block.size() == m_inclusion_order.size()); // Do not try to continue building the block template with a different feerate. m_ready_to_calculate = false; } + +std::map MiniMiner::Linearize() +{ + BuildMockTemplate(std::nullopt); + return m_inclusion_order; +} + std::map MiniMiner::CalculateBumpFees(const CFeeRate& target_feerate) { if (!m_ready_to_calculate) return {}; diff --git a/src/node/mini_miner.h b/src/node/mini_miner.h index e0051f33640..8f86709ae4e 100644 --- a/src/node/mini_miner.h +++ b/src/node/mini_miner.h @@ -67,8 +67,14 @@ struct IteratorComparator } }; -/** A minimal version of BlockAssembler. Allows us to run the mining algorithm on a subset of - * mempool transactions, ignoring consensus rules, to calculate mining scores. */ +/** A minimal version of BlockAssembler, using the same ancestor set scoring algorithm. Allows us to + * run this algorithm on a limited set of transactions (e.g. subset of mempool or transactions that + * are not yet in mempool) instead of the entire mempool, ignoring consensus rules. + * Callers may use this to: + * - Calculate the "bump fee" needed to spend an unconfirmed UTXO at a given feerate + * - "Linearize" a list of transactions to see the order in which they would be selected for + * inclusion in a block + */ class MiniMiner { // When true, a caller may use CalculateBumpFees(). Becomes false if we failed to retrieve @@ -84,6 +90,10 @@ class MiniMiner // the same tx will have the same bumpfee. Excludes non-mempool transactions. std::map> m_requested_outpoints_by_txid; + // Txid to a number representing the order in which this transaction was included (smaller + // number = included earlier). Transactions included in an ancestor set together have the same + // sequence number. + std::map m_inclusion_order; // What we're trying to calculate. Outpoint to the fee needed to bring the transaction to the target feerate. std::map m_bump_fees; @@ -151,6 +161,11 @@ public: * if it cannot be calculated. */ std::optional CalculateTotalBumpFees(const CFeeRate& target_feerate); + /** Construct a new block template with all of the transactions and calculate the order in which + * they are selected. Returns the sequence number (lower = selected earlier) with which each + * transaction was selected, indexed by txid, or an empty map if it cannot be calculated. + */ + std::map Linearize(); }; } // namespace node