kaspa_mining/
lib.rs

1use std::{
2    sync::atomic::{AtomicU64, Ordering},
3    time::{Duration, Instant},
4};
5
6use mempool::tx::Priority;
7
8mod block_template;
9pub(crate) mod cache;
10pub mod errors;
11pub mod feerate;
12pub mod manager;
13mod manager_tests;
14pub mod mempool;
15pub mod model;
16pub mod monitor;
17
18// Exposed for benchmarks
19pub use block_template::{policy::Policy, selector::RebalancingWeightedTransactionSelector};
20pub use mempool::model::frontier::{feerate_key::FeerateTransactionKey, search_tree::SearchTree, Frontier};
21
22#[cfg(test)]
23pub mod testutils;
24
25pub struct MiningCounters {
26    pub creation_time: Instant,
27
28    // Counters
29    pub high_priority_tx_counts: AtomicU64,
30    pub low_priority_tx_counts: AtomicU64,
31    pub block_tx_counts: AtomicU64,
32    pub tx_accepted_counts: AtomicU64,
33    pub tx_evicted_counts: AtomicU64,
34    pub input_counts: AtomicU64,
35    pub output_counts: AtomicU64,
36
37    // Samples
38    pub ready_txs_sample: AtomicU64,
39    pub txs_sample: AtomicU64,
40    pub orphans_sample: AtomicU64,
41    pub accepted_sample: AtomicU64,
42}
43
44impl Default for MiningCounters {
45    fn default() -> Self {
46        Self {
47            creation_time: Instant::now(),
48            high_priority_tx_counts: Default::default(),
49            low_priority_tx_counts: Default::default(),
50            block_tx_counts: Default::default(),
51            tx_accepted_counts: Default::default(),
52            tx_evicted_counts: Default::default(),
53            input_counts: Default::default(),
54            output_counts: Default::default(),
55            ready_txs_sample: Default::default(),
56            txs_sample: Default::default(),
57            orphans_sample: Default::default(),
58            accepted_sample: Default::default(),
59        }
60    }
61}
62
63impl MiningCounters {
64    pub fn snapshot(&self) -> MempoolCountersSnapshot {
65        MempoolCountersSnapshot {
66            elapsed_time: (Instant::now() - self.creation_time),
67            high_priority_tx_counts: self.high_priority_tx_counts.load(Ordering::Relaxed),
68            low_priority_tx_counts: self.low_priority_tx_counts.load(Ordering::Relaxed),
69            block_tx_counts: self.block_tx_counts.load(Ordering::Relaxed),
70            tx_accepted_counts: self.tx_accepted_counts.load(Ordering::Relaxed),
71            tx_evicted_counts: self.tx_evicted_counts.load(Ordering::Relaxed),
72            input_counts: self.input_counts.load(Ordering::Relaxed),
73            output_counts: self.output_counts.load(Ordering::Relaxed),
74            ready_txs_sample: self.ready_txs_sample.load(Ordering::Relaxed),
75            txs_sample: self.txs_sample.load(Ordering::Relaxed),
76            orphans_sample: self.orphans_sample.load(Ordering::Relaxed),
77            accepted_sample: self.accepted_sample.load(Ordering::Relaxed),
78        }
79    }
80
81    pub fn p2p_tx_count_sample(&self) -> P2pTxCountSample {
82        P2pTxCountSample {
83            elapsed_time: (Instant::now() - self.creation_time),
84            low_priority_tx_counts: self.low_priority_tx_counts.load(Ordering::Relaxed),
85        }
86    }
87
88    pub fn increase_tx_counts(&self, value: u64, priority: Priority) {
89        match priority {
90            Priority::Low => {
91                self.low_priority_tx_counts.fetch_add(value, Ordering::Relaxed);
92            }
93            Priority::High => {
94                self.high_priority_tx_counts.fetch_add(value, Ordering::Relaxed);
95            }
96        }
97    }
98}
99
100#[derive(Debug, PartialEq, Eq)]
101pub struct MempoolCountersSnapshot {
102    pub elapsed_time: Duration,
103    pub high_priority_tx_counts: u64,
104    pub low_priority_tx_counts: u64,
105    pub block_tx_counts: u64,
106    pub tx_accepted_counts: u64,
107    pub tx_evicted_counts: u64,
108    pub input_counts: u64,
109    pub output_counts: u64,
110    pub ready_txs_sample: u64,
111    pub txs_sample: u64,
112    pub orphans_sample: u64,
113    pub accepted_sample: u64,
114}
115
116impl MempoolCountersSnapshot {
117    pub fn in_tx_counts(&self) -> u64 {
118        self.high_priority_tx_counts + self.low_priority_tx_counts
119    }
120
121    /// Indicates whether this snapshot has any TPS activity which is worth logging
122    pub fn has_tps_activity(&self) -> bool {
123        self.tx_accepted_counts > 0 || self.block_tx_counts > 0 || self.low_priority_tx_counts > 0 || self.high_priority_tx_counts > 0
124    }
125
126    /// Returns an estimate of _Unique-TPS_, i.e. the number of unique transactions per second on average
127    /// (excluding coinbase transactions)
128    pub fn u_tps(&self) -> f64 {
129        let elapsed = self.elapsed_time.as_secs_f64();
130        if elapsed != 0f64 {
131            self.tx_accepted_counts as f64 / elapsed
132        } else {
133            0f64
134        }
135    }
136
137    /// Returns an estimate to the _Effective-TPS_ fraction which is a measure of how much of DAG capacity
138    /// is utilized compared to the number of available mempool transactions. For instance a max
139    /// value of `1.0` indicates that we cannot do any better in terms of throughput vs. current
140    /// demand. A value close to `0.0` means that DAG capacity is mostly filled with duplicate
141    /// transactions even though the mempool (demand) offers a much larger amount of unique transactions.   
142    pub fn e_tps(&self) -> f64 {
143        let accepted_txs = u64::min(self.ready_txs_sample, self.tx_accepted_counts); // The throughput
144        let total_txs = u64::min(self.ready_txs_sample, self.block_tx_counts); // The min of demand and capacity
145        if total_txs > 0 {
146            accepted_txs as f64 / total_txs as f64
147        } else {
148            1f64 // No demand means we are 100% efficient
149        }
150    }
151}
152
153impl core::ops::Sub for &MempoolCountersSnapshot {
154    type Output = MempoolCountersSnapshot;
155
156    fn sub(self, rhs: Self) -> Self::Output {
157        Self::Output {
158            elapsed_time: self.elapsed_time.saturating_sub(rhs.elapsed_time),
159            high_priority_tx_counts: self.high_priority_tx_counts.saturating_sub(rhs.high_priority_tx_counts),
160            low_priority_tx_counts: self.low_priority_tx_counts.saturating_sub(rhs.low_priority_tx_counts),
161            block_tx_counts: self.block_tx_counts.saturating_sub(rhs.block_tx_counts),
162            tx_accepted_counts: self.tx_accepted_counts.saturating_sub(rhs.tx_accepted_counts),
163            tx_evicted_counts: self.tx_evicted_counts.saturating_sub(rhs.tx_evicted_counts),
164            input_counts: self.input_counts.saturating_sub(rhs.input_counts),
165            output_counts: self.output_counts.saturating_sub(rhs.output_counts),
166            ready_txs_sample: (self.ready_txs_sample + rhs.ready_txs_sample) / 2,
167            txs_sample: (self.txs_sample + rhs.txs_sample) / 2,
168            orphans_sample: (self.orphans_sample + rhs.orphans_sample) / 2,
169            accepted_sample: (self.accepted_sample + rhs.accepted_sample) / 2,
170        }
171    }
172}
173
174/// Contains a snapshot of only the P2P transaction counter and time elapsed
175pub struct P2pTxCountSample {
176    pub elapsed_time: Duration,
177    pub low_priority_tx_counts: u64,
178}
179
180impl core::ops::Sub for &P2pTxCountSample {
181    type Output = P2pTxCountSample;
182
183    fn sub(self, rhs: Self) -> Self::Output {
184        Self::Output {
185            elapsed_time: self.elapsed_time.saturating_sub(rhs.elapsed_time),
186            low_priority_tx_counts: self.low_priority_tx_counts.saturating_sub(rhs.low_priority_tx_counts),
187        }
188    }
189}