diff --git a/src/lib.rs b/src/lib.rs index 187600610..5a8599131 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1449,8 +1449,8 @@ impl Node { /// Connect to a node and open a new unannounced channel, in which the target node can /// spend its entire balance. /// - /// This channel allows the target node to try to steal your funds with no financial - /// penalty, so this channel should only be opened to nodes you trust. + /// This channel allows the target node to try to steal your funds in that channel with no + /// financial penalty, so this channel should only be opened to nodes you trust. /// /// Disconnects and reconnects are handled automatically. /// @@ -1484,8 +1484,8 @@ impl Node { /// minus fees and anchor reserves. The target node will be able to spend its entire channel /// balance. /// - /// This channel allows the target node to try to steal your funds with no financial - /// penalty, so this channel should only be opened to nodes you trust. + /// This channel allows the target node to try to steal your funds in that channel with no + /// financial penalty, so this channel should only be opened to nodes you trust. /// /// Disconnects and reconnects are handled automatically. /// diff --git a/src/liquidity.rs b/src/liquidity.rs index 5dbd35b62..78dec3627 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -142,9 +142,9 @@ pub struct LSPS2ServiceConfig { /// /// [`bLIP-52`]: https://github.com/lightning/blips/blob/master/blip-0052.md#trust-models pub client_trusts_lsp: bool, - /// When set, clients that we open channels to will be allowed to spend their entire channel - /// balance. This allows clients to try to steal your funds with no financial penalty, so - /// this should only be set if you trust your clients. + /// When set, we will allow clients to spend their entire channel balance in the channels + /// we open to them. This allows clients to try to steal your funds in those channels with + /// no financial penalty, so this should only be set if you trust your clients. pub allow_client_0reserve: bool, } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a44aee174..d0e55956c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -790,7 +790,7 @@ pub async fn splice_in_with_all( pub(crate) async fn do_channel_full_cycle( node_a: TestNode, node_b: TestNode, bitcoind: &BitcoindClient, electrsd: &E, allow_0conf: bool, - expect_anchor_channel: bool, force_close: bool, + allow_0reserve: bool, expect_anchor_channel: bool, force_close: bool, ) { let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -846,15 +846,27 @@ pub(crate) async fn do_channel_full_cycle( println!("\nA -- open_channel -> B"); let funding_amount_sat = 2_080_000; let push_msat = (funding_amount_sat / 2) * 1000; // balance the channel - node_a - .open_announced_channel( - node_b.node_id(), - node_b.listening_addresses().unwrap().first().unwrap().clone(), - funding_amount_sat, - Some(push_msat), - None, - ) - .unwrap(); + if allow_0reserve { + node_a + .open_0reserve_channel( + node_b.node_id(), + node_b.listening_addresses().unwrap().first().unwrap().clone(), + funding_amount_sat, + Some(push_msat), + None, + ) + .unwrap(); + } else { + node_a + .open_announced_channel( + node_b.node_id(), + node_b.listening_addresses().unwrap().first().unwrap().clone(), + funding_amount_sat, + Some(push_msat), + None, + ) + .unwrap(); + } assert_eq!(node_a.list_peers().first().unwrap().node_id, node_b.node_id()); assert!(node_a.list_peers().first().unwrap().is_persisted); @@ -913,6 +925,22 @@ pub(crate) async fn do_channel_full_cycle( node_b_anchor_reserve_sat ); + // Note that only node B has 0-reserve, we don't yet have an API to allow the opener of the + // channel to have 0-reserve. + if allow_0reserve { + assert_eq!(node_b.list_channels()[0].unspendable_punishment_reserve, Some(0)); + assert_eq!(node_b.list_channels()[0].outbound_capacity_msat, push_msat); + assert_eq!(node_b.list_channels()[0].next_outbound_htlc_limit_msat, push_msat); + + assert_eq!(node_b.list_balances().total_lightning_balance_sats * 1000, push_msat); + let LightningBalance::ClaimableOnChannelClose { amount_satoshis, .. } = + node_b.list_balances().lightning_balances[0] + else { + panic!("Unexpected `LightningBalance` variant"); + }; + assert_eq!(amount_satoshis * 1000, push_msat); + } + let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); @@ -1267,6 +1295,37 @@ pub(crate) async fn do_channel_full_cycle( 2 ); + if allow_0reserve { + let node_a_outbound_capacity_msat = node_a.list_channels()[0].outbound_capacity_msat; + let node_a_reserve_msat = + node_a.list_channels()[0].unspendable_punishment_reserve.unwrap() * 1000; + // TODO: Update this for zero-fee commitment channels + let node_a_anchors_msat = if expect_anchor_channel { 2 * 330 * 1000 } else { 0 }; + let funding_amount_msat = node_a.list_channels()[0].channel_value_sats * 1000; + // Node B does not have any reserve, so we only subtract a few items on node A's + // side to arrive at node B's capacity + let node_b_capacity_msat = funding_amount_msat + - node_a_outbound_capacity_msat + - node_a_reserve_msat + - node_a_anchors_msat; + let got_capacity_msat = node_b.list_channels()[0].outbound_capacity_msat; + assert_eq!(got_capacity_msat, node_b_capacity_msat); + assert_ne!(got_capacity_msat, 0); + // Sanity check to make sure this is a non-trivial amount + assert!(got_capacity_msat > 15_000_000); + + // This is a private channel, so node B can send 100% of the value over + assert_eq!(node_b.list_channels()[0].next_outbound_htlc_limit_msat, node_b_capacity_msat); + + node_b.spontaneous_payment().send(node_b_capacity_msat, node_a.node_id(), None).unwrap(); + expect_event!(node_b, PaymentSuccessful); + expect_event!(node_a, PaymentReceived); + + node_a.spontaneous_payment().send(node_b_capacity_msat, node_b.node_id(), None).unwrap(); + expect_event!(node_a, PaymentSuccessful); + expect_event!(node_b, PaymentReceived); + } + println!("\nB close_channel (force: {})", force_close); tokio::time::sleep(Duration::from_secs(1)).await; if force_close { diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 9d1f99bed..35a6c9b3e 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -48,8 +48,17 @@ async fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false) - .await; + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + false, + false, + true, + false, + ) + .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -57,8 +66,17 @@ async fn channel_full_cycle_force_close() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true) - .await; + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + false, + false, + true, + true, + ) + .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -66,8 +84,17 @@ async fn channel_full_cycle_force_close_trusted_no_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true) - .await; + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + false, + false, + true, + true, + ) + .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -75,8 +102,17 @@ async fn channel_full_cycle_0conf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, true, true, false) - .await; + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + true, + false, + true, + false, + ) + .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -84,8 +120,53 @@ async fn channel_full_cycle_legacy_staticremotekey() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, false, false) - .await; + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + false, + false, + false, + false, + ) + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn channel_full_cycle_0reserve() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + false, + true, + true, + false, + ) + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn channel_full_cycle_0conf_0reserve() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); + let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false); + do_channel_full_cycle( + node_a, + node_b, + &bitcoind.client, + &electrsd.client, + true, + true, + true, + false, + ) + .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs index 32226a8b0..210e9a8b2 100644 --- a/tests/integration_tests_vss.rs +++ b/tests/integration_tests_vss.rs @@ -54,6 +54,7 @@ async fn channel_full_cycle_with_vss_store() { &bitcoind.client, &electrsd.client, false, + false, true, false, )