kaspa_mining/
manager.rs

1use crate::{
2    block_template::{builder::BlockTemplateBuilder, errors::BuilderError},
3    cache::BlockTemplateCache,
4    errors::MiningManagerResult,
5    feerate::{FeeEstimateVerbose, FeerateEstimations, FeerateEstimatorArgs},
6    mempool::{
7        config::Config,
8        model::tx::{MempoolTransaction, TransactionPostValidation, TransactionPreValidation, TxRemovalReason},
9        populate_entries_and_try_validate::{
10            populate_mempool_transactions_in_parallel, validate_mempool_transaction, validate_mempool_transactions_in_parallel,
11        },
12        tx::{Orphan, Priority, RbfPolicy},
13        Mempool,
14    },
15    model::{
16        owner_txs::{GroupedOwnerTransactions, ScriptPublicKeySet},
17        topological_sort::IntoIterTopologically,
18        tx_insert::TransactionInsertion,
19        tx_query::TransactionQuery,
20    },
21    MempoolCountersSnapshot, MiningCounters, P2pTxCountSample,
22};
23use itertools::Itertools;
24use kaspa_consensus_core::{
25    api::{
26        args::{TransactionValidationArgs, TransactionValidationBatchArgs},
27        ConsensusApi,
28    },
29    block::{BlockTemplate, TemplateBuildMode, TemplateTransactionSelector},
30    coinbase::MinerData,
31    errors::{block::RuleError as BlockRuleError, tx::TxRuleError},
32    tx::{MutableTransaction, Transaction, TransactionId, TransactionOutput},
33};
34use kaspa_consensusmanager::{spawn_blocking, ConsensusProxy};
35use kaspa_core::{debug, error, info, time::Stopwatch, warn};
36use kaspa_mining_errors::{manager::MiningManagerError, mempool::RuleError};
37use parking_lot::RwLock;
38use std::sync::Arc;
39use tokio::sync::mpsc::UnboundedSender;
40
41pub struct MiningManager {
42    config: Arc<Config>,
43    block_template_cache: BlockTemplateCache,
44    mempool: RwLock<Mempool>,
45    counters: Arc<MiningCounters>,
46}
47
48impl MiningManager {
49    pub fn new(
50        target_time_per_block: u64,
51        relay_non_std_transactions: bool,
52        max_block_mass: u64,
53        cache_lifetime: Option<u64>,
54        counters: Arc<MiningCounters>,
55    ) -> Self {
56        let config = Config::build_default(target_time_per_block, relay_non_std_transactions, max_block_mass);
57        Self::with_config(config, cache_lifetime, counters)
58    }
59
60    pub fn new_with_extended_config(
61        target_time_per_block: u64,
62        relay_non_std_transactions: bool,
63        max_block_mass: u64,
64        ram_scale: f64,
65        cache_lifetime: Option<u64>,
66        counters: Arc<MiningCounters>,
67    ) -> Self {
68        let config =
69            Config::build_default(target_time_per_block, relay_non_std_transactions, max_block_mass).apply_ram_scale(ram_scale);
70        Self::with_config(config, cache_lifetime, counters)
71    }
72
73    pub(crate) fn with_config(config: Config, cache_lifetime: Option<u64>, counters: Arc<MiningCounters>) -> Self {
74        let config = Arc::new(config);
75        let mempool = RwLock::new(Mempool::new(config.clone(), counters.clone()));
76        let block_template_cache = BlockTemplateCache::new(cache_lifetime);
77        Self { config, block_template_cache, mempool, counters }
78    }
79
80    pub fn get_block_template(&self, consensus: &dyn ConsensusApi, miner_data: &MinerData) -> MiningManagerResult<BlockTemplate> {
81        let virtual_state_approx_id = consensus.get_virtual_state_approx_id();
82        let mut cache_lock = self.block_template_cache.lock(virtual_state_approx_id);
83        let immutable_template = cache_lock.get_immutable_cached_template();
84
85        // We first try and use a cached template if not expired
86        if let Some(immutable_template) = immutable_template {
87            drop(cache_lock);
88            if immutable_template.miner_data == *miner_data {
89                return Ok(immutable_template.as_ref().clone());
90            }
91            // Miner data is new -- make the minimum changes required
92            // Note the call returns a modified clone of the cached block template
93            let block_template = BlockTemplateBuilder::modify_block_template(consensus, miner_data, &immutable_template)?;
94
95            // No point in updating cache since we have no reason to believe this coinbase will be used more
96            // than the previous one, and we want to maintain the original template caching time
97            return Ok(block_template);
98        }
99
100        // Rust rewrite:
101        // We avoid passing a mempool ref to blockTemplateBuilder by calling
102        // mempool.BlockCandidateTransactions and mempool.RemoveTransactions here.
103        // We remove recursion seen in blockTemplateBuilder.BuildBlockTemplate here.
104        debug!("Building a new block template...");
105        let _swo = Stopwatch::<22>::with_threshold("build_block_template full loop");
106        let mut attempts: u64 = 0;
107        loop {
108            attempts += 1;
109
110            let selector = self.build_selector();
111            let block_template_builder = BlockTemplateBuilder::new();
112            let build_mode = if attempts < self.config.maximum_build_block_template_attempts {
113                TemplateBuildMode::Standard
114            } else {
115                TemplateBuildMode::Infallible
116            };
117            match block_template_builder.build_block_template(consensus, miner_data, selector, build_mode) {
118                Ok(block_template) => {
119                    let block_template = cache_lock.set_immutable_cached_template(block_template);
120                    match attempts {
121                        1 => {
122                            debug!(
123                                "Built a new block template with {} transactions in {:#?}",
124                                block_template.block.transactions.len(),
125                                _swo.elapsed()
126                            );
127                        }
128                        2 => {
129                            debug!(
130                                "Built a new block template with {} transactions at second attempt in {:#?}",
131                                block_template.block.transactions.len(),
132                                _swo.elapsed()
133                            );
134                        }
135                        n => {
136                            debug!(
137                                "Built a new block template with {} transactions in {} attempts totaling {:#?}",
138                                block_template.block.transactions.len(),
139                                n,
140                                _swo.elapsed()
141                            );
142                        }
143                    }
144                    return Ok(block_template.as_ref().clone());
145                }
146                Err(BuilderError::ConsensusError(BlockRuleError::InvalidTransactionsInNewBlock(invalid_transactions))) => {
147                    let mut missing_outpoint: usize = 0;
148                    let mut invalid: usize = 0;
149
150                    let mut mempool_write = self.mempool.write();
151                    invalid_transactions.iter().for_each(|(x, err)| {
152                        // On missing outpoints, the most likely is that the tx was already in a block accepted by
153                        // the consensus but not yet processed by handle_new_block_transactions(). Another possibility
154                        // is a double spend. In both cases, we simply remove the transaction but keep its redeemers.
155                        // Those will either be valid in a next block template or invalidated if it's a double spend.
156                        //
157                        // If the redeemers of a transaction accepted in consensus but not yet handled in mempool were
158                        // removed, it would lead to having subsequently submitted children transactions of the removed
159                        // redeemers being unexpectedly either orphaned or rejected in case orphans are disallowed.
160                        //
161                        // For all other errors, we do remove the redeemers.
162
163                        let removal_result = if *err == TxRuleError::MissingTxOutpoints {
164                            missing_outpoint += 1;
165                            mempool_write.remove_transaction(x, false, TxRemovalReason::Muted, "")
166                        } else {
167                            invalid += 1;
168                            warn!("Remove per BBT invalid transaction and descendants");
169                            mempool_write.remove_transaction(
170                                x,
171                                true,
172                                TxRemovalReason::InvalidInBlockTemplate,
173                                format!(" error: {}", err).as_str(),
174                            )
175                        };
176                        if let Err(err) = removal_result {
177                            // Original golang comment:
178                            // mempool.remove_transactions might return errors in situations that are perfectly fine in this context.
179                            // TODO: Once the mempool invariants are clear, this might return an error:
180                            // https://github.com/kaspanet/kaspad/issues/1553
181                            // NOTE: unlike golang, here we continue removing also if an error was found
182                            error!("Error from mempool.remove_transactions: {:?}", err);
183                        }
184                    });
185                    drop(mempool_write);
186
187                    debug!(
188                        "Building a new block template failed for {} txs missing outpoint and {} invalid txs",
189                        missing_outpoint, invalid
190                    );
191                }
192                Err(err) => {
193                    warn!("Building a new block template failed: {}", err);
194                    return Err(err)?;
195                }
196            }
197        }
198    }
199
200    /// Dynamically builds a transaction selector based on the specific state of the ready transactions frontier
201    pub(crate) fn build_selector(&self) -> Box<dyn TemplateTransactionSelector> {
202        self.mempool.read().build_selector()
203    }
204
205    /// Returns realtime feerate estimations based on internal mempool state
206    pub(crate) fn get_realtime_feerate_estimations(&self) -> FeerateEstimations {
207        let args = FeerateEstimatorArgs::new(self.config.network_blocks_per_second, self.config.maximum_mass_per_block);
208        let estimator = self.mempool.read().build_feerate_estimator(args);
209        estimator.calc_estimations(self.config.minimum_feerate())
210    }
211
212    /// Returns realtime feerate estimations based on internal mempool state with additional verbose data
213    pub(crate) fn get_realtime_feerate_estimations_verbose(
214        &self,
215        consensus: &dyn ConsensusApi,
216        prefix: kaspa_addresses::Prefix,
217    ) -> MiningManagerResult<FeeEstimateVerbose> {
218        let args = FeerateEstimatorArgs::new(self.config.network_blocks_per_second, self.config.maximum_mass_per_block);
219        let network_mass_per_second = args.network_mass_per_second();
220        let mempool_read = self.mempool.read();
221        let estimator = mempool_read.build_feerate_estimator(args);
222        let ready_transactions_count = mempool_read.ready_transaction_count();
223        let ready_transaction_total_mass = mempool_read.ready_transaction_total_mass();
224        drop(mempool_read);
225        let mut resp = FeeEstimateVerbose {
226            estimations: estimator.calc_estimations(self.config.minimum_feerate()),
227            network_mass_per_second,
228            mempool_ready_transactions_count: ready_transactions_count as u64,
229            mempool_ready_transactions_total_mass: ready_transaction_total_mass,
230
231            next_block_template_feerate_min: -1.0,
232            next_block_template_feerate_median: -1.0,
233            next_block_template_feerate_max: -1.0,
234        };
235        // calculate next_block_template_feerate_xxx
236        {
237            let script_public_key = kaspa_txscript::pay_to_address_script(&kaspa_addresses::Address::new(
238                prefix,
239                kaspa_addresses::Version::PubKey,
240                &[0u8; 32],
241            ));
242            let miner_data: MinerData = MinerData::new(script_public_key, vec![]);
243
244            let BlockTemplate { block: kaspa_consensus_core::block::MutableBlock { transactions, .. }, calculated_fees, .. } =
245                self.get_block_template(consensus, &miner_data)?;
246
247            let Some(Stats { max, median, min }) = feerate_stats(transactions, calculated_fees) else {
248                return Ok(resp);
249            };
250
251            resp.next_block_template_feerate_max = max;
252            resp.next_block_template_feerate_min = min;
253            resp.next_block_template_feerate_median = median;
254        }
255        Ok(resp)
256    }
257
258    /// Clears the block template cache, forcing the next call to get_block_template to build a new block template.
259    #[cfg(test)]
260    pub(crate) fn clear_block_template(&self) {
261        self.block_template_cache.clear();
262    }
263
264    #[cfg(test)]
265    pub(crate) fn block_template_builder(&self) -> BlockTemplateBuilder {
266        BlockTemplateBuilder::new()
267    }
268
269    /// validate_and_insert_transaction validates the given transaction, and
270    /// adds it to the set of known transactions that have not yet been
271    /// added to any block.
272    ///
273    /// The validation is constrained by a Replace by fee policy applied
274    /// to double spends in the mempool. For more information, see [`RbfPolicy`].
275    ///
276    /// On success, returns transactions that where unorphaned following the insertion
277    /// of the provided transaction.
278    ///
279    /// The returned transactions are references of objects owned by the mempool.
280    pub fn validate_and_insert_transaction(
281        &self,
282        consensus: &dyn ConsensusApi,
283        transaction: Transaction,
284        priority: Priority,
285        orphan: Orphan,
286        rbf_policy: RbfPolicy,
287    ) -> MiningManagerResult<TransactionInsertion> {
288        self.validate_and_insert_mutable_transaction(consensus, MutableTransaction::from_tx(transaction), priority, orphan, rbf_policy)
289    }
290
291    /// Exposed for tests only
292    ///
293    /// See `validate_and_insert_transaction`
294    pub(crate) fn validate_and_insert_mutable_transaction(
295        &self,
296        consensus: &dyn ConsensusApi,
297        transaction: MutableTransaction,
298        priority: Priority,
299        orphan: Orphan,
300        rbf_policy: RbfPolicy,
301    ) -> MiningManagerResult<TransactionInsertion> {
302        // read lock on mempool
303        let TransactionPreValidation { mut transaction, feerate_threshold } =
304            self.mempool.read().pre_validate_and_populate_transaction(consensus, transaction, rbf_policy)?;
305        let args = TransactionValidationArgs::new(feerate_threshold);
306        // no lock on mempool
307        let validation_result = validate_mempool_transaction(consensus, &mut transaction, &args);
308        // write lock on mempool
309        let mut mempool = self.mempool.write();
310        match mempool.post_validate_and_insert_transaction(consensus, validation_result, transaction, priority, orphan, rbf_policy)? {
311            TransactionPostValidation { removed, accepted: Some(accepted_transaction) } => {
312                let unorphaned_transactions = mempool.get_unorphaned_transactions_after_accepted_transaction(&accepted_transaction);
313                drop(mempool);
314
315                // The capacity used here may be exceeded since accepted unorphaned transaction may themselves unorphan other transactions.
316                let mut accepted_transactions = Vec::with_capacity(unorphaned_transactions.len() + 1);
317                // We include the original accepted transaction as well
318                accepted_transactions.push(accepted_transaction);
319                accepted_transactions.extend(self.validate_and_insert_unorphaned_transactions(consensus, unorphaned_transactions));
320                self.counters.increase_tx_counts(1, priority);
321
322                Ok(TransactionInsertion::new(removed, accepted_transactions))
323            }
324            TransactionPostValidation { removed, accepted: None } => Ok(TransactionInsertion::new(removed, vec![])),
325        }
326    }
327
328    fn validate_and_insert_unorphaned_transactions(
329        &self,
330        consensus: &dyn ConsensusApi,
331        mut incoming_transactions: Vec<MempoolTransaction>,
332    ) -> Vec<Arc<Transaction>> {
333        // The capacity used here may be exceeded (see next comment).
334        let mut accepted_transactions = Vec::with_capacity(incoming_transactions.len());
335        // The validation args map is immutably empty since unorphaned transactions do not require pre processing so there
336        // are no feerate thresholds to use. Instead, we rely on this being checked during post processing.
337        let args = TransactionValidationBatchArgs::new();
338        // We loop as long as incoming unorphaned transactions do unorphan other transactions when they
339        // get validated and inserted into the mempool.
340        while !incoming_transactions.is_empty() {
341            // Since the consensus validation requires a slice of MutableTransaction, we destructure the vector of
342            // MempoolTransaction into 2 distinct vectors holding respectively the needed MutableTransaction and Priority.
343            let (mut transactions, priorities): (Vec<MutableTransaction>, Vec<Priority>) =
344                incoming_transactions.into_iter().map(|x| (x.mtx, x.priority)).unzip();
345
346            // no lock on mempool
347            // We process the transactions by chunks of max block mass to prevent locking the virtual processor for too long.
348            let mut lower_bound: usize = 0;
349            let mut validation_results = Vec::with_capacity(transactions.len());
350            while let Some(upper_bound) = self.next_transaction_chunk_upper_bound(&transactions, lower_bound) {
351                assert!(lower_bound < upper_bound, "the chunk is never empty");
352                validation_results.extend(validate_mempool_transactions_in_parallel(
353                    consensus,
354                    &mut transactions[lower_bound..upper_bound],
355                    &args,
356                ));
357                lower_bound = upper_bound;
358            }
359            assert_eq!(transactions.len(), validation_results.len(), "every transaction should have a matching validation result");
360
361            // write lock on mempool
362            let mut mempool = self.mempool.write();
363            incoming_transactions = transactions
364                .into_iter()
365                .zip(priorities)
366                .zip(validation_results)
367                .flat_map(|((transaction, priority), validation_result)| {
368                    let orphan_id = transaction.id();
369                    let rbf_policy = Mempool::get_orphan_transaction_rbf_policy(priority);
370                    match mempool.post_validate_and_insert_transaction(
371                        consensus,
372                        validation_result,
373                        transaction,
374                        priority,
375                        Orphan::Forbidden,
376                        rbf_policy,
377                    ) {
378                        Ok(TransactionPostValidation { removed: _, accepted: Some(accepted_transaction) }) => {
379                            accepted_transactions.push(accepted_transaction.clone());
380                            self.counters.increase_tx_counts(1, priority);
381                            mempool.get_unorphaned_transactions_after_accepted_transaction(&accepted_transaction)
382                        }
383                        Ok(TransactionPostValidation { removed: _, accepted: None }) => vec![],
384                        Err(err) => {
385                            debug!("Failed to unorphan transaction {0} due to rule error: {1}", orphan_id, err);
386                            vec![]
387                        }
388                    }
389                })
390                .collect::<Vec<_>>();
391            drop(mempool);
392        }
393        accepted_transactions
394    }
395
396    /// Validates a batch of transactions, handling iteratively only the independent ones, and
397    /// adds those to the set of known transactions that have not yet been added to any block.
398    ///
399    /// The validation is constrained by a Replace by fee policy applied
400    /// to double spends in the mempool. For more information, see [`RbfPolicy`].
401    ///
402    /// Returns transactions that where unorphaned following the insertion of the provided
403    /// transactions. The returned transactions are references of objects owned by the mempool.
404    pub fn validate_and_insert_transaction_batch(
405        &self,
406        consensus: &dyn ConsensusApi,
407        transactions: Vec<Transaction>,
408        priority: Priority,
409        orphan: Orphan,
410        rbf_policy: RbfPolicy,
411    ) -> Vec<MiningManagerResult<Arc<Transaction>>> {
412        const TRANSACTION_CHUNK_SIZE: usize = 250;
413
414        // The capacity used here may be exceeded since accepted transactions may unorphan other transactions.
415        let mut insert_results: Vec<MiningManagerResult<Arc<Transaction>>> = Vec::with_capacity(transactions.len());
416        let mut unorphaned_transactions = vec![];
417        let _swo = Stopwatch::<80>::with_threshold("validate_and_insert_transaction_batch topological_sort op");
418        let sorted_transactions = transactions.into_iter().map(MutableTransaction::from_tx).topological_into_iter();
419        drop(_swo);
420
421        // read lock on mempool
422        // Here, we simply log and drop all erroneous transactions since the caller doesn't care about those anyway
423        let mut transactions = Vec::with_capacity(sorted_transactions.len());
424        let mut args = TransactionValidationBatchArgs::new();
425        for chunk in &sorted_transactions.chunks(TRANSACTION_CHUNK_SIZE) {
426            let mempool = self.mempool.read();
427            let txs = chunk.filter_map(|tx| {
428                let transaction_id = tx.id();
429                match mempool.pre_validate_and_populate_transaction(consensus, tx, rbf_policy) {
430                    Ok(TransactionPreValidation { transaction, feerate_threshold }) => {
431                        if let Some(threshold) = feerate_threshold {
432                            args.set_feerate_threshold(transaction.id(), threshold);
433                        }
434                        Some(transaction)
435                    }
436                    Err(RuleError::RejectAlreadyAccepted(transaction_id)) => {
437                        debug!("Ignoring already accepted transaction {}", transaction_id);
438                        None
439                    }
440                    Err(RuleError::RejectDuplicate(transaction_id)) => {
441                        debug!("Ignoring transaction already in the mempool {}", transaction_id);
442                        None
443                    }
444                    Err(RuleError::RejectDuplicateOrphan(transaction_id)) => {
445                        debug!("Ignoring transaction already in the orphan pool {}", transaction_id);
446                        None
447                    }
448                    Err(err) => {
449                        debug!("Failed to pre validate transaction {0} due to rule error: {1}", transaction_id, err);
450                        insert_results.push(Err(MiningManagerError::MempoolError(err)));
451                        None
452                    }
453                }
454            });
455            transactions.extend(txs);
456        }
457
458        // no lock on mempool
459        // We process the transactions by chunks of max block mass to prevent locking the virtual processor for too long.
460        let mut lower_bound: usize = 0;
461        let mut validation_results = Vec::with_capacity(transactions.len());
462        while let Some(upper_bound) = self.next_transaction_chunk_upper_bound(&transactions, lower_bound) {
463            assert!(lower_bound < upper_bound, "the chunk is never empty");
464            validation_results.extend(validate_mempool_transactions_in_parallel(
465                consensus,
466                &mut transactions[lower_bound..upper_bound],
467                &args,
468            ));
469            lower_bound = upper_bound;
470        }
471        assert_eq!(transactions.len(), validation_results.len(), "every transaction should have a matching validation result");
472
473        // write lock on mempool
474        // Here again, transactions failing post validation are logged and dropped
475        for chunk in &transactions.into_iter().zip(validation_results).chunks(TRANSACTION_CHUNK_SIZE) {
476            let mut mempool = self.mempool.write();
477            let txs = chunk.flat_map(|(transaction, validation_result)| {
478                let transaction_id = transaction.id();
479                match mempool.post_validate_and_insert_transaction(
480                    consensus,
481                    validation_result,
482                    transaction,
483                    priority,
484                    orphan,
485                    rbf_policy,
486                ) {
487                    Ok(TransactionPostValidation { removed: _, accepted: Some(accepted_transaction) }) => {
488                        insert_results.push(Ok(accepted_transaction.clone()));
489                        self.counters.increase_tx_counts(1, priority);
490                        mempool.get_unorphaned_transactions_after_accepted_transaction(&accepted_transaction)
491                    }
492                    Ok(TransactionPostValidation { removed: _, accepted: None }) | Err(RuleError::RejectDuplicate(_)) => {
493                        // Either orphaned or already existing in the mempool
494                        vec![]
495                    }
496                    Err(err) => {
497                        debug!("Failed to post validate transaction {0} due to rule error: {1}", transaction_id, err);
498                        insert_results.push(Err(MiningManagerError::MempoolError(err)));
499                        vec![]
500                    }
501                }
502            });
503            unorphaned_transactions.extend(txs);
504        }
505
506        insert_results
507            .extend(self.validate_and_insert_unorphaned_transactions(consensus, unorphaned_transactions).into_iter().map(Ok));
508        insert_results
509    }
510
511    fn next_transaction_chunk_upper_bound(&self, transactions: &[MutableTransaction], lower_bound: usize) -> Option<usize> {
512        if lower_bound >= transactions.len() {
513            return None;
514        }
515        let mut mass = 0;
516        transactions[lower_bound..]
517            .iter()
518            .position(|tx| {
519                mass += tx.calculated_compute_mass.unwrap();
520                mass >= self.config.maximum_mass_per_block
521            })
522            // Make sure the upper bound is greater than the lower bound, allowing to handle a very unlikely,
523            // (if not impossible) case where the mass of a single transaction is greater than the maximum
524            // chunk mass.
525            .map(|relative_index| relative_index.max(1) + lower_bound)
526            .or(Some(transactions.len()))
527    }
528
529    /// Try to return a mempool transaction by its id.
530    ///
531    /// Note: the transaction is an orphan if tx.is_fully_populated() returns false.
532    pub fn get_transaction(&self, transaction_id: &TransactionId, query: TransactionQuery) -> Option<MutableTransaction> {
533        self.mempool.read().get_transaction(transaction_id, query)
534    }
535
536    /// Returns whether the mempool holds this transaction in any form.
537    pub fn has_transaction(&self, transaction_id: &TransactionId, query: TransactionQuery) -> bool {
538        self.mempool.read().has_transaction(transaction_id, query)
539    }
540
541    pub fn get_all_transactions(&self, query: TransactionQuery) -> (Vec<MutableTransaction>, Vec<MutableTransaction>) {
542        const TRANSACTION_CHUNK_SIZE: usize = 1000;
543        // read lock on mempool by transaction chunks
544        let transactions = if query.include_transaction_pool() {
545            let transaction_ids = self.mempool.read().get_all_transaction_ids(TransactionQuery::TransactionsOnly).0;
546            let mut transactions = Vec::with_capacity(self.mempool.read().transaction_count(TransactionQuery::TransactionsOnly));
547            for chunks in transaction_ids.chunks(TRANSACTION_CHUNK_SIZE) {
548                let mempool = self.mempool.read();
549                transactions.extend(chunks.iter().filter_map(|x| mempool.get_transaction(x, TransactionQuery::TransactionsOnly)));
550            }
551            transactions
552        } else {
553            vec![]
554        };
555        // read lock on mempool
556        let orphans = if query.include_orphan_pool() {
557            self.mempool.read().get_all_transactions(TransactionQuery::OrphansOnly).1
558        } else {
559            vec![]
560        };
561        (transactions, orphans)
562    }
563
564    /// get_transactions_by_addresses returns the sending and receiving transactions for
565    /// a set of addresses.
566    ///
567    /// Note: a transaction is an orphan if tx.is_fully_populated() returns false.
568    pub fn get_transactions_by_addresses(
569        &self,
570        script_public_keys: &ScriptPublicKeySet,
571        query: TransactionQuery,
572    ) -> GroupedOwnerTransactions {
573        // TODO: break the monolithic lock
574        self.mempool.read().get_transactions_by_addresses(script_public_keys, query)
575    }
576
577    pub fn transaction_count(&self, query: TransactionQuery) -> usize {
578        self.mempool.read().transaction_count(query)
579    }
580
581    pub fn handle_new_block_transactions(
582        &self,
583        consensus: &dyn ConsensusApi,
584        block_daa_score: u64,
585        block_transactions: &[Transaction],
586    ) -> MiningManagerResult<Vec<Arc<Transaction>>> {
587        // TODO: should use tx acceptance data to verify that new block txs are actually accepted into virtual state.
588        // TODO: avoid returning a result from this function (and the underlying function). Any possible error is a
589        // problem of the internal implementation and unrelated to the caller
590
591        // write lock on mempool
592        let unorphaned_transactions = self.mempool.write().handle_new_block_transactions(block_daa_score, block_transactions)?;
593
594        // alternate no & write lock on mempool
595        let accepted_transactions = self.validate_and_insert_unorphaned_transactions(consensus, unorphaned_transactions);
596
597        Ok(accepted_transactions)
598    }
599
600    pub fn expire_low_priority_transactions(&self, consensus: &dyn ConsensusApi) {
601        // very fine-grained write locks on mempool
602        debug!("<> Expiring low priority transactions...");
603
604        // orphan pool
605        if let Err(err) = self.mempool.write().expire_orphan_low_priority_transactions(consensus) {
606            warn!("Failed to expire transactions from orphan pool: {}", err);
607        }
608
609        // accepted transaction cache
610        self.mempool.write().expire_accepted_transactions(consensus);
611
612        // mempool
613        let expired_low_priority_transactions = self.mempool.write().collect_expired_low_priority_transactions(consensus);
614        for chunk in &expired_low_priority_transactions.iter().chunks(24) {
615            let mut mempool = self.mempool.write();
616            chunk.into_iter().for_each(|tx| {
617                if let Err(err) = mempool.remove_transaction(tx, true, TxRemovalReason::Muted, "") {
618                    warn!("Failed to remove transaction {} from mempool: {}", tx, err);
619                }
620            });
621        }
622        match expired_low_priority_transactions.len() {
623            0 => {}
624            1 => debug!("Removed transaction ({}) {}", TxRemovalReason::Expired, expired_low_priority_transactions[0]),
625            n => debug!("Removed {} transactions ({}): {}...", n, TxRemovalReason::Expired, expired_low_priority_transactions[0]),
626        }
627    }
628
629    pub fn revalidate_high_priority_transactions(
630        &self,
631        consensus: &dyn ConsensusApi,
632        transaction_ids_sender: UnboundedSender<Vec<TransactionId>>,
633    ) {
634        const TRANSACTION_CHUNK_SIZE: usize = 1000;
635
636        // read lock on mempool
637        // Prepare a vector with clones of high priority transactions found in the mempool
638        let mempool = self.mempool.read();
639        let transaction_ids = mempool.all_transaction_ids_with_priority(Priority::High);
640        if transaction_ids.is_empty() {
641            debug!("<> Revalidating high priority transactions found no transactions");
642            return;
643        } else {
644            debug!("<> Revalidating {} high priority transactions...", transaction_ids.len());
645        }
646        drop(mempool);
647        // read lock on mempool by transaction chunks
648        let mut transactions = Vec::with_capacity(transaction_ids.len());
649        for chunk in &transaction_ids.iter().chunks(TRANSACTION_CHUNK_SIZE) {
650            let mempool = self.mempool.read();
651            transactions.extend(chunk.filter_map(|x| mempool.get_transaction(x, TransactionQuery::TransactionsOnly)));
652        }
653
654        let mut valid: usize = 0;
655        let mut accepted: usize = 0;
656        let mut other: usize = 0;
657        let mut missing_outpoint: usize = 0;
658        let mut invalid: usize = 0;
659
660        // We process the transactions by level of dependency inside the batch.
661        // Doing so allows to remove all chained dependencies of rejected transactions.
662        let _swo = Stopwatch::<800>::with_threshold("revalidate topological_sort op");
663        let sorted_transactions = transactions.topological_into_iter();
664        drop(_swo);
665
666        // read lock on mempool by transaction chunks
667        // As the revalidation process is no longer atomic, we filter the transactions ready for revalidation,
668        // keeping only the ones actually present in the mempool (see comment above).
669        let _swo = Stopwatch::<900>::with_threshold("revalidate populate_mempool_entries op");
670        let mut transactions = Vec::with_capacity(sorted_transactions.len());
671        for chunk in &sorted_transactions.chunks(TRANSACTION_CHUNK_SIZE) {
672            let mempool = self.mempool.read();
673            let txs = chunk.filter_map(|mut x| {
674                let transaction_id = x.id();
675                if mempool.has_accepted_transaction(&transaction_id) {
676                    accepted += 1;
677                    None
678                } else if mempool.has_transaction(&transaction_id, TransactionQuery::TransactionsOnly) {
679                    x.clear_entries();
680                    mempool.populate_mempool_entries(&mut x);
681                    match x.is_fully_populated() {
682                        false => Some(x),
683                        true => {
684                            // If all entries are populated with mempool UTXOs, we already know the transaction is valid
685                            valid += 1;
686                            None
687                        }
688                    }
689                } else {
690                    other += 1;
691                    None
692                }
693            });
694            transactions.extend(txs);
695        }
696        drop(_swo);
697
698        // no lock on mempool
699        // We process the transactions by chunks of max block mass to prevent locking the virtual processor for too long.
700        let mut lower_bound: usize = 0;
701        let mut validation_results = Vec::with_capacity(transactions.len());
702        while let Some(upper_bound) = self.next_transaction_chunk_upper_bound(&transactions, lower_bound) {
703            assert!(lower_bound < upper_bound, "the chunk is never empty");
704            let _swo = Stopwatch::<60>::with_threshold("revalidate validate_mempool_transactions_in_parallel op");
705            validation_results
706                .extend(populate_mempool_transactions_in_parallel(consensus, &mut transactions[lower_bound..upper_bound]));
707            drop(_swo);
708            lower_bound = upper_bound;
709        }
710        assert_eq!(transactions.len(), validation_results.len(), "every transaction should have a matching validation result");
711
712        // write lock on mempool
713        // Depending on the validation result, transactions are either accepted or removed
714        for chunk in &transactions.into_iter().zip(validation_results).chunks(TRANSACTION_CHUNK_SIZE) {
715            let mut valid_ids = Vec::with_capacity(TRANSACTION_CHUNK_SIZE);
716            let mut mempool = self.mempool.write();
717            let _swo = Stopwatch::<60>::with_threshold("revalidate update_revalidated_transaction op");
718            for (transaction, validation_result) in chunk {
719                let transaction_id = transaction.id();
720                match validation_result {
721                    Ok(()) => {
722                        // Only consider transactions still being in the mempool since during the validation some might have been removed.
723                        if mempool.update_revalidated_transaction(transaction) {
724                            // A following transaction should not remove this one from the pool since we process in a topological order.
725                            // Still, considering the (very unlikely) scenario of two high priority txs sandwiching a low one, where
726                            // in this case topological order is not guaranteed since we only considered chained dependencies of
727                            // high-priority transactions, we might wrongfully return as valid the id of a removed transaction.
728                            // However, as only consequence, said transaction would then be advertised to registered peers and not be
729                            // provided upon request.
730                            valid_ids.push(transaction_id);
731                            valid += 1;
732                        } else {
733                            other += 1;
734                        }
735                    }
736                    Err(RuleError::RejectMissingOutpoint) => {
737                        let missing_txs = transaction
738                            .entries
739                            .iter()
740                            .zip(transaction.tx.inputs.iter())
741                            .filter_map(|(entry, input)| entry.is_none().then_some(input.previous_outpoint.transaction_id))
742                            .collect::<Vec<_>>();
743
744                        // A transaction may have missing outpoints for legitimate reasons related to concurrency, like a race condition between
745                        // an accepted block having not started yet or unfinished call to handle_new_block_transactions but already processed by
746                        // the consensus and this ongoing call to revalidate.
747                        //
748                        // So we only remove the transaction and keep its redeemers in the mempool because we cannot be sure they are invalid, in
749                        // fact in the race condition case they are valid regarding outpoints.
750                        let extra_info = match missing_txs.len() {
751                            0 => " but no missing tx!".to_string(), // this is never supposed to happen
752                            1 => format!(" missing tx {}", missing_txs[0]),
753                            n => format!(" with {} missing txs {}..{}", n, missing_txs[0], missing_txs.last().unwrap()),
754                        };
755
756                        // This call cleanly removes the invalid transaction.
757                        _ = mempool
758                            .remove_transaction(
759                                &transaction_id,
760                                false,
761                                TxRemovalReason::RevalidationWithMissingOutpoints,
762                                extra_info.as_str(),
763                            )
764                            .inspect_err(|err| warn!("Failed to remove transaction {} from mempool: {}", transaction_id, err));
765                        missing_outpoint += 1;
766                    }
767                    Err(err) => {
768                        // Rust rewrite note:
769                        // The behavior changes here compared to the golang version.
770                        // The failed revalidation is simply logged and the process continues.
771                        warn!(
772                            "Removing high priority transaction {0} and its redeemers, it failed revalidation with {1}",
773                            transaction_id, err
774                        );
775                        // This call cleanly removes the invalid transaction and its redeemers.
776                        _ = mempool
777                            .remove_transaction(&transaction_id, true, TxRemovalReason::Muted, "")
778                            .inspect_err(|err| warn!("Failed to remove transaction {} from mempool: {}", transaction_id, err));
779                        invalid += 1;
780                    }
781                }
782            }
783            if !valid_ids.is_empty() {
784                let _ = transaction_ids_sender.send(valid_ids);
785            }
786            drop(_swo);
787            drop(mempool);
788        }
789        match accepted + missing_outpoint + invalid {
790            0 => {
791                info!("Revalidated {} high priority transactions", valid);
792            }
793            _ => {
794                info!(
795                    "Revalidated {} and removed {} high priority transactions (removals: {} accepted, {} missing outpoint, {} invalid)",
796                    valid,
797                    accepted + missing_outpoint + invalid,
798                    accepted,
799                    missing_outpoint,
800                    invalid,
801                );
802                if other > 0 {
803                    debug!(
804                        "During revalidation of high priority transactions {} txs were removed from the mempool by concurrent flows",
805                        other
806                    )
807                }
808            }
809        }
810    }
811
812    /// is_transaction_output_dust returns whether or not the passed transaction output
813    /// amount is considered dust or not based on the configured minimum transaction
814    /// relay fee.
815    ///
816    /// Dust is defined in terms of the minimum transaction relay fee. In particular,
817    /// if the cost to the network to spend coins is more than 1/3 of the minimum
818    /// transaction relay fee, it is considered dust.
819    pub fn is_transaction_output_dust(&self, transaction_output: &TransactionOutput) -> bool {
820        self.mempool.read().is_transaction_output_dust(transaction_output)
821    }
822
823    pub fn has_accepted_transaction(&self, transaction_id: &TransactionId) -> bool {
824        self.mempool.read().has_accepted_transaction(transaction_id)
825    }
826
827    pub fn unaccepted_transactions(&self, transactions: Vec<TransactionId>) -> Vec<TransactionId> {
828        self.mempool.read().unaccepted_transactions(transactions)
829    }
830
831    pub fn unknown_transactions(&self, transactions: Vec<TransactionId>) -> Vec<TransactionId> {
832        self.mempool.read().unknown_transactions(transactions)
833    }
834
835    #[cfg(test)]
836    pub(crate) fn get_estimated_size(&self) -> usize {
837        self.mempool.read().get_estimated_size()
838    }
839}
840
841/// Async proxy for the mining manager
842#[derive(Clone)]
843pub struct MiningManagerProxy {
844    inner: Arc<MiningManager>,
845}
846
847impl MiningManagerProxy {
848    pub fn new(inner: Arc<MiningManager>) -> Self {
849        Self { inner }
850    }
851
852    pub async fn get_block_template(self, consensus: &ConsensusProxy, miner_data: MinerData) -> MiningManagerResult<BlockTemplate> {
853        consensus.clone().spawn_blocking(move |c| self.inner.get_block_template(c, &miner_data)).await
854    }
855
856    /// Returns realtime feerate estimations based on internal mempool state
857    pub async fn get_realtime_feerate_estimations(self) -> FeerateEstimations {
858        spawn_blocking(move || self.inner.get_realtime_feerate_estimations()).await.unwrap()
859    }
860
861    /// Returns realtime feerate estimations based on internal mempool state with additional verbose data
862    pub async fn get_realtime_feerate_estimations_verbose(
863        self,
864        consensus: &ConsensusProxy,
865        prefix: kaspa_addresses::Prefix,
866    ) -> MiningManagerResult<FeeEstimateVerbose> {
867        consensus.clone().spawn_blocking(move |c| self.inner.get_realtime_feerate_estimations_verbose(c, prefix)).await
868    }
869
870    /// Validates a transaction and adds it to the set of known transactions that have not yet been
871    /// added to any block.
872    ///
873    /// The validation is constrained by a Replace by fee policy applied
874    /// to double spends in the mempool. For more information, see [`RbfPolicy`].
875    ///
876    /// The returned transactions are references of objects owned by the mempool.
877    pub async fn validate_and_insert_transaction(
878        self,
879        consensus: &ConsensusProxy,
880        transaction: Transaction,
881        priority: Priority,
882        orphan: Orphan,
883        rbf_policy: RbfPolicy,
884    ) -> MiningManagerResult<TransactionInsertion> {
885        consensus
886            .clone()
887            .spawn_blocking(move |c| self.inner.validate_and_insert_transaction(c, transaction, priority, orphan, rbf_policy))
888            .await
889    }
890
891    /// Validates a batch of transactions, handling iteratively only the independent ones, and
892    /// adds those to the set of known transactions that have not yet been added to any block.
893    ///
894    /// The validation is constrained by a Replace by fee policy applied
895    /// to double spends in the mempool. For more information, see [`RbfPolicy`].
896    ///
897    /// Returns transactions that where unorphaned following the insertion of the provided
898    /// transactions. The returned transactions are references of objects owned by the mempool.
899    pub async fn validate_and_insert_transaction_batch(
900        self,
901        consensus: &ConsensusProxy,
902        transactions: Vec<Transaction>,
903        priority: Priority,
904        orphan: Orphan,
905        rbf_policy: RbfPolicy,
906    ) -> Vec<MiningManagerResult<Arc<Transaction>>> {
907        consensus
908            .clone()
909            .spawn_blocking(move |c| self.inner.validate_and_insert_transaction_batch(c, transactions, priority, orphan, rbf_policy))
910            .await
911    }
912
913    pub async fn handle_new_block_transactions(
914        self,
915        consensus: &ConsensusProxy,
916        block_daa_score: u64,
917        block_transactions: Arc<Vec<Transaction>>,
918    ) -> MiningManagerResult<Vec<Arc<Transaction>>> {
919        consensus
920            .clone()
921            .spawn_blocking(move |c| self.inner.handle_new_block_transactions(c, block_daa_score, &block_transactions))
922            .await
923    }
924
925    pub async fn expire_low_priority_transactions(self, consensus: &ConsensusProxy) {
926        consensus.clone().spawn_blocking(move |c| self.inner.expire_low_priority_transactions(c)).await;
927    }
928
929    pub async fn revalidate_high_priority_transactions(
930        self,
931        consensus: &ConsensusProxy,
932        transaction_ids_sender: UnboundedSender<Vec<TransactionId>>,
933    ) {
934        consensus.clone().spawn_blocking(move |c| self.inner.revalidate_high_priority_transactions(c, transaction_ids_sender)).await;
935    }
936
937    /// Try to return a mempool transaction by its id.
938    ///
939    /// Note: the transaction is an orphan if tx.is_fully_populated() returns false.
940    pub async fn get_transaction(self, transaction_id: TransactionId, query: TransactionQuery) -> Option<MutableTransaction> {
941        spawn_blocking(move || self.inner.get_transaction(&transaction_id, query)).await.unwrap()
942    }
943
944    /// Returns whether the mempool holds this transaction in any form.
945    pub async fn has_transaction(self, transaction_id: TransactionId, query: TransactionQuery) -> bool {
946        spawn_blocking(move || self.inner.has_transaction(&transaction_id, query)).await.unwrap()
947    }
948
949    pub async fn transaction_count(self, query: TransactionQuery) -> usize {
950        spawn_blocking(move || self.inner.transaction_count(query)).await.unwrap()
951    }
952
953    pub async fn get_all_transactions(self, query: TransactionQuery) -> (Vec<MutableTransaction>, Vec<MutableTransaction>) {
954        spawn_blocking(move || self.inner.get_all_transactions(query)).await.unwrap()
955    }
956
957    /// get_transactions_by_addresses returns the sending and receiving transactions for
958    /// a set of addresses.
959    ///
960    /// Note: a transaction is an orphan if tx.is_fully_populated() returns false.
961    pub async fn get_transactions_by_addresses(
962        self,
963        script_public_keys: ScriptPublicKeySet,
964        query: TransactionQuery,
965    ) -> GroupedOwnerTransactions {
966        spawn_blocking(move || self.inner.get_transactions_by_addresses(&script_public_keys, query)).await.unwrap()
967    }
968
969    /// Returns whether a transaction id was registered as accepted in the mempool, meaning
970    /// that the consensus accepted a block containing it and said block was handled by the
971    /// mempool.
972    ///
973    /// Registered transaction ids expire after a delay and are unregistered from the mempool.
974    /// So a returned value of true means with certitude that the transaction was accepted and
975    /// a false means either the transaction was never accepted or it was but beyond the expiration
976    /// delay.
977    pub async fn has_accepted_transaction(self, transaction_id: TransactionId) -> bool {
978        spawn_blocking(move || self.inner.has_accepted_transaction(&transaction_id)).await.unwrap()
979    }
980
981    /// Returns a vector of unaccepted transactions.
982    /// For more details, see [`Self::has_accepted_transaction()`].
983    pub async fn unaccepted_transactions(self, transactions: Vec<TransactionId>) -> Vec<TransactionId> {
984        spawn_blocking(move || self.inner.unaccepted_transactions(transactions)).await.unwrap()
985    }
986
987    /// Returns a vector with all transaction ids that are neither in the mempool, nor in the orphan pool
988    /// nor accepted.
989    pub async fn unknown_transactions(self, transactions: Vec<TransactionId>) -> Vec<TransactionId> {
990        spawn_blocking(move || self.inner.unknown_transactions(transactions)).await.unwrap()
991    }
992
993    pub fn snapshot(&self) -> MempoolCountersSnapshot {
994        self.inner.counters.snapshot()
995    }
996
997    pub fn p2p_tx_count_sample(&self) -> P2pTxCountSample {
998        self.inner.counters.p2p_tx_count_sample()
999    }
1000
1001    /// Returns a recent sample of transaction count which is not necessarily accurate
1002    /// but is updated enough for being used as a stats/metric
1003    pub fn transaction_count_sample(&self, query: TransactionQuery) -> u64 {
1004        let mut count = 0;
1005        if query.include_transaction_pool() {
1006            count += self.inner.counters.txs_sample.load(std::sync::atomic::Ordering::Relaxed)
1007        }
1008        if query.include_orphan_pool() {
1009            count += self.inner.counters.orphans_sample.load(std::sync::atomic::Ordering::Relaxed)
1010        }
1011        count
1012    }
1013}
1014
1015/// Represents statistical information about fee rates of transactions.
1016struct Stats {
1017    /// The maximum fee rate observed.
1018    max: f64,
1019    /// The median fee rate observed.
1020    median: f64,
1021    /// The minimum fee rate observed.
1022    min: f64,
1023}
1024/// Calculates the maximum, median, and minimum fee rates (fee per unit mass)
1025/// for a set of transactions, excluding the first transaction which is assumed
1026/// to be the coinbase transaction.
1027///
1028/// # Arguments
1029///
1030/// * `transactions` - A vector of `Transaction` objects. The first transaction
1031///   is assumed to be the coinbase transaction and is excluded from fee rate
1032///   calculations.
1033/// * `calculated_fees` - A vector of fees associated with the transactions.
1034///   This vector should have one less element than the `transactions` vector
1035///   since the first transaction (coinbase) does not have a fee.
1036///
1037/// # Returns
1038///
1039/// Returns an `Option<Stats>` containing the maximum, median, and minimum fee
1040/// rates if the input vectors are valid. Returns `None` if the vectors are
1041/// empty or if the lengths are inconsistent.
1042fn feerate_stats(transactions: Vec<Transaction>, calculated_fees: Vec<u64>) -> Option<Stats> {
1043    if calculated_fees.is_empty() {
1044        return None;
1045    }
1046    if transactions.len() != calculated_fees.len() + 1 {
1047        error!(
1048            "[feerate_stats] block template transactions length ({}) is expected to be one more than `calculated_fees` length ({})",
1049            transactions.len(),
1050            calculated_fees.len()
1051        );
1052        return None;
1053    }
1054    debug_assert!(transactions[0].is_coinbase());
1055    let mut feerates = calculated_fees
1056        .into_iter()
1057        .zip(transactions
1058            .iter()
1059            // skip coinbase tx
1060            .skip(1)
1061            .map(Transaction::mass))
1062        .map(|(fee, mass)| fee as f64 / mass as f64)
1063        .collect_vec();
1064    feerates.sort_unstable_by(f64::total_cmp);
1065
1066    let max = feerates[feerates.len() - 1];
1067    let min = feerates[0];
1068    let median = feerates[feerates.len() / 2];
1069
1070    Some(Stats { max, median, min })
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075    use super::*;
1076    use kaspa_consensus_core::subnets;
1077    use std::iter::repeat;
1078
1079    fn transactions(length: usize) -> Vec<Transaction> {
1080        let tx = || {
1081            let tx = Transaction::new(0, vec![], vec![], 0, Default::default(), 0, vec![]);
1082            tx.set_mass(2);
1083            tx
1084        };
1085        let mut txs = repeat(tx()).take(length).collect_vec();
1086        txs[0].subnetwork_id = subnets::SUBNETWORK_ID_COINBASE;
1087        txs
1088    }
1089
1090    #[test]
1091    fn feerate_stats_test() {
1092        let calculated_fees = vec![100u64, 200, 300, 400];
1093        let txs = transactions(calculated_fees.len() + 1);
1094        let Stats { max, median, min } = feerate_stats(txs, calculated_fees).unwrap();
1095        assert_eq!(max, 200.0);
1096        assert_eq!(median, 150.0);
1097        assert_eq!(min, 50.0);
1098    }
1099
1100    #[test]
1101    fn feerate_stats_empty_test() {
1102        let calculated_fees = vec![];
1103        let txs = transactions(calculated_fees.len() + 1);
1104        assert!(feerate_stats(txs, calculated_fees).is_none());
1105    }
1106
1107    #[test]
1108    fn feerate_stats_inconsistent_test() {
1109        let calculated_fees = vec![100u64, 200, 300, 400];
1110        let txs = transactions(calculated_fees.len());
1111        assert!(feerate_stats(txs, calculated_fees).is_none());
1112    }
1113}