Skip to main content

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