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
26 changes: 13 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,16 @@ harness = false
#vss-client-ng = { path = "../vss-client" }
#vss-client-ng = { git = "https://github.com/lightningdevkit/vss-client", branch = "main" }
#
#[patch."https://github.com/lightningdevkit/rust-lightning"]
#lightning = { path = "../rust-lightning/lightning" }
#lightning-types = { path = "../rust-lightning/lightning-types" }
#lightning-invoice = { path = "../rust-lightning/lightning-invoice" }
#lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" }
#lightning-persister = { path = "../rust-lightning/lightning-persister" }
#lightning-background-processor = { path = "../rust-lightning/lightning-background-processor" }
#lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" }
#lightning-block-sync = { path = "../rust-lightning/lightning-block-sync" }
#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync" }
#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity" }
#lightning-macros = { path = "../rust-lightning/lightning-macros" }
#lightning-dns-resolver = { path = "../rust-lightning/lightning-dns-resolver" }
[patch."https://github.com/lightningdevkit/rust-lightning"]
lightning = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-types = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-invoice = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-net-tokio = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-persister = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-background-processor = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-rapid-gossip-sync = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-block-sync = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-transaction-sync = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-liquidity = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-macros = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
lightning-dns-resolver = { git = "https://git.rust-bitcoin.org/tnull/rust-lightning", rev = "54f4ae38f09bf936376796685bdcdd30abc112e2" }
25 changes: 22 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15;
/// | `announcement_addresses` | None |
/// | `node_alias` | None |
/// | `trusted_peers_0conf` | [] |
/// | `enable_v2_channel_close` | false |
/// | `probing_liquidity_limit_multiplier` | 3 |
/// | `anchor_channels_config` | AnchorChannelsConfig::default() |
/// | `route_parameters` | None |
Expand Down Expand Up @@ -182,6 +183,10 @@ pub struct Config {
/// funding transaction ends up never being confirmed on-chain. Zero-confirmation channels
/// should therefore only be accepted from trusted peers.
pub trusted_peers_0conf: Vec<PublicKey>,
/// Whether to enable the `option_simple_close` protocol for cooperative channel closures.
///
/// The protocol will only be used when supported by the channel counterparty.
pub enable_v2_channel_close: bool,
/// The liquidity factor by which we filter the outgoing channels used for sending probes.
///
/// Channels with available liquidity less than the required amount times this value won't be
Expand Down Expand Up @@ -222,6 +227,7 @@ impl Default for Config {
listening_addresses: None,
announcement_addresses: None,
trusted_peers_0conf: Vec::new(),
enable_v2_channel_close: false,
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
anchor_channels_config: AnchorChannelsConfig::default(),
tor_config: None,
Expand Down Expand Up @@ -428,6 +434,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.channel_handshake_config.negotiate_anchor_zero_fee_commitments =
config.anchor_channels_config.enable_zero_fee_commitments;
user_config.reject_inbound_splices = false;
user_config.enable_v2_channel_close = config.enable_v2_channel_close;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down Expand Up @@ -776,9 +783,9 @@ mod tests {
use std::str::FromStr;

use super::{
clamp_full_scan_stop_gap, may_announce_channel, AnnounceError, Config, ElectrumSyncConfig,
EsploraSyncConfig, NodeAlias, SocketAddress, DEFAULT_FULL_SCAN_STOP_GAP,
MAX_FULL_SCAN_STOP_GAP, MIN_FULL_SCAN_STOP_GAP,
clamp_full_scan_stop_gap, default_user_config, may_announce_channel, AnnounceError, Config,
ElectrumSyncConfig, EsploraSyncConfig, NodeAlias, SocketAddress,
DEFAULT_FULL_SCAN_STOP_GAP, MAX_FULL_SCAN_STOP_GAP, MIN_FULL_SCAN_STOP_GAP,
};

#[test]
Expand Down Expand Up @@ -844,4 +851,16 @@ mod tests {
assert_eq!(clamp_full_scan_stop_gap(0), MIN_FULL_SCAN_STOP_GAP);
assert_eq!(clamp_full_scan_stop_gap(MAX_FULL_SCAN_STOP_GAP + 1), MAX_FULL_SCAN_STOP_GAP);
}

#[test]
fn v2_channel_close_config() {
let default_config = Config::default();
assert!(!default_user_config(&default_config).enable_v2_channel_close);

let enabled_config = Config { enable_v2_channel_close: true, ..Config::default() };
assert!(
default_user_config(&enabled_config).enable_v2_channel_close,
"v2 channel close opt-in must be forwarded to LDK"
);
}
}
25 changes: 17 additions & 8 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -799,21 +799,30 @@ pub(crate) async fn wait_for_tx<E: ElectrumApi>(electrs: &E, txid: Txid) {
.await;
}

pub(crate) async fn wait_for_outpoint_spend<E: ElectrumApi>(electrs: &E, outpoint: OutPoint) {
pub(crate) async fn wait_for_outpoint_spend<E: ElectrumApi>(
electrs: &E, outpoint: OutPoint,
) -> Transaction {
let tx = electrs.transaction_get(&outpoint.txid).unwrap();
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;

// Script history already contains the funding transaction itself, so wait until the exact
// funding outpoint leaves the unspent set instead of treating any history as a spend.
// Script history already contains the funding transaction itself and may contain unrelated
// transactions for reused scripts, so only return a transaction spending the exact outpoint.
exponential_backoff_poll(|| {
electrs.ping().unwrap();

let is_spent = !electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});
is_spent.then_some(())
electrs.script_get_history(&txout_script).unwrap().into_iter().find_map(|entry| {
if entry.tx_hash == outpoint.txid {
return None;
}
let transaction = electrs.transaction_get(&entry.tx_hash).ok()?;
transaction
.input
.iter()
.any(|input| input.previous_output == outpoint)
.then_some(transaction)
})
})
.await;
.await
}

/// Polls the channel from `source_node` to `counterparty_node` until it reports `is_usable`
Expand Down
24 changes: 23 additions & 1 deletion tests/common/scenarios/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

use std::time::Duration;

use bitcoin::Sequence;
use electrsd::corepc_node::Client as BitcoindClient;
use electrsd::electrum_client::ElectrumApi;
use ldk_node::{Event, Node};

use super::super::external_node::ExternalNode;
use super::super::generate_blocks_and_wait;
use super::super::{generate_blocks_and_wait, wait_for_outpoint_spend};
use super::Side;

/// Open a channel from LDK to peer; returns (user_channel_id, external_channel_id).
Expand Down Expand Up @@ -50,6 +51,12 @@ pub(crate) async fn cooperative_close<E: ElectrumApi>(
user_channel_id: &ldk_node::UserChannelId, ext_channel_id: &str, initiator: Side,
) {
tokio::time::sleep(Duration::from_secs(2)).await;
let funding_txo = node
.list_channels()
.into_iter()
.find(|channel| channel.user_channel_id == *user_channel_id)
.and_then(|channel| channel.funding_txo)
.expect("channel funding outpoint must be available before cooperative close");
match initiator {
Side::Ldk => {
let ext_node_id = peer.get_node_id().await.unwrap();
Expand All @@ -59,6 +66,21 @@ pub(crate) async fn cooperative_close<E: ElectrumApi>(
peer.close_channel(ext_channel_id).await.unwrap();
},
}
let closing_tx = wait_for_outpoint_spend(electrs, funding_txo).await;
let funding_input = closing_tx
.input
.iter()
.find(|input| input.previous_output == funding_txo)
.expect("closing transaction must spend the channel funding outpoint");
let expected_sequence = if node.config().enable_v2_channel_close {
Sequence::ENABLE_RBF_NO_LOCKTIME
} else {
Sequence::MAX
};
assert_eq!(
funding_input.sequence, expected_sequence,
"cooperative close used an unexpected transaction format"
);
generate_blocks_and_wait(bitcoind, electrs, 1).await;
super::sync_wallets_with_retry(node).await;
expect_event!(node, ChannelClosed);
Expand Down
8 changes: 5 additions & 3 deletions tests/common/scenarios/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ pub(crate) async fn wait_for_htlcs_settled(

/// Build a fresh LDK node configured for interop tests. Uses electrum at the
/// docker-compose default port and bumps sync timeouts for combo stress.
pub(crate) fn setup_ldk_node() -> Node {
let config = crate::common::random_config();
pub(crate) fn setup_ldk_node(enable_v2_channel_close: bool) -> Node {
let mut config = crate::common::random_config();
config.node_config.enable_v2_channel_close = enable_v2_channel_close;
let mut builder = ldk_node::Builder::from_config(config.node_config);
let mut sync_config = ldk_node::config::ElectrumSyncConfig::default();
sync_config.timeouts_config.onchain_wallet_sync_timeout_secs = 180;
Expand Down Expand Up @@ -138,13 +139,14 @@ pub(crate) async fn setup_interop_test<E: ElectrumApi>(
/// per-impl `setup_clients` future and a scenario fn.
pub(crate) async fn run_interop_scenario<N, E, F>(
setup_fut: impl Future<Output = (BitcoindClient, E, N)>, scenario: F,
enable_v2_channel_close: bool,
) where
N: ExternalNode,
E: ElectrumApi,
F: AsyncFnOnce(&Node, &N, &BitcoindClient, &E),
{
let (bitcoind, electrs, ext) = setup_fut.await;
let node = setup_ldk_node();
let node = setup_ldk_node(enable_v2_channel_close);
setup_interop_test(&node, &ext, &bitcoind, &electrs).await;
scenario(&node, &ext, &bitcoind, &electrs).await;
node.stop().unwrap();
Expand Down
1 change: 1 addition & 0 deletions tests/docker/docker-compose-lnd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ services:
- "--bitcoind.zmqpubrawblock=tcp://bitcoin:28332"
- "--bitcoind.zmqpubrawtx=tcp://bitcoin:28333"
- "--accept-keysend"
- "--protocol.rbf-coop-close"
- "--rpclisten=0.0.0.0:8081"
- "--tlsextradomain=lnd"
- "--tlsextraip=0.0.0.0"
Expand Down
10 changes: 5 additions & 5 deletions tests/integration_tests_cln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,26 @@ async fn setup_clients() -> (BitcoindClient, ElectrumClient, TestClnNode) {

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_basic_channel_cycle() {
run_interop_scenario(setup_clients(), basic_channel_cycle_scenario).await;
run_interop_scenario(setup_clients(), basic_channel_cycle_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[ignore = "CLN <=v25.12.x keysend final_cltv=22 < LDK min 42; fixed in master (ElementsProject/lightning#9034), awaiting v26.04 Docker image"]
async fn test_keysend() {
run_interop_scenario(setup_clients(), keysend_scenario).await;
run_interop_scenario(setup_clients(), keysend_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_force_close_after_payment() {
run_interop_scenario(setup_clients(), force_close_after_payment_scenario).await;
run_interop_scenario(setup_clients(), force_close_after_payment_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_disconnect_during_payment() {
run_interop_scenario(setup_clients(), disconnect_during_payment_scenario).await;
run_interop_scenario(setup_clients(), disconnect_during_payment_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_splice_in() {
run_interop_scenario(setup_clients(), splice_in_scenario).await;
run_interop_scenario(setup_clients(), splice_in_scenario, false).await;
}
19 changes: 14 additions & 5 deletions tests/integration_tests_eclair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,37 @@ async fn setup_clients() -> (BitcoindClient, ElectrumClient, TestEclairNode) {
(bitcoind, electrs, eclair)
}

async fn do_test_basic_channel_cycle(v2_closing: bool) {
run_interop_scenario(setup_clients(), basic_channel_cycle_scenario, v2_closing).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_basic_channel_cycle() {
run_interop_scenario(setup_clients(), basic_channel_cycle_scenario).await;
do_test_basic_channel_cycle(false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_basic_channel_cycle_with_simple_close() {
do_test_basic_channel_cycle(true).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_keysend() {
run_interop_scenario(setup_clients(), keysend_scenario).await;
run_interop_scenario(setup_clients(), keysend_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_force_close_after_payment() {
run_interop_scenario(setup_clients(), force_close_after_payment_scenario).await;
run_interop_scenario(setup_clients(), force_close_after_payment_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_disconnect_during_payment() {
run_interop_scenario(setup_clients(), disconnect_during_payment_scenario).await;
run_interop_scenario(setup_clients(), disconnect_during_payment_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[ignore = "Eclair advertises splicing via custom bit 154 instead of BOLT bit 62/63; disjoint from LDK until Eclair migrates"]
async fn test_splice_in() {
run_interop_scenario(setup_clients(), splice_in_scenario).await;
run_interop_scenario(setup_clients(), splice_in_scenario, false).await;
}
19 changes: 14 additions & 5 deletions tests/integration_tests_lnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,37 @@ async fn setup_clients() -> (BitcoindClient, ElectrumClient, TestLndNode) {
(bitcoind, electrs, lnd)
}

async fn do_test_basic_channel_cycle(v2_closing: bool) {
run_interop_scenario(setup_clients(), basic_channel_cycle_scenario, v2_closing).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_basic_channel_cycle() {
run_interop_scenario(setup_clients(), basic_channel_cycle_scenario).await;
do_test_basic_channel_cycle(false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_basic_channel_cycle_with_simple_close() {
do_test_basic_channel_cycle(true).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_keysend() {
run_interop_scenario(setup_clients(), keysend_scenario).await;
run_interop_scenario(setup_clients(), keysend_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_force_close_after_payment() {
run_interop_scenario(setup_clients(), force_close_after_payment_scenario).await;
run_interop_scenario(setup_clients(), force_close_after_payment_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_disconnect_during_payment() {
run_interop_scenario(setup_clients(), disconnect_during_payment_scenario).await;
run_interop_scenario(setup_clients(), disconnect_during_payment_scenario, false).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[ignore = "LND does not implement BOLT splicing"]
async fn test_splice_in() {
run_interop_scenario(setup_clients(), splice_in_scenario).await;
run_interop_scenario(setup_clients(), splice_in_scenario, false).await;
}
Loading