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 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 let block_template = BlockTemplateBuilder::modify_block_template(consensus, miner_data, &immutable_template)?;
94
95 return Ok(block_template);
98 }
99
100 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 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 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 pub(crate) fn build_selector(&self) -> Box<dyn TemplateTransactionSelector> {
202 self.mempool.read().build_selector()
203 }
204
205 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 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 {
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 #[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 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 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 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 let validation_result = validate_mempool_transaction(consensus, &mut transaction, &args);
308 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 let mut accepted_transactions = Vec::with_capacity(unorphaned_transactions.len() + 1);
317 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 let mut accepted_transactions = Vec::with_capacity(incoming_transactions.len());
335 let args = TransactionValidationBatchArgs::new();
338 while !incoming_transactions.is_empty() {
341 let (mut transactions, priorities): (Vec<MutableTransaction>, Vec<Priority>) =
344 incoming_transactions.into_iter().map(|x| (x.mtx, x.priority)).unzip();
345
346 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 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 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 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 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 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 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 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 .map(|relative_index| relative_index.max(1) + lower_bound)
526 .or(Some(transactions.len()))
527 }
528
529 pub fn get_transaction(&self, transaction_id: &TransactionId, query: TransactionQuery) -> Option<MutableTransaction> {
533 self.mempool.read().get_transaction(transaction_id, query)
534 }
535
536 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 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 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 pub fn get_transactions_by_addresses(
569 &self,
570 script_public_keys: &ScriptPublicKeySet,
571 query: TransactionQuery,
572 ) -> GroupedOwnerTransactions {
573 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 let unorphaned_transactions = self.mempool.write().handle_new_block_transactions(block_daa_score, block_transactions)?;
593
594 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 debug!("<> Expiring low priority transactions...");
603
604 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 self.mempool.write().expire_accepted_transactions(consensus);
611
612 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 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 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 let _swo = Stopwatch::<800>::with_threshold("revalidate topological_sort op");
663 let sorted_transactions = transactions.topological_into_iter();
664 drop(_swo);
665
666 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 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 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 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 if mempool.update_revalidated_transaction(transaction) {
724 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 let extra_info = match missing_txs.len() {
751 0 => " but no missing tx!".to_string(), 1 => format!(" missing tx {}", missing_txs[0]),
753 n => format!(" with {} missing txs {}..{}", n, missing_txs[0], missing_txs.last().unwrap()),
754 };
755
756 _ = 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 warn!(
772 "Removing high priority transaction {0} and its redeemers, it failed revalidation with {1}",
773 transaction_id, err
774 );
775 _ = 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 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#[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 pub async fn get_realtime_feerate_estimations(self) -> FeerateEstimations {
858 spawn_blocking(move || self.inner.get_realtime_feerate_estimations()).await.unwrap()
859 }
860
861 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 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 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 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 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 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 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 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 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 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
1015struct Stats {
1017 max: f64,
1019 median: f64,
1021 min: f64,
1023}
1024fn 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(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}