Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ default = []
#lightning-macros = { version = "0.2.0" }
#lightning-dns-resolver = { version = "0.3.0" }

lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["std"] }
lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" }
lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["std"] }
lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" }
lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["tokio"] }
lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" }
lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" }
lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["rest-client", "rpc-client", "tokio"] }
lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["esplora-async-https", "time", "electrum-rustls-ring"] }
lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["std"] }
lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" }
lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" }
lightning = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["std"] }
lightning-types = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3" }
lightning-invoice = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["std"] }
lightning-net-tokio = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3" }
lightning-persister = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["tokio"] }
lightning-background-processor = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3" }
lightning-rapid-gossip-sync = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3" }
lightning-block-sync = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["rest-client", "rpc-client", "tokio"] }
lightning-transaction-sync = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["esplora-async-https", "time", "electrum-rustls-ring"] }
lightning-liquidity = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["std"] }
lightning-macros = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3" }
lightning-dns-resolver = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3" }

bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] }
bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]}
Expand Down Expand Up @@ -81,13 +81,13 @@ async-trait = { version = "0.1", default-features = false }
vss-client = { package = "vss-client-ng", version = "0.5" }
prost = { version = "0.11.6", default-features = false}
#bitcoin-payment-instructions = { version = "0.6" }
bitcoin-payment-instructions = { git = "https://github.com/jkczyz/bitcoin-payment-instructions", rev = "679dac50cc0d81ec4d31da94b93d467e5308f16a" }
bitcoin-payment-instructions = { git = "https://github.com/jkczyz/bitcoin-payment-instructions", rev = "d0b3708c7f6f49a15dfc4b2cfbf7dceda8ab010b" }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winbase"] }

[dev-dependencies]
lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144", features = ["std", "_test_utils"] }
lightning = { git = "https://github.com/jkczyz/rust-lightning", rev = "4b827703a81f46dc45c99d9aedd7a0b59cc440f3", features = ["std", "_test_utils"] }
rand = { version = "0.9.2", default-features = false, features = ["std", "thread_rng", "os_rng"] }
proptest = "1.0.0"
regex = "1.5.6"
Expand Down
2 changes: 2 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ interface Node {
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void rbf_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand Down
2 changes: 2 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,8 @@ fn build_with_store_internal(
Arc::clone(&pending_payment_store),
));

tx_broadcaster.set_wallet(Arc::downgrade(&wallet));

// Initialize the KeysManager
let cur_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map_err(|e| {
log_error!(logger, "Failed to get current time: {}", e);
Expand Down
8 changes: 5 additions & 3 deletions src/chain/bitcoind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,16 +568,18 @@ impl BitcoindChainSource {
Ok(())
}

pub(crate) async fn process_broadcast_package(&self, package: Vec<Transaction>) {
pub(crate) async fn process_broadcast_package(
&self, txs: impl IntoIterator<Item = Transaction>,
) {
// While it's a bit unclear when we'd be able to lean on Bitcoin Core >v28
// features, we should eventually switch to use `submitpackage` via the
// `rust-bitcoind-json-rpc` crate rather than just broadcasting individual
// transactions.
for tx in &package {
for tx in txs {
let txid = tx.compute_txid();
let timeout_fut = tokio::time::timeout(
Duration::from_secs(DEFAULT_TX_BROADCAST_TIMEOUT_SECS),
self.api_client.broadcast_transaction(tx),
self.api_client.broadcast_transaction(&tx),
);
match timeout_fut.await {
Ok(res) => match res {
Expand Down
6 changes: 4 additions & 2 deletions src/chain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,9 @@ impl ElectrumChainSource {
Ok(())
}

pub(crate) async fn process_broadcast_package(&self, package: Vec<Transaction>) {
pub(crate) async fn process_broadcast_package(
&self, txs: impl IntoIterator<Item = Transaction>,
) {
let electrum_client: Arc<ElectrumRuntimeClient> = if let Some(client) =
self.electrum_runtime_status.read().expect("lock").client().as_ref()
{
Expand All @@ -285,7 +287,7 @@ impl ElectrumChainSource {
return;
};

for tx in package {
for tx in txs {
electrum_client.broadcast(tx).await;
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/chain/esplora.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,14 @@ impl EsploraChainSource {
Ok(())
}

pub(crate) async fn process_broadcast_package(&self, package: Vec<Transaction>) {
for tx in &package {
pub(crate) async fn process_broadcast_package(
&self, txs: impl IntoIterator<Item = Transaction>,
) {
for tx in txs {
let txid = tx.compute_txid();
let timeout_fut = tokio::time::timeout(
Duration::from_secs(self.sync_config.timeouts_config.tx_broadcast_timeout_secs),
self.esplora_client.broadcast(tx),
self.esplora_client.broadcast(&tx),
);
match timeout_fut.await {
Ok(res) => match res {
Expand Down
20 changes: 16 additions & 4 deletions src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::config::{
WALLET_SYNC_INTERVAL_MINIMUM_SECS,
};
use crate::fee_estimator::OnchainFeeEstimator;
use crate::logger::{log_debug, log_info, log_trace, LdkLogger, Logger};
use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger};
use crate::runtime::Runtime;
use crate::types::{Broadcaster, ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet};
use crate::{Error, NodeMetrics};
Expand Down Expand Up @@ -453,15 +453,27 @@ impl ChainSource {
return;
}
Some(next_package) = receiver.recv() => {
let package = match self.tx_broadcaster.classify_package(next_package).await {
Ok(p) => p,
Err(e) => {
log_error!(
tx_bcast_logger,
"Skipping broadcast: failed to persist payment records: {:?}",
e,
);
continue;
},
};
let txs = package.into_iter().map(|(tx, _)| tx);
match &self.kind {
ChainSourceKind::Esplora(esplora_chain_source) => {
esplora_chain_source.process_broadcast_package(next_package).await
esplora_chain_source.process_broadcast_package(txs).await
},
ChainSourceKind::Electrum(electrum_chain_source) => {
electrum_chain_source.process_broadcast_package(next_package).await
electrum_chain_source.process_broadcast_package(txs).await
},
ChainSourceKind::Bitcoind(bitcoind_chain_source) => {
bitcoind_chain_source.process_broadcast_package(next_package).await
bitcoind_chain_source.process_broadcast_package(txs).await
},
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,18 @@ where
);
}

if let Err(e) =
self.wallet.handle_channel_ready(channel_id, funding_txo.map(|txo| txo.txid))
{
log_error!(
self.logger,
"Failed to graduate funding payment on ChannelReady for channel {}: {:?}",
channel_id,
e,
);
return Err(ReplayEvent());
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
.handle_channel_ready(user_channel_id, &channel_id, &counterparty_node_id)
Expand Down Expand Up @@ -1558,6 +1570,16 @@ where
} => {
log_info!(self.logger, "Channel {} closed due to: {}", channel_id, reason);

if let Err(e) = self.wallet.handle_channel_closed(channel_id) {
log_error!(
self.logger,
"Failed to handle ChannelClosed for channel {}: {:?}",
channel_id,
e,
);
return Err(ReplayEvent());
}

let event = Event::ChannelClosed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
Expand Down
86 changes: 81 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,14 @@ impl Node {
Error::ChannelSplicingFailed
})?;

if funding_template.min_rbf_feerate().is_some() {
log_error!(
self.logger,
"Failed to splice channel: pending splice requires RBF, use rbf_channel instead"
);
return Err(Error::ChannelSplicingFailed);
}

let contribution = self
.runtime
.block_on(funding_template.splice_in(
Expand Down Expand Up @@ -1694,20 +1702,88 @@ impl Node {
Error::ChannelSplicingFailed
})?;

if funding_template.min_rbf_feerate().is_some() {
log_error!(
self.logger,
"Failed to splice channel: pending splice requires RBF, use rbf_channel instead"
);
return Err(Error::ChannelSplicingFailed);
}

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| {
log_error!(self.logger, "Failed to splice 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 splice 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)
}
}

/// Replace a pending splice's funding transaction with a higher-feerate version.
///
/// If a prior splice negotiation is pending, this bumps its feerate via RBF. The prior
/// contribution is reused when possible; otherwise, coin selection is re-run.
///
/// # Experimental API
///
/// This API is experimental and may change in the future.
pub fn rbf_channel(
&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 =
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 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
})?;

if funding_template.min_rbf_feerate().is_none() {
log_error!(self.logger, "Failed to RBF channel: no pending splice to replace");
return Err(Error::ChannelSplicingFailed);
}

let contribution = self
.runtime
.block_on(funding_template.splice_out(
outputs,
min_feerate,
.block_on(funding_template.rbf_prior_contribution(
None,
max_feerate,
Arc::clone(&self.wallet),
))
.map_err(|e| {
log_error!(self.logger, "Failed to splice channel: {}", e);
log_error!(self.logger, "Failed to RBF channel: {}", e);
Error::ChannelSplicingFailed
})?;

Expand All @@ -1719,7 +1795,7 @@ impl Node {
None,
)
.map_err(|e| {
log_error!(self.logger, "Failed to splice channel: {:?}", e);
log_error!(self.logger, "Failed to RBF channel: {:?}", e);
Error::ChannelSplicingFailed
})
} else {
Expand Down
Loading
Loading