Skip to content

Commit 2c2c287

Browse files
committed
Reserve onchain funds for anchor channels when peer sets them optional
When we are preparing to open a channel to a peer, we should reserve onchain funds for an anchor channel when the peer's init features signals anchor channels as optional, as channel negotiation with such a peer can result in an anchor channel. Tests written with codex.
1 parent 4c8ecb2 commit 2c2c287

3 files changed

Lines changed: 159 additions & 4 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,7 @@ impl Node {
13531353
.peer_by_node_id(peer_node_id)
13541354
.ok_or(Error::ConnectionFailed)?
13551355
.init_features;
1356-
let anchor_channel = init_features.requires_anchors_zero_fee_htlc_tx();
1356+
let anchor_channel = init_features.supports_anchors_zero_fee_htlc_tx();
13571357
Ok(new_channel_anchor_reserve_sats(&self.config, peer_node_id, anchor_channel))
13581358
}
13591359

src/liquidity/service/lsps2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ where
454454
self.wallet.get_spendable_amount_sats(cur_anchor_reserve_sats).unwrap_or(0);
455455
let required_funds_sats = channel_amount_sats
456456
+ self.config.anchor_channels_config.as_ref().map_or(0, |c| {
457-
if init_features.requires_anchors_zero_fee_htlc_tx()
457+
if init_features.supports_anchors_zero_fee_htlc_tx()
458458
&& !c.trusted_peers_no_reserve.contains(&their_network_key)
459459
{
460460
c.per_channel_reserve_sats
@@ -465,7 +465,7 @@ where
465465
if spendable_amount_sats < required_funds_sats {
466466
log_error!(self.logger,
467467
"Unable to create channel due to insufficient funds. Available: {}sats, Required: {}sats",
468-
spendable_amount_sats, channel_amount_sats
468+
spendable_amount_sats, required_funds_sats,
469469
);
470470
// TODO: We just silently fail here. Eventually we will need to remember
471471
// the pending requests and regularly retry opening the channel until we

tests/integration_tests_rust.rs

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use ldk_node::payment::{
3636
ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus,
3737
TransactionType, UnifiedPaymentResult,
3838
};
39-
use ldk_node::{BuildError, Builder, Event, Node, NodeError};
39+
use ldk_node::{BuildError, Builder, Event, Node, NodeError, ReserveType};
4040
use lightning::ln::channelmanager::PaymentId;
4141
use lightning::routing::gossip::{NodeAlias, NodeId};
4242
use lightning::routing::router::RouteParametersConfig;
@@ -3937,6 +3937,161 @@ async fn open_channel_with_all_with_anchors() {
39373937
node_b.stop().unwrap();
39383938
}
39393939

3940+
#[derive(Clone, Copy)]
3941+
enum OpenChannelVariant {
3942+
Standard,
3943+
Announced,
3944+
ZeroReserve,
3945+
StandardWithAll,
3946+
AnnouncedWithAll,
3947+
ZeroReserveWithAll,
3948+
}
3949+
3950+
impl OpenChannelVariant {
3951+
fn label(&self) -> &'static str {
3952+
match self {
3953+
Self::Standard => "open_channel",
3954+
Self::Announced => "open_announced_channel",
3955+
Self::ZeroReserve => "open_0reserve_channel",
3956+
Self::StandardWithAll => "open_channel_with_all",
3957+
Self::AnnouncedWithAll => "open_announced_channel_with_all",
3958+
Self::ZeroReserveWithAll => "open_0reserve_channel_with_all",
3959+
}
3960+
}
3961+
}
3962+
3963+
fn open_channel_variant(
3964+
variant: OpenChannelVariant, node_a: &Node, node_b: &Node, channel_amount_sats: u64,
3965+
) -> Result<(), NodeError> {
3966+
let address = node_b.listening_addresses().unwrap().first().unwrap().clone();
3967+
match variant {
3968+
OpenChannelVariant::Standard => node_a
3969+
.open_channel(node_b.node_id(), address, channel_amount_sats, None, None)
3970+
.map(|_| ()),
3971+
OpenChannelVariant::Announced => node_a
3972+
.open_announced_channel(node_b.node_id(), address, channel_amount_sats, None, None)
3973+
.map(|_| ()),
3974+
OpenChannelVariant::ZeroReserve => node_a
3975+
.open_0reserve_channel(node_b.node_id(), address, channel_amount_sats, None, None)
3976+
.map(|_| ()),
3977+
OpenChannelVariant::StandardWithAll => {
3978+
node_a.open_channel_with_all(node_b.node_id(), address, None, None).map(|_| ())
3979+
},
3980+
OpenChannelVariant::AnnouncedWithAll => node_a
3981+
.open_announced_channel_with_all(node_b.node_id(), address, None, None)
3982+
.map(|_| ()),
3983+
OpenChannelVariant::ZeroReserveWithAll => {
3984+
node_a.open_0reserve_channel_with_all(node_b.node_id(), address, None, None).map(|_| ())
3985+
},
3986+
}
3987+
}
3988+
3989+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
3990+
async fn open_channel_variants_reserve_funds_for_anchor_peers() {
3991+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
3992+
let chain_source = random_chain_source(&bitcoind, &electrsd);
3993+
3994+
let exact_variants = [
3995+
OpenChannelVariant::Standard,
3996+
OpenChannelVariant::Announced,
3997+
OpenChannelVariant::ZeroReserve,
3998+
];
3999+
let with_all_variants = [
4000+
OpenChannelVariant::StandardWithAll,
4001+
OpenChannelVariant::AnnouncedWithAll,
4002+
OpenChannelVariant::ZeroReserveWithAll,
4003+
];
4004+
4005+
let premine_amount_sat = 1_000_000;
4006+
let exact_channel_amount_sat = premine_amount_sat - 10_000;
4007+
let anchor_reserve_sat = 25_000;
4008+
4009+
let mut addresses = Vec::new();
4010+
let mut exact_cases = Vec::new();
4011+
for variant in exact_variants {
4012+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
4013+
addresses.push(node_a.onchain_payment().new_address().unwrap());
4014+
addresses.push(node_b.onchain_payment().new_address().unwrap());
4015+
exact_cases.push((variant, node_a, node_b));
4016+
}
4017+
4018+
let mut with_all_cases = Vec::new();
4019+
for variant in with_all_variants {
4020+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
4021+
addresses.push(node_a.onchain_payment().new_address().unwrap());
4022+
addresses.push(node_b.onchain_payment().new_address().unwrap());
4023+
with_all_cases.push((variant, node_a, node_b));
4024+
}
4025+
4026+
premine_and_distribute_funds(
4027+
&bitcoind.client,
4028+
&electrsd.client,
4029+
addresses,
4030+
Amount::from_sat(premine_amount_sat),
4031+
)
4032+
.await;
4033+
4034+
for (_, node_a, node_b) in exact_cases.iter().chain(with_all_cases.iter()) {
4035+
node_a.sync_wallets().unwrap();
4036+
node_b.sync_wallets().unwrap();
4037+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
4038+
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
4039+
}
4040+
4041+
for (variant, node_a, node_b) in exact_cases {
4042+
assert_eq!(
4043+
Err(NodeError::InsufficientFunds),
4044+
open_channel_variant(variant, &node_a, &node_b, exact_channel_amount_sat),
4045+
"{} should require funds for the channel amount plus anchor reserve",
4046+
variant.label()
4047+
);
4048+
node_a.stop().unwrap();
4049+
node_b.stop().unwrap();
4050+
}
4051+
4052+
let mut opened_with_all_cases = Vec::new();
4053+
for (variant, node_a, node_b) in with_all_cases {
4054+
open_channel_variant(variant, &node_a, &node_b, 0)
4055+
.unwrap_or_else(|e| panic!("{} failed: {e:?}", variant.label()));
4056+
4057+
let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id());
4058+
let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id());
4059+
assert_eq!(funding_txo_a, funding_txo_b, "{} funding txo mismatch", variant.label());
4060+
wait_for_tx(&electrsd.client, funding_txo_a.txid).await;
4061+
4062+
opened_with_all_cases.push((variant, node_a, node_b, funding_txo_a));
4063+
}
4064+
4065+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
4066+
4067+
for (variant, node_a, node_b, funding_txo) in opened_with_all_cases {
4068+
node_a.sync_wallets().unwrap();
4069+
node_b.sync_wallets().unwrap();
4070+
4071+
let _user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id());
4072+
let _user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id());
4073+
4074+
let balances = node_a.list_balances();
4075+
assert_eq!(balances.total_onchain_balance_sats, anchor_reserve_sat - 1);
4076+
assert_eq!(balances.total_anchor_channels_reserve_sats, anchor_reserve_sat - 1);
4077+
assert_eq!(balances.spendable_onchain_balance_sats, 0);
4078+
4079+
let channels = node_a.list_channels();
4080+
assert_eq!(channels.len(), 1, "{} should have one channel", variant.label());
4081+
let channel = &channels[0];
4082+
// Also subtract the fees spent to open the channel
4083+
assert_eq!(channel.channel_value_sats, premine_amount_sat - anchor_reserve_sat - 155);
4084+
assert_eq!(channel.counterparty.node_id, node_b.node_id());
4085+
assert!(channel.counterparty.features.supports_anchors_zero_fee_htlc_tx());
4086+
assert!(!channel.counterparty.features.requires_anchors_zero_fee_htlc_tx());
4087+
assert_eq!(channel.funding_txo.unwrap(), funding_txo);
4088+
assert_eq!(channel.reserve_type, Some(ReserveType::Adaptive));
4089+
4090+
node_a.stop().unwrap();
4091+
node_b.stop().unwrap();
4092+
}
4093+
}
4094+
39404095
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
39414096
async fn open_channel_with_all_without_anchors() {
39424097
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)