mempool/backend/rust-gbt/src/audit_transaction.rs

221 lines
6.9 KiB
Rust
Raw Normal View History

2023-06-25 19:04:01 -07:00
use crate::{
2023-06-29 15:48:40 -07:00
u32_hasher_types::{u32hashset_new, U32HasherState},
ThreadTransaction,
2023-06-25 19:04:01 -07:00
};
2023-06-23 20:41:39 -04:00
use std::{
cmp::Ordering,
collections::HashSet,
hash::{Hash, Hasher},
};
2023-06-23 16:42:58 -04:00
#[allow(clippy::struct_excessive_bools)]
2023-06-26 19:29:32 -07:00
#[derive(Clone, Debug)]
2023-06-23 16:42:58 -04:00
pub struct AuditTransaction {
2023-06-23 20:41:39 -04:00
pub uid: u32,
order: u32,
2023-06-23 20:41:39 -04:00
pub fee: u64,
pub weight: u32,
// exact sigop-adjusted weight
pub sigop_adjusted_weight: u32,
// sigop-adjusted vsize rounded up the the next integer
2023-06-27 18:51:18 -04:00
pub sigop_adjusted_vsize: u32,
2023-06-23 20:41:39 -04:00
pub sigops: u32,
adjusted_fee_per_vsize: f64,
2023-06-23 20:41:39 -04:00
pub effective_fee_per_vsize: f64,
pub dependency_rate: f64,
pub inputs: Vec<u32>,
pub relatives_set_flag: bool,
2023-06-25 19:04:01 -07:00
pub ancestors: HashSet<u32, U32HasherState>,
pub children: HashSet<u32, U32HasherState>,
ancestor_fee: u64,
ancestor_sigop_adjusted_weight: u32,
2023-06-27 18:51:18 -04:00
ancestor_sigop_adjusted_vsize: u32,
ancestor_sigops: u32,
// Safety: Must be private to prevent NaN breaking Ord impl.
score: f64,
2023-06-23 20:41:39 -04:00
pub used: bool,
2023-06-26 17:35:33 -04:00
/// whether this transaction has been moved to the "modified" priority queue
2023-06-23 20:41:39 -04:00
pub modified: bool,
pub dirty: bool,
2023-06-23 16:42:58 -04:00
}
impl Hash for AuditTransaction {
2023-06-23 20:41:39 -04:00
fn hash<H: Hasher>(&self, state: &mut H) {
self.uid.hash(state);
}
2023-06-23 16:42:58 -04:00
}
impl PartialEq for AuditTransaction {
2023-06-23 20:41:39 -04:00
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
}
2023-06-23 16:42:58 -04:00
}
impl Eq for AuditTransaction {}
2023-06-27 22:48:22 -07:00
#[inline]
pub fn partial_cmp_uid_score(a: (u32, u32, f64), b: (u32, u32, f64)) -> Option<Ordering> {
2023-06-27 22:48:22 -07:00
// If either score is NaN, this is false,
// and partial_cmp will return None
if a.2 != b.2 {
2023-07-03 11:57:12 -04:00
// compare by score (sorts by ascending score)
a.2.partial_cmp(&b.2)
} else if a.1 != b.1 {
2023-07-03 11:57:12 -04:00
// tie-break by comparing partial txids (sorts by descending txid)
Some(b.1.cmp(&a.1))
2023-06-27 22:48:22 -07:00
} else {
2023-07-03 11:57:12 -04:00
// tie-break partial txid collisions by comparing uids (sorts by descending uid)
Some(b.0.cmp(&a.0))
2023-06-27 22:48:22 -07:00
}
}
2023-06-23 16:42:58 -04:00
impl PartialOrd for AuditTransaction {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
partial_cmp_uid_score(
(self.uid, self.order, self.score),
2023-07-03 11:57:12 -04:00
(other.uid, other.order, other.score),
)
2023-06-23 16:42:58 -04:00
}
}
impl Ord for AuditTransaction {
fn cmp(&self, other: &Self) -> Ordering {
// Safety: The only possible values for score are f64
// that are not NaN. This is because outside code can not
// freely assign score. Also, calc_new_score guarantees no NaN.
2023-06-24 16:41:53 -07:00
self.partial_cmp(other).expect("score will never be NaN")
2023-06-23 20:41:39 -04:00
}
}
2023-06-23 23:06:13 -07:00
#[inline]
fn calc_fee_rate(fee: f64, vsize: f64) -> f64 {
fee / (if vsize == 0.0 { 1.0 } else { vsize })
}
2023-06-23 23:06:13 -07:00
impl AuditTransaction {
pub fn from_thread_transaction(tx: &ThreadTransaction) -> Self {
2023-06-27 22:48:22 -07:00
// rounded up to the nearest integer
let is_adjusted = tx.weight < (tx.sigops * 20);
2023-06-27 22:48:22 -07:00
let sigop_adjusted_vsize = ((tx.weight + 3) / 4).max(tx.sigops * 5);
let sigop_adjusted_weight = tx.weight.max(tx.sigops * 20);
let effective_fee_per_vsize = if is_adjusted {
calc_fee_rate(tx.fee, f64::from(sigop_adjusted_weight) / 4.0)
} else {
tx.effective_fee_per_vsize
};
Self {
2023-06-23 23:06:13 -07:00
uid: tx.uid,
order: tx.order,
fee: tx.fee as u64,
2023-06-23 23:06:13 -07:00
weight: tx.weight,
sigop_adjusted_weight,
2023-06-27 22:48:22 -07:00
sigop_adjusted_vsize,
2023-06-23 23:06:13 -07:00
sigops: tx.sigops,
adjusted_fee_per_vsize: calc_fee_rate(tx.fee, f64::from(sigop_adjusted_vsize)),
effective_fee_per_vsize,
2023-06-23 23:06:13 -07:00
dependency_rate: f64::INFINITY,
inputs: tx.inputs.clone(),
relatives_set_flag: false,
2023-06-25 19:04:01 -07:00
ancestors: u32hashset_new(),
children: u32hashset_new(),
ancestor_fee: tx.fee as u64,
ancestor_sigop_adjusted_weight: sigop_adjusted_weight,
2023-06-27 22:48:22 -07:00
ancestor_sigop_adjusted_vsize: sigop_adjusted_vsize,
2023-06-23 23:06:13 -07:00
ancestor_sigops: tx.sigops,
score: 0.0,
used: false,
modified: false,
dirty: effective_fee_per_vsize != tx.effective_fee_per_vsize,
2023-06-23 23:06:13 -07:00
}
}
#[inline]
pub const fn score(&self) -> f64 {
self.score
}
#[inline]
pub const fn order(&self) -> u32 {
self.order
}
#[inline]
2023-06-27 18:51:18 -04:00
pub const fn ancestor_sigop_adjusted_vsize(&self) -> u32 {
self.ancestor_sigop_adjusted_vsize
}
#[inline]
pub const fn ancestor_sigops(&self) -> u32 {
self.ancestor_sigops
}
2023-06-26 19:29:32 -07:00
#[inline]
pub fn cluster_rate(&self) -> f64 {
// Safety: self.ancestor_weight can never be 0.
// Even if it could, as it approaches 0, the value inside the min() call
// grows, so if we think of 0 as "grew infinitely" then dependency_rate would be
// the smaller of the two. If either side is NaN, the other side is returned.
self.dependency_rate.min(calc_fee_rate(
self.ancestor_fee as f64,
f64::from(self.ancestor_sigop_adjusted_weight) / 4.0,
))
2023-06-26 19:29:32 -07:00
}
pub fn set_dirty_if_different(&mut self, cluster_rate: f64) {
if self.effective_fee_per_vsize != cluster_rate {
self.effective_fee_per_vsize = cluster_rate;
self.dirty = true;
}
}
/// Safety: This function must NEVER set score to NaN.
#[inline]
2023-06-25 16:16:24 -07:00
fn calc_new_score(&mut self) {
2023-06-29 15:48:40 -07:00
self.score = self.adjusted_fee_per_vsize.min(calc_fee_rate(
self.ancestor_fee as f64,
f64::from(self.ancestor_sigop_adjusted_vsize),
2023-06-29 15:48:40 -07:00
));
}
#[inline]
pub fn set_ancestors(
&mut self,
2023-06-25 19:04:01 -07:00
ancestors: HashSet<u32, U32HasherState>,
total_fee: u64,
total_sigop_adjusted_weight: u32,
2023-06-27 18:51:18 -04:00
total_sigop_adjusted_vsize: u32,
total_sigops: u32,
) {
self.ancestors = ancestors;
self.ancestor_fee = self.fee + total_fee;
self.ancestor_sigop_adjusted_weight =
self.sigop_adjusted_weight + total_sigop_adjusted_weight;
2023-06-27 18:51:18 -04:00
self.ancestor_sigop_adjusted_vsize = self.sigop_adjusted_vsize + total_sigop_adjusted_vsize;
self.ancestor_sigops = self.sigops + total_sigops;
self.calc_new_score();
self.relatives_set_flag = true;
}
#[inline]
pub fn remove_root(
&mut self,
root_txid: u32,
root_fee: u64,
root_sigop_adjusted_weight: u32,
2023-06-27 18:51:18 -04:00
root_sigop_adjusted_vsize: u32,
root_sigops: u32,
cluster_rate: f64,
) -> f64 {
let old_score = self.score();
self.dependency_rate = self.dependency_rate.min(cluster_rate);
2023-06-26 17:35:33 -04:00
if self.ancestors.remove(&root_txid) {
self.ancestor_fee -= root_fee;
self.ancestor_sigop_adjusted_weight -= root_sigop_adjusted_weight;
2023-06-27 18:51:18 -04:00
self.ancestor_sigop_adjusted_vsize -= root_sigop_adjusted_vsize;
2023-06-26 17:35:33 -04:00
self.ancestor_sigops -= root_sigops;
self.calc_new_score();
}
old_score
}
2023-06-23 23:06:13 -07:00
}