1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use crate::mempool::{
errors::{RuleError, RuleResult},
model::tx::{DoubleSpend, MempoolTransaction, TxRemovalReason},
tx::RbfPolicy,
Mempool,
};
use kaspa_consensus_core::tx::{MutableTransaction, Transaction};
use std::sync::Arc;
impl Mempool {
/// Returns the replace by fee (RBF) constraint fee/mass threshold for an incoming transaction and a policy.
///
/// Fails if the transaction does not meet some condition of the RBF policy, excluding the fee/mass condition.
///
/// See [`RbfPolicy`] variants for details of each policy process and success conditions.
pub(super) fn get_replace_by_fee_constraint(
&self,
transaction: &MutableTransaction,
rbf_policy: RbfPolicy,
) -> RuleResult<Option<f64>> {
match rbf_policy {
RbfPolicy::Forbidden => {
// When RBF is forbidden, fails early on any double spend
self.transaction_pool.check_double_spends(transaction)?;
Ok(None)
}
RbfPolicy::Allowed => {
// When RBF is allowed, never fails since both insertion and replacement are possible
let double_spends = self.transaction_pool.get_double_spend_transaction_ids(transaction);
if double_spends.is_empty() {
Ok(None)
} else {
let mut feerate_threshold = 0f64;
for double_spend in double_spends {
// We take the max over all double spends as the required threshold
feerate_threshold = feerate_threshold.max(self.get_double_spend_feerate(&double_spend)?);
}
Ok(Some(feerate_threshold))
}
}
RbfPolicy::Mandatory => {
// When RBF is mandatory, fails early if we do not have exactly one double spending transaction
let double_spends = self.transaction_pool.get_double_spend_transaction_ids(transaction);
match double_spends.len() {
0 => Err(RuleError::RejectRbfNoDoubleSpend),
1 => {
let feerate_threshold = self.get_double_spend_feerate(&double_spends[0])?;
Ok(Some(feerate_threshold))
}
_ => Err(RuleError::RejectRbfTooManyDoubleSpendingTransactions),
}
}
}
}
/// Executes replace by fee (RBF) for an incoming transaction and a policy.
///
/// See [`RbfPolicy`] variants for details of each policy process and success conditions.
///
/// On success, `transaction` is guaranteed to embed no double spend with the mempool.
///
/// On success with the [`RbfPolicy::Mandatory`] policy, some removed transaction is always returned.
pub(super) fn execute_replace_by_fee(
&mut self,
transaction: &MutableTransaction,
rbf_policy: RbfPolicy,
) -> RuleResult<Option<Arc<Transaction>>> {
match rbf_policy {
RbfPolicy::Forbidden => {
self.transaction_pool.check_double_spends(transaction)?;
Ok(None)
}
RbfPolicy::Allowed => {
let double_spends = self.transaction_pool.get_double_spend_transaction_ids(transaction);
match double_spends.is_empty() {
true => Ok(None),
false => {
let removed = self.validate_double_spending_transaction(transaction, &double_spends[0])?.mtx.tx.clone();
for double_spend in double_spends.iter().skip(1) {
// Validate the feerate threshold is passed for all double spends
self.validate_double_spending_transaction(transaction, double_spend)?;
}
// We apply consequences such as removal only after we fully validate against all double spends
for double_spend in double_spends {
self.remove_transaction(
&double_spend.owner_id,
true,
TxRemovalReason::ReplacedByFee,
format!("by {}", transaction.id()).as_str(),
)?;
}
Ok(Some(removed))
}
}
}
RbfPolicy::Mandatory => {
let double_spends = self.transaction_pool.get_double_spend_transaction_ids(transaction);
match double_spends.len() {
0 => Err(RuleError::RejectRbfNoDoubleSpend),
1 => {
let removed = self.validate_double_spending_transaction(transaction, &double_spends[0])?.mtx.tx.clone();
self.remove_transaction(
&double_spends[0].owner_id,
true,
TxRemovalReason::ReplacedByFee,
format!("by {}", transaction.id()).as_str(),
)?;
Ok(Some(removed))
}
_ => Err(RuleError::RejectRbfTooManyDoubleSpendingTransactions),
}
}
}
}
fn get_double_spend_feerate(&self, double_spend: &DoubleSpend) -> RuleResult<f64> {
let owner = self.transaction_pool.get_double_spend_owner(double_spend)?;
match owner.mtx.calculated_feerate() {
Some(double_spend_feerate) => Ok(double_spend_feerate),
// Getting here is unexpected since a mempool owned tx should be populated with fee
// and mass at this stage but nonetheless we fail gracefully
None => Err(double_spend.into()),
}
}
fn validate_double_spending_transaction<'a>(
&'a self,
transaction: &MutableTransaction,
double_spend: &DoubleSpend,
) -> RuleResult<&'a MempoolTransaction> {
let owner = self.transaction_pool.get_double_spend_owner(double_spend)?;
if let (Some(transaction_feerate), Some(double_spend_feerate)) =
(transaction.calculated_feerate(), owner.mtx.calculated_feerate())
{
if transaction_feerate > double_spend_feerate {
return Ok(owner);
} else {
return Err(double_spend.into());
}
}
// Getting here is unexpected since both txs should be populated with
// fee and mass at this stage but nonetheless we fail gracefully
Err(double_spend.into())
}
}