@@ -56,7 +56,7 @@ use crate::ln::channelmanager::{
5656 MAX_LOCAL_BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA,
5757};
5858use crate::ln::funding::{
59- FeeRateAdjustmentError, FundingContribution, FundingTemplate, FundingTxInput,
59+ FeeRateAdjustmentError, FundingContribution, FundingTemplate, FundingTxInput, PriorContribution,
6060};
6161use crate::ln::interactivetxs::{
6262 AbortReason, HandleTxCompleteValue, InteractiveTxConstructor, InteractiveTxConstructorArgs,
@@ -11907,7 +11907,7 @@ where
1190711907 }
1190811908 }
1190911909
11910- /// Initiate splicing.
11910+ /// Builds a [`FundingTemplate`] for splicing or RBF, if the channel state allows it .
1191111911 pub fn splice_channel(&self) -> Result<FundingTemplate, APIError> {
1191211912 if self.holder_commitment_point.current_point().is_none() {
1191311913 return Err(APIError::APIMisuseError {
@@ -11950,19 +11950,35 @@ where
1195011950 });
1195111951 }
1195211952
11953- // Compute the RBF feerate floor from either negotiated candidates (via
11954- // can_initiate_rbf) or an in-progress funding negotiation (which will become a
11955- // negotiated candidate once it completes).
11956- let min_rbf_feerate = self.can_initiate_rbf().ok().flatten().or_else(|| {
11957- self.pending_splice
11958- .as_ref()
11959- .and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
11960- .map(|negotiation| {
11961- let prev_feerate = negotiation.funding_feerate_sat_per_1000_weight();
11962- let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
11963- FeeRate::from_sat_per_kwu(min_feerate_kwu)
11964- })
11965- });
11953+ let (min_rbf_feerate, prior_contribution) = if self.is_rbf_compatible().is_err() {
11954+ // Channel can never RBF (e.g., zero-conf).
11955+ (None, None)
11956+ } else if let Ok(min_rbf_feerate) = self.can_initiate_rbf() {
11957+ // A previous splice was negotiated but not yet locked. The user's splice
11958+ // will be an RBF, so provide the minimum RBF feerate and prior contribution.
11959+ let prior = self.build_prior_contribution();
11960+ (Some(min_rbf_feerate), prior)
11961+ } else if let Some(negotiation) = self
11962+ .pending_splice
11963+ .as_ref()
11964+ .and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
11965+ {
11966+ // A splice is currently being negotiated.
11967+ // - If the negotiation succeeds, the user's splice will need to satisfy the RBF
11968+ // feerate requirement. Derive the minimum RBF feerate from the negotiation's
11969+ // feerate so the user can choose an appropriate feerate.
11970+ // - If the negotiation fails (e.g., tx_abort), the splice will proceed as a fresh
11971+ // splice instead. In this case, the min_rbf_feerate becomes stale, causing a
11972+ // slightly higher feerate than necessary. Call splice_channel again after
11973+ // receiving SpliceFailed to get a fresh template without the RBF constraint.
11974+ let prev_feerate = negotiation.funding_feerate_sat_per_1000_weight();
11975+ let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
11976+ (Some(FeeRate::from_sat_per_kwu(min_feerate_kwu)), None)
11977+ } else {
11978+ // No RBF feerate to derive — either a fresh splice or a pending splice that
11979+ // can't be RBF'd (e.g., splice_locked already exchanged).
11980+ (None, None)
11981+ };
1196611982
1196711983 let funding_txo = self.funding.get_funding_txo().expect("funding_txo should be set");
1196811984 let previous_utxo =
@@ -11973,63 +11989,35 @@ where
1197311989 satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT,
1197411990 };
1197511991
11976- Ok(FundingTemplate::new(Some(shared_input), min_rbf_feerate))
11992+ Ok(FundingTemplate::new(Some(shared_input), min_rbf_feerate, prior_contribution ))
1197711993 }
1197811994
11979- /// Initiate an RBF of a pending splice transaction.
11980- pub fn rbf_channel(&self) -> Result<FundingTemplate, APIError> {
11981- if self.holder_commitment_point.current_point().is_none() {
11982- return Err(APIError::APIMisuseError {
11983- err: format!(
11984- "Channel {} cannot RBF until a payment is routed",
11985- self.context.channel_id(),
11986- ),
11987- });
11988- }
11989-
11990- if self.quiescent_action.is_some() {
11991- return Err(APIError::APIMisuseError {
11992- err: format!(
11993- "Channel {} cannot RBF as one is waiting to be negotiated",
11994- self.context.channel_id(),
11995- ),
11996- });
11997- }
11998-
11999- if !self.context.is_usable() {
12000- return Err(APIError::APIMisuseError {
12001- err: format!(
12002- "Channel {} cannot RBF as it is either pending open/close",
12003- self.context.channel_id()
12004- ),
12005- });
12006- }
11995+ /// Clones the prior contribution and fetches the holder balance for deferred feerate
11996+ /// adjustment.
11997+ fn build_prior_contribution(&self) -> Option<PriorContribution> {
11998+ debug_assert!(self.pending_splice.is_some(), "can_initiate_rbf requires pending_splice");
11999+ let prior = self.pending_splice.as_ref()?.contributions.last()?;
12000+ let holder_balance = self
12001+ .get_holder_counterparty_balances_floor_incl_fee(&self.funding)
12002+ .map(|(h, _)| h)
12003+ .ok();
12004+ Some(PriorContribution::new(prior.clone(), holder_balance))
12005+ }
1200712006
12007+ /// Returns whether this channel can ever RBF, independent of splice state.
12008+ fn is_rbf_compatible(&self) -> Result<(), String> {
1200812009 if self.context.minimum_depth(&self.funding) == Some(0) {
12009- return Err(APIError::APIMisuseError {
12010- err: format!(
12011- "Channel {} has option_zeroconf, cannot RBF splice",
12012- self.context.channel_id(),
12013- ),
12014- });
12010+ return Err(format!(
12011+ "Channel {} has option_zeroconf, cannot RBF",
12012+ self.context.channel_id(),
12013+ ));
1201512014 }
12016-
12017- let min_rbf_feerate =
12018- self.can_initiate_rbf().map_err(|err| APIError::APIMisuseError { err })?;
12019-
12020- let funding_txo = self.funding.get_funding_txo().expect("funding_txo should be set");
12021- let previous_utxo =
12022- self.funding.get_funding_output().expect("funding_output should be set");
12023- let shared_input = Input {
12024- outpoint: funding_txo.into_bitcoin_outpoint(),
12025- previous_utxo,
12026- satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT,
12027- };
12028-
12029- Ok(FundingTemplate::new(Some(shared_input), min_rbf_feerate))
12015+ Ok(())
1203012016 }
1203112017
12032- fn can_initiate_rbf(&self) -> Result<Option<FeeRate>, String> {
12018+ fn can_initiate_rbf(&self) -> Result<FeeRate, String> {
12019+ self.is_rbf_compatible()?;
12020+
1203312021 let pending_splice = match &self.pending_splice {
1203412022 Some(pending_splice) => pending_splice,
1203512023 None => {
@@ -12068,13 +12056,16 @@ where
1206812056 ));
1206912057 }
1207012058
12071- let min_rbf_feerate =
12072- pending_splice.last_funding_feerate_sat_per_1000_weight.map(| prev_feerate| {
12059+ match pending_splice.last_funding_feerate_sat_per_1000_weight {
12060+ Some( prev_feerate) => {
1207312061 let min_feerate_kwu = ((prev_feerate as u64) * 25).div_ceil(24);
12074- FeeRate::from_sat_per_kwu(min_feerate_kwu)
12075- });
12076-
12077- Ok(min_rbf_feerate)
12062+ Ok(FeeRate::from_sat_per_kwu(min_feerate_kwu))
12063+ },
12064+ None => Err(format!(
12065+ "Channel {} has no prior feerate to compute RBF minimum",
12066+ self.context.channel_id(),
12067+ )),
12068+ }
1207812069 }
1207912070
1208012071 /// Attempts to adjust the contribution's feerate to the minimum RBF feerate so the splice can
@@ -12205,7 +12196,7 @@ where
1220512196 // If a pending splice exists with negotiated candidates, attempt to adjust the
1220612197 // contribution's feerate to the minimum RBF feerate so it can proceed as an RBF immediately
1220712198 // rather than waiting for the splice to lock.
12208- let contribution = if let Ok(Some( min_rbf_feerate) ) = self.can_initiate_rbf() {
12199+ let contribution = if let Ok(min_rbf_feerate) = self.can_initiate_rbf() {
1220912200 self.maybe_adjust_for_rbf(contribution, min_rbf_feerate, logger)
1221012201 } else {
1221112202 contribution
@@ -12605,12 +12596,7 @@ where
1260512596 return Err(ChannelError::WarnAndDisconnect("Quiescence needed for RBF".to_owned()));
1260612597 }
1260712598
12608- if self.context.minimum_depth(&self.funding) == Some(0) {
12609- return Err(ChannelError::WarnAndDisconnect(format!(
12610- "Channel {} has option_zeroconf, cannot RBF splice",
12611- self.context.channel_id(),
12612- )));
12613- }
12599+ self.is_rbf_compatible().map_err(|msg| ChannelError::WarnAndDisconnect(msg))?;
1261412600
1261512601 let pending_splice = match &self.pending_splice {
1261612602 Some(pending_splice) => pending_splice,
@@ -13817,7 +13803,7 @@ where
1381713803 );
1381813804 return None;
1381913805 },
13820- Ok(Some( min_rbf_feerate) ) if contribution.feerate() < min_rbf_feerate => {
13806+ Ok(min_rbf_feerate) if contribution.feerate() < min_rbf_feerate => {
1382113807 log_given_level!(
1382213808 logger,
1382313809 logger_level,
0 commit comments