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
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.

nit: Please avoid the 'this channel (..) that channel' duplication (here and below).

/// 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
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.

Maybe just say 'your channel balance' rather than funds, which should allow to drop the 'in those channels' part? (also note it should be 'which', not 'with' in the current version, but probably better to drop that altogether).

/// 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(
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
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.

Mind opening an issue for this TODO, so we don't forget to actually address it. Could also be a bit more verbose, i.e., what are we waiting for exactly/what do we intend to update?

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