Skip to content

Commit 3505dfb

Browse files
jkczyzbenthecarman
authored andcommitted
Add Node::splice_out method
Instead of closing and re-opening a channel when on-chain funds are needed, splicing allows removing funds (splice-out) while keeping the channel operational. This commit implements splice-out sending funds to a user-provided on-chain address.
1 parent 58e883b commit 3505dfb

File tree

2 files changed

+58
-7
lines changed

2 files changed

+58
-7
lines changed

src/lib.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
110110
use crate::scoring::setup_background_pathfinding_scores_sync;
111111
pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance};
112112
use bitcoin::secp256k1::PublicKey;
113-
use bitcoin::Amount;
113+
use bitcoin::{Address, Amount};
114114
#[cfg(feature = "uniffi")]
115115
pub use builder::ArcedNodeBuilder as Builder;
116116
pub use builder::BuildError;
@@ -1323,6 +1323,59 @@ impl Node {
13231323
}
13241324
}
13251325

1326+
/// Remove funds from an existing channel, sending them to an on-chain address.
1327+
///
1328+
/// This provides for decreasing a channel's outbound liquidity without re-balancing or closing
1329+
/// it. Once negotiation with the counterparty is complete, the channel remains operational
1330+
/// while waiting for a new funding transaction to confirm.
1331+
pub fn splice_out(
1332+
&self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, address: Address,
1333+
splice_amount_sats: u64,
1334+
) -> Result<(), Error> {
1335+
let open_channels =
1336+
self.channel_manager.list_channels_with_counterparty(&counterparty_node_id);
1337+
if let Some(channel_details) =
1338+
open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0)
1339+
{
1340+
if splice_amount_sats > channel_details.outbound_capacity_msat {
1341+
return Err(Error::ChannelSplicingFailed);
1342+
}
1343+
1344+
self.wallet.parse_and_validate_address(&address)?;
1345+
1346+
let contribution = SpliceContribution::SpliceOut {
1347+
outputs: vec![bitcoin::TxOut {
1348+
value: Amount::from_sat(splice_amount_sats),
1349+
script_pubkey: address.script_pubkey(),
1350+
}],
1351+
};
1352+
1353+
let fee_rate = self.wallet.estimate_channel_funding_fee_rate();
1354+
let funding_feerate_per_kw = fee_rate.to_sat_per_kwu().try_into().unwrap_or(u32::MAX);
1355+
1356+
self.channel_manager
1357+
.splice_channel(
1358+
&channel_details.channel_id,
1359+
&counterparty_node_id,
1360+
contribution,
1361+
funding_feerate_per_kw,
1362+
None,
1363+
)
1364+
.map_err(|e| {
1365+
log_error!(self.logger, "Failed to splice channel: {:?}", e);
1366+
Error::ChannelSplicingFailed
1367+
})
1368+
} else {
1369+
log_error!(
1370+
self.logger,
1371+
"Channel not found for user_channel_id: {:?} and counterparty: {}",
1372+
user_channel_id,
1373+
counterparty_node_id
1374+
);
1375+
Err(Error::ChannelSplicingFailed)
1376+
}
1377+
}
1378+
13261379
/// Manually sync the LDK and BDK wallets with the current chain state and update the fee rate
13271380
/// cache.
13281381
///

src/wallet/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret;
2525
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
2626
use bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey};
2727
use bitcoin::{
28-
Address, Amount, FeeRate, Network, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, Weight,
28+
Address, Amount, FeeRate, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, Weight,
2929
WitnessProgram, WitnessVersion,
3030
};
3131
use lightning::chain::chaininterface::BroadcasterInterface;
@@ -334,12 +334,10 @@ impl Wallet {
334334
self.get_balances(total_anchor_channels_reserve_sats).map(|(_, s)| s)
335335
}
336336

337-
fn parse_and_validate_address(
338-
&self, network: Network, address: &Address,
339-
) -> Result<Address, Error> {
337+
pub(crate) fn parse_and_validate_address(&self, address: &Address) -> Result<Address, Error> {
340338
Address::<NetworkUnchecked>::from_str(address.to_string().as_str())
341339
.map_err(|_| Error::InvalidAddress)?
342-
.require_network(network)
340+
.require_network(self.config.network)
343341
.map_err(|_| Error::InvalidAddress)
344342
}
345343

@@ -348,7 +346,7 @@ impl Wallet {
348346
&self, address: &bitcoin::Address, send_amount: OnchainSendAmount,
349347
fee_rate: Option<FeeRate>,
350348
) -> Result<Txid, Error> {
351-
self.parse_and_validate_address(self.config.network, &address)?;
349+
self.parse_and_validate_address(&address)?;
352350

353351
// Use the set fee_rate or default to fee estimation.
354352
let confirmation_target = ConfirmationTarget::OnchainPayment;

0 commit comments

Comments
 (0)