Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 161
Add splice RBF support#888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
93797a279fd087c8bc878c34473d9f98db5e541265513553454eb085f9a64a05d21bd7File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -119,8 +119,6 @@ pub use bitcoin; | ||
| use bitcoin::secp256k1::PublicKey; | ||
| #[cfg(feature = "uniffi")] | ||
| pub use bitcoin::FeeRate; | ||
| #[cfg(not(feature = "uniffi"))] | ||
| use bitcoin::FeeRate; | ||
| use bitcoin::{Address, Amount, BlockHash, Network}; | ||
| #[cfg(feature = "uniffi")] | ||
| pub use builder::ArcedNodeBuilder as Builder; | ||
| @@ -138,7 +136,9 @@ pub use error::Error as NodeError; | ||
| use error::Error; | ||
| pub use event::Event; | ||
| use event::{EventHandler, EventQueue}; | ||
| use fee_estimator::{ConfirmationTarget, FeeEstimator, OnchainFeeEstimator}; | ||
| use fee_estimator::{ | ||
| max_funding_feerate, rbf_splice_feerates, ConfirmationTarget, FeeEstimator, OnchainFeeEstimator, | ||
| }; | ||
| #[cfg(feature = "uniffi")] | ||
| use ffi::*; | ||
| use gossip::GossipSource; | ||
| @@ -1584,7 +1584,7 @@ impl Node { | ||
| { | ||
| let min_feerate = | ||
| self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding); | ||
| let max_feerate = FeeRate::from_sat_per_kwu(min_feerate.to_sat_per_kwu() * 3 / 2); | ||
| let max_feerate = max_funding_feerate(min_feerate); | ||
| let splice_amount_sats = match splice_amount_sats { | ||
| FundingAmount::Exact { amount_sats } => amount_sats, | ||
| @@ -1653,16 +1653,26 @@ impl Node { | ||
| if funding_template.prior_contribution().is_some() { | ||
| log_error!( | ||
| self.logger, | ||
| "Failed to splice channel: a prior splice contribution is pending" | ||
| "Failed to splice channel: a prior splice contribution is pending; use bump_channel_funding_fee to bump its fee" | ||
| ); | ||
| return Err(Error::ChannelSplicingFailed); | ||
| } | ||
| // When contributing to a pending splice, the funding template requires at least the RBF | ||
| // minimum feerate to replace the in-flight transaction. Use it in place of our funding | ||
| // feerate estimate when it's higher, as long as it stays within our max. | ||
| let feerate = match funding_template.min_rbf_feerate() { | ||
| Some(min_rbf_feerate) if min_rbf_feerate <= max_feerate => { | ||
| min_feerate.max(min_rbf_feerate) | ||
| }, | ||
| _ => min_feerate, | ||
| }; | ||
| let contribution = self | ||
| .runtime | ||
| .block_on(funding_template.splice_in( | ||
| Amount::from_sat(splice_amount_sats), | ||
| min_feerate, | ||
| feerate, | ||
| max_feerate, | ||
| Arc::clone(&self.wallet), | ||
| )) | ||
| @@ -1763,7 +1773,7 @@ impl Node { | ||
| let min_feerate = | ||
| self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding); | ||
| let max_feerate = FeeRate::from_sat_per_kwu(min_feerate.to_sat_per_kwu() * 3 / 2); | ||
| let max_feerate = max_funding_feerate(min_feerate); | ||
| let funding_template = self | ||
| .channel_manager | ||
| @@ -1776,17 +1786,27 @@ impl Node { | ||
| if funding_template.prior_contribution().is_some() { | ||
| log_error!( | ||
| self.logger, | ||
| "Failed to splice channel: a prior splice contribution is pending" | ||
| "Failed to splice channel: a prior splice contribution is pending; use bump_channel_funding_fee to bump its fee" | ||
| ); | ||
| return Err(Error::ChannelSplicingFailed); | ||
jkczyz marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| // When contributing to a pending splice, the funding template requires at least the RBF | ||
| // minimum feerate to replace the in-flight transaction. Use it in place of our funding | ||
| // feerate estimate when it's higher, as long as it stays within our max. | ||
| let feerate = match funding_template.min_rbf_feerate() { | ||
| Some(min_rbf_feerate) if min_rbf_feerate <= max_feerate => { | ||
| min_feerate.max(min_rbf_feerate) | ||
| }, | ||
| _ => min_feerate, | ||
| }; | ||
| let outputs = vec![bitcoin::TxOut { | ||
| value: Amount::from_sat(splice_amount_sats), | ||
| script_pubkey: address.script_pubkey(), | ||
| }]; | ||
| let contribution = | ||
| funding_template.splice_out(outputs, min_feerate, max_feerate).map_err(|e| { | ||
| funding_template.splice_out(outputs, feerate, max_feerate).map_err(|e| { | ||
| log_error!(self.logger, "Failed to splice channel: {}", e); | ||
| Error::ChannelSplicingFailed | ||
| })?; | ||
| @@ -1813,6 +1833,77 @@ impl Node { | ||
| } | ||
| } | ||
| /// Fee-bumps the pending splice on a channel by replacing its in-flight funding transaction | ||
| /// (RBF). The splice's amount and destination are preserved; only the fee rate is raised. | ||
| /// Errors if the channel has no pending splice to bump. | ||
| pub fn bump_channel_funding_fee( | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the plan for ldk-node to eventually monitor pending splice candidates and automatically bump them? It seems a bit awkward to make users/applications notice that a pending splice is stuck and manually call this API.
| ||
| &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, | ||
| ) -> Result<(), Error> { | ||
| let open_channels = | ||
| self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); | ||
| if let Some(channel_details) = | ||
| open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0) | ||
| { | ||
| let min_feerate = | ||
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codex:
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good catch. Done. | ||
| self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding); | ||
| let funding_template = self | ||
| .channel_manager | ||
| .splice_channel(&channel_details.channel_id, &counterparty_node_id) | ||
| .map_err(|e| { | ||
| log_error!(self.logger, "Failed to RBF channel: {:?}", e); | ||
| Error::ChannelSplicingFailed | ||
| })?; | ||
| let Some(min_rbf_feerate) = funding_template.min_rbf_feerate() else { | ||
| log_error!(self.logger, "Failed to RBF channel: no pending splice to replace"); | ||
| return Err(Error::ChannelSplicingFailed); | ||
| }; | ||
| let Some((target_feerate, max_feerate)) = | ||
| rbf_splice_feerates(min_feerate, min_rbf_feerate) | ||
| else { | ||
| log_error!( | ||
| self.logger, | ||
| "Failed to RBF channel: the RBF minimum feerate exceeds our maximum" | ||
| ); | ||
| return Err(Error::ChannelSplicingFailed); | ||
| }; | ||
| let contribution = self | ||
| .runtime | ||
| .block_on(funding_template.rbf_prior_contribution( | ||
| Some(target_feerate), | ||
| max_feerate, | ||
| Arc::clone(&self.wallet), | ||
| )) | ||
| .map_err(|e| { | ||
| log_error!(self.logger, "Failed to RBF channel: {}", e); | ||
| Error::ChannelSplicingFailed | ||
| })?; | ||
| self.channel_manager | ||
| .funding_contributed( | ||
| &channel_details.channel_id, | ||
| &counterparty_node_id, | ||
| contribution, | ||
| None, | ||
| ) | ||
| .map_err(|e| { | ||
| log_error!(self.logger, "Failed to RBF channel: {:?}", e); | ||
| Error::ChannelSplicingFailed | ||
| }) | ||
| } else { | ||
| log_error!( | ||
| self.logger, | ||
| "Channel not found for user_channel_id {} and counterparty {}", | ||
| user_channel_id, | ||
| counterparty_node_id | ||
| ); | ||
| Err(Error::ChannelSplicingFailed) | ||
| } | ||
| } | ||
| /// Manually sync the LDK and BDK wallets with the current chain state and update the fee rate | ||
| /// cache. | ||
| /// | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It says use
rbf_channelinstead but that function doesn't let us change the amount in/out. Some likeuse rbf_channel to bump feewould be more accurate. Also would be nice if this had a separate error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the message, but I'm not sure it justifies a new error type. Callers will be able to check
SpliceDetailsonce lightningdevkit/rust-lightning#4687 is included. @tnull Any preference?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should that be added to the 0.3 milestone?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No preference, fine to leave it as-is.