Skip to content

Commit e4746a2

Browse files
committed
Explicitly support counterparty setting 0 channel reserve
A peer providing a channel_reserve_satoshis of 0 (or less than our dust limit) is insecure, but only for them. Because some LSPs do it with some level of trust of the clients (for a substantial UX improvement), we explicitly allow it. Because its unlikely to happen often in normal testing, we test it explicitly here.
1 parent d2df65a commit e4746a2

File tree

3 files changed

+92
-19
lines changed

3 files changed

+92
-19
lines changed

lightning/src/ln/channel.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,9 @@ pub(super) struct MonitorRestoreUpdates {
387387
/// the channel. Sadly, there isn't really a good number for this - if we expect to have no new
388388
/// HTLCs for days we may need this to suffice for feerate increases across days, but that may
389389
/// leave the channel less usable as we hold a bigger reserve.
390-
#[cfg(fuzzing)]
390+
#[cfg(any(fuzzing, test))]
391391
pub const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2;
392-
#[cfg(not(fuzzing))]
392+
#[cfg(not(any(fuzzing, test)))]
393393
const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2;
394394

395395
/// If we fail to see a funding transaction confirmed on-chain within this many blocks after the
@@ -516,19 +516,30 @@ pub(super) struct Channel<Signer: Sign> {
516516
channel_creation_height: u32,
517517

518518
counterparty_dust_limit_satoshis: u64,
519+
519520
#[cfg(test)]
520521
pub(super) holder_dust_limit_satoshis: u64,
521522
#[cfg(not(test))]
522523
holder_dust_limit_satoshis: u64,
524+
523525
#[cfg(test)]
524526
pub(super) counterparty_max_htlc_value_in_flight_msat: u64,
525527
#[cfg(not(test))]
526528
counterparty_max_htlc_value_in_flight_msat: u64,
529+
530+
#[cfg(test)]
531+
pub(super) holder_max_htlc_value_in_flight_msat: u64,
532+
#[cfg(not(test))]
527533
holder_max_htlc_value_in_flight_msat: u64,
528534

529535
/// minimum channel reserve for self to maintain - set by them.
530536
counterparty_selected_channel_reserve_satoshis: Option<u64>,
537+
538+
#[cfg(test)]
539+
pub(super) holder_selected_channel_reserve_satoshis: u64,
540+
#[cfg(not(test))]
531541
holder_selected_channel_reserve_satoshis: u64,
542+
532543
counterparty_htlc_minimum_msat: u64,
533544
holder_htlc_minimum_msat: u64,
534545
#[cfg(test)]
@@ -921,9 +932,6 @@ impl<Signer: Sign> Channel<Signer> {
921932
if msg.dust_limit_satoshis > msg.funding_satoshis {
922933
return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than funding_satoshis {}. Peer never wants payout outputs?", msg.dust_limit_satoshis, msg.funding_satoshis)));
923934
}
924-
if msg.dust_limit_satoshis > msg.channel_reserve_satoshis {
925-
return Err(ChannelError::Close(format!("Bogus; channel reserve ({}) is less than dust limit ({})", msg.channel_reserve_satoshis, msg.dust_limit_satoshis)));
926-
}
927935
let full_channel_value_msat = (msg.funding_satoshis - msg.channel_reserve_satoshis) * 1000;
928936
if msg.htlc_minimum_msat >= full_channel_value_msat {
929937
return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat)));
@@ -979,9 +987,6 @@ impl<Signer: Sign> Channel<Signer> {
979987
if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS {
980988
return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({}). dust_limit_satoshis is ({}).", holder_selected_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS)));
981989
}
982-
if msg.channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS {
983-
return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is smaller than our dust limit ({})", msg.channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS)));
984-
}
985990
if holder_selected_channel_reserve_satoshis < msg.dust_limit_satoshis {
986991
return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", msg.dust_limit_satoshis, holder_selected_channel_reserve_satoshis)));
987992
}

lightning/src/ln/functional_test_utils.rs

+17-8
Original file line numberDiff line numberDiff line change
@@ -526,19 +526,16 @@ pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_
526526
_ => panic!("Unexpected event"),
527527
}
528528
}
529-
530-
pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64, a_flags: InitFeatures, b_flags: InitFeatures) -> Transaction {
531-
let create_chan_id = node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None).unwrap();
532-
node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), a_flags, &get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id()));
533-
node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), b_flags, &get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id()));
534-
529+
pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: [u8; 32]) -> Transaction {
535530
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, channel_value, 42);
536-
assert_eq!(temporary_channel_id, create_chan_id);
531+
assert_eq!(temporary_channel_id, expected_temporary_channel_id);
537532

538533
node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).unwrap();
539534
check_added_monitors!(node_a, 0);
540535

541-
node_b.node.handle_funding_created(&node_a.node.get_our_node_id(), &get_event_msg!(node_a, MessageSendEvent::SendFundingCreated, node_b.node.get_our_node_id()));
536+
let funding_created_msg = get_event_msg!(node_a, MessageSendEvent::SendFundingCreated, node_b.node.get_our_node_id());
537+
assert_eq!(funding_created_msg.temporary_channel_id, expected_temporary_channel_id);
538+
node_b.node.handle_funding_created(&node_a.node.get_our_node_id(), &funding_created_msg);
542539
{
543540
let mut added_monitors = node_b.chain_monitor.added_monitors.lock().unwrap();
544541
assert_eq!(added_monitors.len(), 1);
@@ -564,6 +561,18 @@ pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, '
564561
tx
565562
}
566563

564+
pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64, a_flags: InitFeatures, b_flags: InitFeatures) -> Transaction {
565+
let create_chan_id = node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None).unwrap();
566+
let open_channel_msg = get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id());
567+
assert_eq!(open_channel_msg.temporary_channel_id, create_chan_id);
568+
node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), a_flags, &open_channel_msg);
569+
let accept_channel_msg = get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id());
570+
assert_eq!(accept_channel_msg.temporary_channel_id, create_chan_id);
571+
node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), b_flags, &accept_channel_msg);
572+
573+
sign_funding_transaction(node_a, node_b, channel_value, create_chan_id)
574+
}
575+
567576
pub fn create_chan_between_nodes_with_value_confirm_first<'a, 'b, 'c, 'd>(node_recv: &'a Node<'b, 'c, 'c>, node_conf: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) {
568577
confirm_transaction_at(node_conf, tx, conf_height);
569578
connect_blocks(node_conf, CHAN_CONFIRM_DEPTH - 1);

lightning/src/ln/functional_tests.rs

+62-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use chain::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PER
1818
use chain::transaction::OutPoint;
1919
use chain::keysinterface::BaseSign;
2020
use ln::{PaymentPreimage, PaymentSecret, PaymentHash};
21-
use ln::channel::{COMMITMENT_TX_BASE_WEIGHT, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, MIN_AFFORDABLE_HTLC_COUNT};
21+
use ln::channel::{COMMITMENT_TX_BASE_WEIGHT, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT};
2222
use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, PaymentSendFailure, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA};
2323
use ln::channel::{Channel, ChannelError};
2424
use ln::{chan_utils, onion_utils};
@@ -108,8 +108,6 @@ fn test_insane_channel_opens() {
108108

109109
insane_open_helper("Peer never wants payout outputs?", |mut msg| { msg.dust_limit_satoshis = msg.funding_satoshis + 1 ; msg });
110110

111-
insane_open_helper(r"Bogus; channel reserve \(\d+\) is less than dust limit \(\d+\)", |mut msg| { msg.dust_limit_satoshis = msg.channel_reserve_satoshis + 1; msg });
112-
113111
insane_open_helper(r"Minimum htlc value \(\d+\) was larger than full channel value \(\d+\)", |mut msg| { msg.htlc_minimum_msat = (msg.funding_satoshis - msg.channel_reserve_satoshis) * 1000; msg });
114112

115113
insane_open_helper("They wanted our payments to be delayed by a needlessly long period", |mut msg| { msg.to_self_delay = MAX_LOCAL_BREAKDOWN_TIMEOUT + 1; msg });
@@ -119,6 +117,67 @@ fn test_insane_channel_opens() {
119117
insane_open_helper("max_accepted_htlcs was 484. It must not be larger than 483", |mut msg| { msg.max_accepted_htlcs = 484; msg });
120118
}
121119

120+
fn do_test_counterparty_no_reserve(send_from_initiator: bool) {
121+
// A peer providing a channel_reserve_satoshis of 0 (or less than our dust limit) is insecure,
122+
// but only for them. Because some LSPs do it with some level of trust of the clients (for a
123+
// substantial UX improvement), we explicitly allow it. Because its unlikely to happen often in
124+
// normal testing, we test it explicitly here.
125+
let chanmon_cfgs = create_chanmon_cfgs(2);
126+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
127+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
128+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
129+
130+
// Have node0 initiate a channel to node1 with aforementioned parameters
131+
let mut push_amt = 100_000_000;
132+
let feerate_per_kw = 253;
133+
push_amt -= feerate_per_kw as u64 * (COMMITMENT_TX_BASE_WEIGHT + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000;
134+
push_amt -= Channel::<EnforcingSigner>::get_holder_selected_channel_reserve_satoshis(100_000) * 1000;
135+
136+
let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, if send_from_initiator { 0 } else { push_amt }, 42, None).unwrap();
137+
let mut open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
138+
if !send_from_initiator {
139+
open_channel_message.channel_reserve_satoshis = 0;
140+
open_channel_message.max_htlc_value_in_flight_msat = 100_000_000;
141+
}
142+
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &open_channel_message);
143+
144+
// Extract the channel accept message from node1 to node0
145+
let mut accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
146+
if send_from_initiator {
147+
accept_channel_message.channel_reserve_satoshis = 0;
148+
accept_channel_message.max_htlc_value_in_flight_msat = 100_000_000;
149+
}
150+
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &accept_channel_message);
151+
{
152+
let mut lock;
153+
let mut chan = get_channel_ref!(if send_from_initiator { &nodes[1] } else { &nodes[0] }, lock, temp_channel_id);
154+
chan.holder_selected_channel_reserve_satoshis = 0;
155+
chan.holder_max_htlc_value_in_flight_msat = 100_000_000;
156+
}
157+
158+
let funding_tx = sign_funding_transaction(&nodes[0], &nodes[1], 100_000, temp_channel_id);
159+
let funding_msgs = create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &funding_tx);
160+
create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &funding_msgs.0);
161+
162+
// nodes[0] should now be able to send the full balance to nodes[1], violating nodes[1]'s
163+
// security model if it ever tries to send funds back to nodes[0] (but that's not our problem).
164+
if send_from_initiator {
165+
send_payment(&nodes[0], &[&nodes[1]], 100_000_000
166+
// Note that for outbound channels we have to consider the commitment tx fee and the
167+
// "fee spike buffer", which is currently a multiple of the total commitment tx fee as
168+
// well as an additional HTLC.
169+
- FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE * feerate_per_kw * (COMMITMENT_TX_BASE_WEIGHT + 2 * COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000);
170+
} else {
171+
send_payment(&nodes[1], &[&nodes[0]], push_amt);
172+
}
173+
}
174+
175+
#[test]
176+
fn test_counterparty_no_reserve() {
177+
do_test_counterparty_no_reserve(true);
178+
do_test_counterparty_no_reserve(false);
179+
}
180+
122181
#[test]
123182
fn test_async_inbound_update_fee() {
124183
let chanmon_cfgs = create_chanmon_cfgs(2);

0 commit comments

Comments
 (0)