Skip to content

Commit f9a64a0

Browse files
jkczyzclaude
andcommitted
Refactor the splice funding-feerate helpers into fee_estimator.rs
The 1.5x-of-estimate funding feerate ceiling was open-coded identically in splice_in and splice_out. Route both through a max_funding_feerate helper and keep it, alongside rbf_splice_feerates, in fee_estimator.rs so the splice funding-feerate policy lives in one place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 54eb085 commit f9a64a0

2 files changed

Lines changed: 44 additions & 37 deletions

File tree

src/fee_estimator.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,42 @@ pub(crate) fn apply_post_estimation_adjustments(
164164
_ => estimated_rate,
165165
}
166166
}
167+
168+
/// The most we are willing to pay for a channel funding transaction: `1.5x` our funding feerate
169+
/// estimate. Used as the `max_feerate` ceiling for splices and their RBF fee bumps.
170+
pub(crate) fn max_funding_feerate(estimate: FeeRate) -> FeeRate {
171+
FeeRate::from_sat_per_kwu(estimate.to_sat_per_kwu() * 3 / 2)
172+
}
173+
174+
/// Picks the `(target, max)` feerates for replacing a pending splice's in-flight funding
175+
/// transaction via RBF, or `None` if the RBF can't be done within our fee ceiling.
176+
///
177+
/// `max` is the most we are willing to pay (see [`max_funding_feerate`]), which tracks our current
178+
/// estimate and so may have risen or fallen since the original splice; it is never inflated to meet
179+
/// the RBF minimum. `target` is what we actually pay — our current estimate, or the template's RBF
180+
/// minimum if that is higher (required to replace the transaction). If that minimum exceeds `max`,
181+
/// we can't RBF.
182+
pub(crate) fn rbf_splice_feerates(
183+
estimate: FeeRate, min_rbf_feerate: FeeRate,
184+
) -> Option<(FeeRate, FeeRate)> {
185+
let max = max_funding_feerate(estimate);
186+
let target = estimate.max(min_rbf_feerate);
187+
(target <= max).then_some((target, max))
188+
}
189+
190+
#[cfg(test)]
191+
mod tests {
192+
use super::*;
193+
194+
#[test]
195+
fn rbf_splice_feerates_target_and_max() {
196+
let kwu = FeeRate::from_sat_per_kwu;
197+
// Estimate below the RBF minimum but within our ceiling: pay the minimum to replace the
198+
// transaction; the max stays 1.5x the estimate (never inflated) and already clears it.
199+
assert_eq!(rbf_splice_feerates(kwu(253), kwu(278)), Some((kwu(278), kwu(253 * 3 / 2))));
200+
// Estimate risen above the RBF minimum: pay the higher estimate, not the stale minimum.
201+
assert_eq!(rbf_splice_feerates(kwu(500), kwu(278)), Some((kwu(500), kwu(500 * 3 / 2))));
202+
// RBF minimum above our max (1.5x a fallen estimate): we can't RBF within our ceiling.
203+
assert_eq!(rbf_splice_feerates(kwu(100), kwu(278)), None);
204+
}
205+
}

src/lib.rs

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ pub use bitcoin;
119119
use bitcoin::secp256k1::PublicKey;
120120
#[cfg(feature = "uniffi")]
121121
pub use bitcoin::FeeRate;
122-
#[cfg(not(feature = "uniffi"))]
123-
use bitcoin::FeeRate;
124122
use bitcoin::{Address, Amount, BlockHash, Network};
125123
#[cfg(feature = "uniffi")]
126124
pub use builder::ArcedNodeBuilder as Builder;
@@ -138,7 +136,9 @@ pub use error::Error as NodeError;
138136
use error::Error;
139137
pub use event::Event;
140138
use event::{EventHandler, EventQueue};
141-
use fee_estimator::{ConfirmationTarget, FeeEstimator, OnchainFeeEstimator};
139+
use fee_estimator::{
140+
max_funding_feerate, rbf_splice_feerates, ConfirmationTarget, FeeEstimator, OnchainFeeEstimator,
141+
};
142142
#[cfg(feature = "uniffi")]
143143
use ffi::*;
144144
use gossip::GossipSource;
@@ -1584,7 +1584,7 @@ impl Node {
15841584
{
15851585
let min_feerate =
15861586
self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding);
1587-
let max_feerate = FeeRate::from_sat_per_kwu(min_feerate.to_sat_per_kwu() * 3 / 2);
1587+
let max_feerate = max_funding_feerate(min_feerate);
15881588

15891589
let splice_amount_sats = match splice_amount_sats {
15901590
FundingAmount::Exact { amount_sats } => amount_sats,
@@ -1773,7 +1773,7 @@ impl Node {
17731773

17741774
let min_feerate =
17751775
self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding);
1776-
let max_feerate = FeeRate::from_sat_per_kwu(min_feerate.to_sat_per_kwu() * 3 / 2);
1776+
let max_feerate = max_funding_feerate(min_feerate);
17771777

17781778
let funding_template = self
17791779
.channel_manager
@@ -2413,44 +2413,12 @@ pub(crate) fn new_channel_anchor_reserve_sats(
24132413
})
24142414
}
24152415

2416-
/// The most we are willing to pay for a channel funding transaction: `1.5x` our funding feerate
2417-
/// estimate. Used as the `max_feerate` ceiling for splices and their RBF fee bumps.
2418-
fn max_funding_feerate(estimate: FeeRate) -> FeeRate {
2419-
FeeRate::from_sat_per_kwu(estimate.to_sat_per_kwu() * 3 / 2)
2420-
}
2421-
2422-
/// Picks the `(target, max)` feerates for replacing a pending splice's in-flight funding
2423-
/// transaction via RBF, or `None` if the RBF can't be done within our fee ceiling.
2424-
///
2425-
/// `max` is the most we are willing to pay (see [`max_funding_feerate`]), which tracks our current
2426-
/// estimate and so may have risen or fallen since the original splice; it is never inflated to meet
2427-
/// the RBF minimum. `target` is what we actually pay — our current estimate, or the template's RBF
2428-
/// minimum if that is higher (required to replace the transaction). If that minimum exceeds `max`,
2429-
/// we can't RBF.
2430-
fn rbf_splice_feerates(estimate: FeeRate, min_rbf_feerate: FeeRate) -> Option<(FeeRate, FeeRate)> {
2431-
let max = max_funding_feerate(estimate);
2432-
let target = estimate.max(min_rbf_feerate);
2433-
(target <= max).then_some((target, max))
2434-
}
2435-
24362416
#[cfg(test)]
24372417
mod tests {
24382418
use lightning::util::ser::{Readable, Writeable};
24392419

24402420
use super::*;
24412421

2442-
#[test]
2443-
fn rbf_splice_feerates_target_and_max() {
2444-
let kwu = FeeRate::from_sat_per_kwu;
2445-
// Estimate below the RBF minimum but within our ceiling: pay the minimum to replace the
2446-
// transaction; the max stays 1.5x the estimate (never inflated) and already clears it.
2447-
assert_eq!(rbf_splice_feerates(kwu(253), kwu(278)), Some((kwu(278), kwu(253 * 3 / 2))));
2448-
// Estimate risen above the RBF minimum: pay the higher estimate, not the stale minimum.
2449-
assert_eq!(rbf_splice_feerates(kwu(500), kwu(278)), Some((kwu(500), kwu(500 * 3 / 2))));
2450-
// RBF minimum above our max (1.5x a fallen estimate): we can't RBF within our ceiling.
2451-
assert_eq!(rbf_splice_feerates(kwu(100), kwu(278)), None);
2452-
}
2453-
24542422
#[test]
24552423
fn node_metrics_reads_legacy_rgs_snapshot_timestamp() {
24562424
// Pre-#615, `NodeMetrics` persisted `latest_rgs_snapshot_timestamp` as an optional

0 commit comments

Comments
 (0)