Skip to content
Open
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
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
///
Expand Down
6 changes: 3 additions & 3 deletions src/liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down
79 changes: 69 additions & 10 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ pub async fn splice_in_with_all(

pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
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();
Expand Down Expand Up @@ -846,15 +846,27 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, but should we also assert that we can spend the full channel balance now that both coutnerparties set 0reserve for each other?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you see below. A caveat is that we don't have an API yet for the opener of the channel to get 0-reserve.

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);
Expand Down Expand Up @@ -913,6 +925,22 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
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());

Expand Down Expand Up @@ -1267,6 +1295,37 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
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 {
Expand Down
101 changes: 91 additions & 10 deletions tests/integration_tests_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,44 +48,125 @@ 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)]
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)]
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)]
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)]
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)]
Expand Down
1 change: 1 addition & 0 deletions tests/integration_tests_vss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ async fn channel_full_cycle_with_vss_store() {
&bitcoind.client,
&electrsd.client,
false,
false,
true,
false,
)
Expand Down