Skip to main content

kaspa_mining/mempool/model/frontier/
feerate_key.rs

1use crate::mempool::model::{frontier::selectors::ALPHA, tx::MempoolTransaction};
2use kaspa_consensus_core::mass::{ContextualMasses, Mass, MassCofactors};
3use kaspa_consensus_core::subnets::SubnetworkId;
4use kaspa_consensus_core::tx::Transaction;
5use std::sync::Arc;
6
7#[derive(Clone, Debug)]
8pub struct FeerateTransactionKey {
9    pub fee: u64,
10    pub mass: u64,
11    weight: f64,
12    pub tx: Arc<Transaction>,
13}
14
15impl Eq for FeerateTransactionKey {}
16
17impl PartialEq for FeerateTransactionKey {
18    fn eq(&self, other: &Self) -> bool {
19        self.tx.id() == other.tx.id()
20    }
21}
22
23impl FeerateTransactionKey {
24    pub fn new(fee: u64, mass: u64, tx: Arc<Transaction>) -> Self {
25        // NOTE: any change to the way this weight is calculated (such as scaling by some factor)
26        // requires a reversed update to total_weight in `Frontier::build_feerate_estimator`. This
27        // is because the math methods in FeeEstimator assume this specific weight function.
28        //
29        // Gas is intentionally not folded into this global weight: gas capacity is lane-local and
30        // is enforced by selectors during block template construction.
31        Self { fee, mass, weight: (fee as f64 / mass as f64).powi(ALPHA), tx }
32    }
33
34    pub fn feerate(&self) -> f64 {
35        self.fee as f64 / self.mass as f64
36    }
37
38    pub fn weight(&self) -> f64 {
39        self.weight
40    }
41
42    pub fn lane(&self) -> SubnetworkId {
43        self.tx.subnetwork_id
44    }
45}
46
47impl std::hash::Hash for FeerateTransactionKey {
48    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
49        // Transaction id is a sufficient identifier for this key
50        self.tx.id().hash(state);
51    }
52}
53
54impl PartialOrd for FeerateTransactionKey {
55    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
56        Some(self.cmp(other))
57    }
58}
59
60impl Ord for FeerateTransactionKey {
61    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
62        // Our first priority is the feerate.
63        // The weight function is monotonic in feerate so we prefer using it
64        // since it is cached
65        match self.weight().total_cmp(&other.weight()) {
66            core::cmp::Ordering::Equal => {}
67            ord => return ord,
68        }
69
70        // If feerates (and thus weights) are equal, prefer the higher fee in absolute value
71        match self.fee.cmp(&other.fee) {
72            core::cmp::Ordering::Equal => {}
73            ord => return ord,
74        }
75
76        //
77        // At this point we don't compare the mass fields since if both feerate
78        // and fee are equal, mass must be equal as well
79        //
80
81        // Finally, we compare transaction ids in order to allow multiple transactions with
82        // the same fee and mass to exist within the same sorted container
83        self.tx.id().cmp(&other.tx.id())
84    }
85}
86
87impl FeerateTransactionKey {
88    pub(crate) fn from_tx(tx: &MempoolTransaction, cofactors: &MassCofactors) -> Self {
89        // NOTE: The code below is a mempool simplification reducing the various block mass units to a
90        //       single one-dimension value (making it easier to select transactions for block templates).
91        // Future mempool improvements are expected to refine this behavior and use the multi-dimension values
92        // in order to optimize and increase block space usage.
93        let mass = Mass::new(
94            tx.mtx.calculated_non_contextual_masses.expect("masses are expected to be calculated"),
95            ContextualMasses::new(tx.mtx.tx.storage_mass()),
96        )
97        .normalized_max(cofactors);
98        let fee = tx.mtx.calculated_fee.expect("fee is expected to be populated");
99        Self::new(fee, mass, tx.mtx.tx.clone())
100    }
101}
102
103#[cfg(test)]
104pub(crate) mod tests {
105    use super::*;
106    use kaspa_consensus_core::{
107        subnets::SUBNETWORK_ID_NATIVE,
108        tx::{Transaction, TransactionInput, TransactionOutpoint},
109    };
110    use kaspa_hashes::{HasherBase, TransactionID};
111    use std::sync::Arc;
112
113    fn generate_unique_tx(i: u64) -> Arc<Transaction> {
114        let mut hasher = TransactionID::new();
115        let prev = hasher.update(i.to_le_bytes()).clone().finalize();
116        let input = TransactionInput::new(TransactionOutpoint::new(prev, 0), vec![], 0, 0);
117        Arc::new(Transaction::new(0, vec![input], vec![], 0, SUBNETWORK_ID_NATIVE, 0, vec![]))
118    }
119
120    /// Test helper for generating a feerate key with a unique tx (per u64 id)
121    pub(crate) fn build_feerate_key(fee: u64, mass: u64, id: u64) -> FeerateTransactionKey {
122        FeerateTransactionKey::new(fee, mass, generate_unique_tx(id))
123    }
124}