Skip to content

Commit c4a45a6

Browse files
committed
Add ability to open channel with all on-chain funds
Adds open_channel_with_all which uses get_max_drain_amount to determine the largest funding amount after accounting for on-chain fees and anchor reserves
1 parent 615814e commit c4a45a6

File tree

5 files changed

+294
-24
lines changed

5 files changed

+294
-24
lines changed

bindings/ldk_node.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ interface Node {
174174
[Throws=NodeError]
175175
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
176176
[Throws=NodeError]
177+
UserChannelId open_channel_with_all(PublicKey node_id, SocketAddress address, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
178+
[Throws=NodeError]
179+
UserChannelId open_announced_channel_with_all(PublicKey node_id, SocketAddress address, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
180+
[Throws=NodeError]
177181
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
178182
[Throws=NodeError]
179183
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);

src/lib.rs

Lines changed: 122 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub use {
169169
};
170170

171171
use crate::scoring::setup_background_pathfinding_scores_sync;
172+
use crate::wallet::FundingAmount;
172173

173174
#[cfg(feature = "uniffi")]
174175
uniffi::include_scaffolding!("ldk_node");
@@ -1092,7 +1093,7 @@ impl Node {
10921093
}
10931094

10941095
fn open_channel_inner(
1095-
&self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64,
1096+
&self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: FundingAmount,
10961097
push_to_counterparty_msat: Option<u64>, channel_config: Option<ChannelConfig>,
10971098
announce_for_forwarding: bool,
10981099
) -> Result<UserChannelId, Error> {
@@ -1112,8 +1113,38 @@ impl Node {
11121113
con_cm.connect_peer_if_necessary(con_node_id, con_addr).await
11131114
})?;
11141115

1115-
// Check funds availability after connection (includes anchor reserve calculation)
1116-
self.check_sufficient_funds_for_channel(channel_amount_sats, &node_id)?;
1116+
let channel_amount_sats = match channel_amount_sats {
1117+
FundingAmount::Exact { amount_sats } => {
1118+
// Check funds availability after connection (includes anchor reserve
1119+
// calculation).
1120+
self.check_sufficient_funds_for_channel(amount_sats, &peer_info.node_id)?;
1121+
amount_sats
1122+
},
1123+
FundingAmount::Max => {
1124+
// Determine max funding amount from all available on-chain funds.
1125+
let cur_anchor_reserve_sats =
1126+
total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
1127+
let new_channel_reserve =
1128+
self.new_channel_anchor_reserve_sats(&peer_info.node_id)?;
1129+
let total_anchor_reserve_sats = cur_anchor_reserve_sats + new_channel_reserve;
1130+
1131+
let fee_rate =
1132+
self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding);
1133+
1134+
let amount =
1135+
self.wallet.get_max_funding_amount(total_anchor_reserve_sats, fee_rate)?;
1136+
1137+
log_info!(
1138+
self.logger,
1139+
"Opening channel with all balance: {}sats (fee rate: {} sat/kw, anchor reserve: {}sats)",
1140+
amount,
1141+
fee_rate.to_sat_per_kwu(),
1142+
total_anchor_reserve_sats,
1143+
);
1144+
1145+
amount
1146+
},
1147+
};
11171148

11181149
let mut user_config = default_user_config(&self.config);
11191150
user_config.channel_handshake_config.announce_for_forwarding = announce_for_forwarding;
@@ -1156,6 +1187,25 @@ impl Node {
11561187
}
11571188
}
11581189

1190+
fn new_channel_anchor_reserve_sats(&self, peer_node_id: &PublicKey) -> Result<u64, Error> {
1191+
let init_features = self
1192+
.peer_manager
1193+
.peer_by_node_id(peer_node_id)
1194+
.ok_or(Error::ConnectionFailed)?
1195+
.init_features;
1196+
let sats = self.config.anchor_channels_config.as_ref().map_or(0, |c| {
1197+
if init_features.requires_anchors_zero_fee_htlc_tx()
1198+
&& !c.trusted_peers_no_reserve.contains(peer_node_id)
1199+
{
1200+
c.per_channel_reserve_sats
1201+
} else {
1202+
0
1203+
}
1204+
});
1205+
1206+
Ok(sats)
1207+
}
1208+
11591209
fn check_sufficient_funds_for_channel(
11601210
&self, amount_sats: u64, peer_node_id: &PublicKey,
11611211
) -> Result<(), Error> {
@@ -1174,21 +1224,8 @@ impl Node {
11741224
}
11751225

11761226
// Fail if we have less than the channel value + anchor reserve available (if applicable).
1177-
let init_features = self
1178-
.peer_manager
1179-
.peer_by_node_id(peer_node_id)
1180-
.ok_or(Error::ConnectionFailed)?
1181-
.init_features;
1182-
let required_funds_sats = amount_sats
1183-
+ self.config.anchor_channels_config.as_ref().map_or(0, |c| {
1184-
if init_features.requires_anchors_zero_fee_htlc_tx()
1185-
&& !c.trusted_peers_no_reserve.contains(peer_node_id)
1186-
{
1187-
c.per_channel_reserve_sats
1188-
} else {
1189-
0
1190-
}
1191-
});
1227+
let required_funds_sats =
1228+
amount_sats + self.new_channel_anchor_reserve_sats(peer_node_id)?;
11921229

11931230
if spendable_amount_sats < required_funds_sats {
11941231
log_error!(self.logger,
@@ -1225,7 +1262,7 @@ impl Node {
12251262
self.open_channel_inner(
12261263
node_id,
12271264
address,
1228-
channel_amount_sats,
1265+
FundingAmount::Exact { amount_sats: channel_amount_sats },
12291266
push_to_counterparty_msat,
12301267
channel_config,
12311268
false,
@@ -1265,7 +1302,72 @@ impl Node {
12651302
self.open_channel_inner(
12661303
node_id,
12671304
address,
1268-
channel_amount_sats,
1305+
FundingAmount::Exact { amount_sats: channel_amount_sats },
1306+
push_to_counterparty_msat,
1307+
channel_config,
1308+
true,
1309+
)
1310+
}
1311+
1312+
/// Connect to a node and open a new unannounced channel, using all available on-chain funds
1313+
/// minus fees and anchor reserves.
1314+
///
1315+
/// To open an announced channel, see [`Node::open_announced_channel_with_all`].
1316+
///
1317+
/// Disconnects and reconnects are handled automatically.
1318+
///
1319+
/// If `push_to_counterparty_msat` is set, the given value will be pushed (read: sent) to the
1320+
/// channel counterparty on channel open. This can be useful to start out with the balance not
1321+
/// entirely shifted to one side, therefore allowing to receive payments from the getgo.
1322+
///
1323+
/// Returns a [`UserChannelId`] allowing to locally keep track of the channel.
1324+
///
1325+
/// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats
1326+
pub fn open_channel_with_all(
1327+
&self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option<u64>,
1328+
channel_config: Option<ChannelConfig>,
1329+
) -> Result<UserChannelId, Error> {
1330+
self.open_channel_inner(
1331+
node_id,
1332+
address,
1333+
FundingAmount::Max,
1334+
push_to_counterparty_msat,
1335+
channel_config,
1336+
false,
1337+
)
1338+
}
1339+
1340+
/// Connect to a node and open a new announced channel, using all available on-chain funds
1341+
/// minus fees and anchor reserves.
1342+
///
1343+
/// This will return an error if the node has not been sufficiently configured to operate as a
1344+
/// forwarding node that can properly announce its existence to the public network graph, i.e.,
1345+
/// [`Config::listening_addresses`] and [`Config::node_alias`] are unset.
1346+
///
1347+
/// To open an unannounced channel, see [`Node::open_channel_with_all`].
1348+
///
1349+
/// Disconnects and reconnects are handled automatically.
1350+
///
1351+
/// If `push_to_counterparty_msat` is set, the given value will be pushed (read: sent) to the
1352+
/// channel counterparty on channel open. This can be useful to start out with the balance not
1353+
/// entirely shifted to one side, therefore allowing to receive payments from the getgo.
1354+
///
1355+
/// Returns a [`UserChannelId`] allowing to locally keep track of the channel.
1356+
///
1357+
/// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats
1358+
pub fn open_announced_channel_with_all(
1359+
&self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option<u64>,
1360+
channel_config: Option<ChannelConfig>,
1361+
) -> Result<UserChannelId, Error> {
1362+
if let Err(err) = may_announce_channel(&self.config) {
1363+
log_error!(self.logger, "Failed to open announced channel as the node hasn't been sufficiently configured to act as a forwarding node: {err}");
1364+
return Err(Error::ChannelCreationFailed);
1365+
}
1366+
1367+
self.open_channel_inner(
1368+
node_id,
1369+
address,
1370+
FundingAmount::Max,
12691371
push_to_counterparty_msat,
12701372
channel_config,
12711373
true,

src/wallet/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ pub(crate) enum OnchainSendAmount {
6868
AllDrainingReserve,
6969
}
7070

71+
pub(crate) enum FundingAmount {
72+
Exact { amount_sats: u64 },
73+
Max,
74+
}
75+
7176
pub(crate) mod persist;
7277
pub(crate) mod ser;
7378

@@ -634,6 +639,29 @@ impl Wallet {
634639
Ok((max_amount, tmp_psbt))
635640
}
636641

642+
/// Returns the maximum amount available for funding a channel, accounting for on-chain fees
643+
/// and anchor reserves.
644+
pub(crate) fn get_max_funding_amount(
645+
&self, cur_anchor_reserve_sats: u64, fee_rate: FeeRate,
646+
) -> Result<u64, Error> {
647+
let mut locked_wallet = self.inner.lock().unwrap();
648+
649+
// Use a dummy P2WSH script (34 bytes) to match the size of a real funding output.
650+
let dummy_p2wsh_script = ScriptBuf::new().to_p2wsh();
651+
652+
let (max_amount, tmp_psbt) = self.get_max_drain_amount(
653+
&mut locked_wallet,
654+
dummy_p2wsh_script,
655+
cur_anchor_reserve_sats,
656+
fee_rate,
657+
None,
658+
)?;
659+
660+
locked_wallet.cancel_tx(&tmp_psbt.unsigned_tx);
661+
662+
Ok(max_amount)
663+
}
664+
637665
pub(crate) fn parse_and_validate_address(&self, address: &Address) -> Result<Address, Error> {
638666
Address::<NetworkUnchecked>::from_str(address.to_string().as_str())
639667
.map_err(|_| Error::InvalidAddress)?

tests/common/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,38 @@ pub async fn open_channel_push_amt(
747747
funding_txo_a
748748
}
749749

750+
pub async fn open_channel_with_all(
751+
node_a: &TestNode, node_b: &TestNode, should_announce: bool, electrsd: &ElectrsD,
752+
) -> OutPoint {
753+
if should_announce {
754+
node_a
755+
.open_announced_channel_with_all(
756+
node_b.node_id(),
757+
node_b.listening_addresses().unwrap().first().unwrap().clone(),
758+
None,
759+
None,
760+
)
761+
.unwrap();
762+
} else {
763+
node_a
764+
.open_channel_with_all(
765+
node_b.node_id(),
766+
node_b.listening_addresses().unwrap().first().unwrap().clone(),
767+
None,
768+
None,
769+
)
770+
.unwrap();
771+
}
772+
assert!(node_a.list_peers().iter().find(|c| { c.node_id == node_b.node_id() }).is_some());
773+
774+
let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id());
775+
let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id());
776+
assert_eq!(funding_txo_a, funding_txo_b);
777+
wait_for_tx(&electrsd.client, funding_txo_a.txid).await;
778+
779+
funding_txo_a
780+
}
781+
750782
pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
751783
node_a: TestNode, node_b: TestNode, bitcoind: &BitcoindClient, electrsd: &E, allow_0conf: bool,
752784
expect_anchor_channel: bool, force_close: bool,

0 commit comments

Comments
 (0)