Skip to content

Commit 127d8a4

Browse files
committed
Maintain and expose anchor reserve
1 parent e4e69e9 commit 127d8a4

File tree

11 files changed

+235
-50
lines changed

11 files changed

+235
-50
lines changed

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ class LibraryTest {
203203
val spendableBalance2AfterOpen = node2.listBalances().spendableOnchainBalanceSats
204204
println("Spendable balance 1 after open: $spendableBalance1AfterOpen")
205205
println("Spendable balance 2 after open: $spendableBalance2AfterOpen")
206-
assert(spendableBalance1AfterOpen > 49000u)
207-
assert(spendableBalance1AfterOpen < 50000u)
208-
assertEquals(100000uL, spendableBalance2AfterOpen)
206+
assert(spendableBalance1AfterOpen > 24000u)
207+
assert(spendableBalance1AfterOpen < 25000u)
208+
assertEquals(75000uL, spendableBalance2AfterOpen)
209209

210210
val channelReadyEvent1 = node1.waitNextEvent()
211211
println("Got event: $channelReadyEvent1")

bindings/ldk_node.udl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ interface PendingSweepBalance {
334334
dictionary BalanceDetails {
335335
u64 total_onchain_balance_sats;
336336
u64 spendable_onchain_balance_sats;
337+
u64 total_anchor_channels_reserve_sats;
337338
u64 total_lightning_balance_sats;
338339
sequence<LightningBalance> lightning_balances;
339340
sequence<PendingSweepBalance> pending_balances_from_channel_closures;

src/balance.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@ pub struct BalanceDetails {
1414
/// The total balance of our on-chain wallet.
1515
pub total_onchain_balance_sats: u64,
1616
/// The currently spendable balance of our on-chain wallet.
17+
///
18+
/// This includes any sufficiently confirmed funds, minus
19+
/// [`total_anchor_channels_reserve_sats`].
20+
///
21+
/// [`total_anchor_channels_reserve_sats`]: Self::total_anchor_channels_reserve_sats
1722
pub spendable_onchain_balance_sats: u64,
23+
/// The share of our total balance which we retain as an emergency reserve to (hopefully) be
24+
/// able to spend the Anchor outputs when one of our channels is closed.
25+
pub total_anchor_channels_reserve_sats: u64,
1826
/// The total balance that we would be able to claim across all our Lightning channels.
1927
///
2028
/// Note this excludes balances that we are unsure if we are able to claim (e.g., as we are

src/event.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -733,9 +733,68 @@ where
733733
temporary_channel_id,
734734
counterparty_node_id,
735735
funding_satoshis,
736-
channel_type: _,
736+
channel_type,
737737
push_msat: _,
738738
} => {
739+
let anchor_channel = channel_type.requires_anchors_zero_fee_htlc_tx();
740+
741+
if anchor_channel {
742+
if let Some(anchor_channels_config) =
743+
self.config.anchor_channels_config.as_ref()
744+
{
745+
let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(
746+
&self.channel_manager,
747+
&self.config,
748+
);
749+
let spendable_amount_sats = self
750+
.wallet
751+
.get_balances(cur_anchor_reserve_sats)
752+
.map(|(_, s)| s)
753+
.unwrap_or(0);
754+
755+
let required_amount_sats = if anchor_channels_config
756+
.trusted_peers_no_reserve
757+
.contains(&counterparty_node_id)
758+
{
759+
0
760+
} else {
761+
anchor_channels_config.per_channel_reserve_sats
762+
};
763+
764+
if spendable_amount_sats < required_amount_sats {
765+
log_error!(
766+
self.logger,
767+
"Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves.",
768+
counterparty_node_id,
769+
);
770+
self.channel_manager
771+
.force_close_without_broadcasting_txn(
772+
&temporary_channel_id,
773+
&counterparty_node_id,
774+
)
775+
.unwrap_or_else(|e| {
776+
log_error!(self.logger, "Failed to reject channel: {:?}", e)
777+
});
778+
return;
779+
}
780+
} else {
781+
log_error!(
782+
self.logger,
783+
"Rejecting inbound channel from peer {} due to Anchor channels being disabled.",
784+
counterparty_node_id,
785+
);
786+
self.channel_manager
787+
.force_close_without_broadcasting_txn(
788+
&temporary_channel_id,
789+
&counterparty_node_id,
790+
)
791+
.unwrap_or_else(|e| {
792+
log_error!(self.logger, "Failed to reject channel: {:?}", e)
793+
});
794+
return;
795+
}
796+
}
797+
739798
let user_channel_id: u128 = rand::thread_rng().gen::<u128>();
740799
let allow_0conf = self.config.trusted_peers_0conf.contains(&counterparty_node_id);
741800
let res = if allow_0conf {
@@ -756,8 +815,9 @@ where
756815
Ok(()) => {
757816
log_info!(
758817
self.logger,
759-
"Accepting inbound{} channel of {}sats from{} peer {}",
818+
"Accepting inbound{}{} channel of {}sats from{} peer {}",
760819
if allow_0conf { " 0conf" } else { "" },
820+
if anchor_channel { " Anchor" } else { "" },
761821
funding_satoshis,
762822
if allow_0conf { " trusted" } else { "" },
763823
counterparty_node_id,
@@ -766,8 +826,9 @@ where
766826
Err(e) => {
767827
log_error!(
768828
self.logger,
769-
"Error while accepting inbound{} channel from{} peer {}: {:?}",
829+
"Error while accepting inbound{}{} channel from{} peer {}: {:?}",
770830
if allow_0conf { " 0conf" } else { "" },
831+
if anchor_channel { " Anchor" } else { "" },
771832
counterparty_node_id,
772833
if allow_0conf { " trusted" } else { "" },
773834
e,

src/lib.rs

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger};
142142

143143
use lightning::chain::{BestBlock, Confirm};
144144
use lightning::events::bump_transaction::Wallet as LdkWallet;
145-
use lightning::ln::channelmanager::PaymentId;
145+
use lightning::ln::channelmanager::{ChannelShutdownState, PaymentId};
146146
use lightning::ln::msgs::SocketAddress;
147147

148148
use lightning::util::config::{ChannelHandshakeConfig, UserConfig};
@@ -886,6 +886,8 @@ impl Node {
886886
OnchainPayment::new(
887887
Arc::clone(&self.runtime),
888888
Arc::clone(&self.wallet),
889+
Arc::clone(&self.channel_manager),
890+
Arc::clone(&self.config),
889891
Arc::clone(&self.logger),
890892
)
891893
}
@@ -896,6 +898,8 @@ impl Node {
896898
Arc::new(OnchainPayment::new(
897899
Arc::clone(&self.runtime),
898900
Arc::clone(&self.wallet),
901+
Arc::clone(&self.channel_manager),
902+
Arc::clone(&self.config),
899903
Arc::clone(&self.logger),
900904
))
901905
}
@@ -971,6 +975,10 @@ impl Node {
971975
/// channel counterparty on channel open. This can be useful to start out with the balance not
972976
/// entirely shifted to one side, therefore allowing to receive payments from the getgo.
973977
///
978+
/// If Anchor channels are enabled, this will ensure the configured
979+
/// [`AnchorChannelsConfig::per_channel_reserve_sats`] is available and will be retained before
980+
/// opening the channel.
981+
///
974982
/// Returns a [`UserChannelId`] allowing to locally keep track of the channel.
975983
pub fn connect_open_channel(
976984
&self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64,
@@ -983,18 +991,26 @@ impl Node {
983991
}
984992
let runtime = rt_lock.as_ref().unwrap();
985993

986-
let cur_balance = self.wallet.get_balance()?;
987-
if cur_balance.get_spendable() < channel_amount_sats {
988-
log_error!(self.logger, "Unable to create channel due to insufficient funds.");
989-
return Err(Error::InsufficientFunds);
990-
}
991-
992994
let peer_info = PeerInfo { node_id, address };
993995

994996
let con_node_id = peer_info.node_id;
995997
let con_addr = peer_info.address.clone();
996998
let con_cm = Arc::clone(&self.connection_manager);
997999

1000+
let cur_anchor_reserve_sats =
1001+
total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
1002+
let spendable_amount_sats =
1003+
self.wallet.get_balances(cur_anchor_reserve_sats).map(|(_, s)| s).unwrap_or(0);
1004+
1005+
// Fail early if we have less than the channel value available.
1006+
if spendable_amount_sats < channel_amount_sats {
1007+
log_error!(self.logger,
1008+
"Unable to create channel due to insufficient funds. Available: {}sats, Required: {}sats",
1009+
spendable_amount_sats, channel_amount_sats
1010+
);
1011+
return Err(Error::InsufficientFunds);
1012+
}
1013+
9981014
// We need to use our main runtime here as a local runtime might not be around to poll
9991015
// connection futures going forward.
10001016
tokio::task::block_in_place(move || {
@@ -1003,11 +1019,37 @@ impl Node {
10031019
})
10041020
})?;
10051021

1022+
// Fail if we have less than the channel value + anchor reserve available (if applicable).
1023+
let init_features = self
1024+
.peer_manager
1025+
.peer_by_node_id(&node_id)
1026+
.ok_or(Error::ConnectionFailed)?
1027+
.init_features;
1028+
let required_funds_sats = channel_amount_sats
1029+
+ self.config.anchor_channels_config.as_ref().map_or(0, |c| {
1030+
if init_features.requires_anchors_zero_fee_htlc_tx()
1031+
&& !c.trusted_peers_no_reserve.contains(&node_id)
1032+
{
1033+
c.per_channel_reserve_sats
1034+
} else {
1035+
0
1036+
}
1037+
});
1038+
1039+
if spendable_amount_sats < required_funds_sats {
1040+
log_error!(self.logger,
1041+
"Unable to create channel due to insufficient funds. Available: {}sats, Required: {}sats",
1042+
spendable_amount_sats, required_funds_sats
1043+
);
1044+
return Err(Error::InsufficientFunds);
1045+
}
1046+
10061047
let channel_config = (*(channel_config.unwrap_or_default())).clone().into();
10071048
let user_config = UserConfig {
10081049
channel_handshake_limits: Default::default(),
10091050
channel_handshake_config: ChannelHandshakeConfig {
10101051
announced_channel: announce_channel,
1052+
negotiate_anchors_zero_fee_htlc_tx: self.config.anchor_channels_config.is_some(),
10111053
..Default::default()
10121054
},
10131055
channel_config,
@@ -1166,11 +1208,13 @@ impl Node {
11661208

11671209
/// Retrieves an overview of all known balances.
11681210
pub fn list_balances(&self) -> BalanceDetails {
1169-
let (total_onchain_balance_sats, spendable_onchain_balance_sats) = self
1170-
.wallet
1171-
.get_balance()
1172-
.map(|bal| (bal.get_total(), bal.get_spendable()))
1173-
.unwrap_or((0, 0));
1211+
let cur_anchor_reserve_sats =
1212+
total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
1213+
let (total_onchain_balance_sats, spendable_onchain_balance_sats) =
1214+
self.wallet.get_balances(cur_anchor_reserve_sats).unwrap_or((0, 0));
1215+
1216+
let total_anchor_channels_reserve_sats =
1217+
std::cmp::min(cur_anchor_reserve_sats, total_onchain_balance_sats);
11741218

11751219
let mut total_lightning_balance_sats = 0;
11761220
let mut lightning_balances = Vec::new();
@@ -1203,6 +1247,7 @@ impl Node {
12031247
BalanceDetails {
12041248
total_onchain_balance_sats,
12051249
spendable_onchain_balance_sats,
1250+
total_anchor_channels_reserve_sats,
12061251
total_lightning_balance_sats,
12071252
lightning_balances,
12081253
pending_balances_from_channel_closures,
@@ -1335,3 +1380,23 @@ pub struct NodeStatus {
13351380
/// Will be `None` if we have no public channels or we haven't broadcasted since the [`Node`] was initialized.
13361381
pub latest_node_announcement_broadcast_timestamp: Option<u64>,
13371382
}
1383+
1384+
pub(crate) fn total_anchor_channels_reserve_sats(
1385+
channel_manager: &ChannelManager, config: &Config,
1386+
) -> u64 {
1387+
config.anchor_channels_config.as_ref().map_or(0, |anchor_channels_config| {
1388+
channel_manager
1389+
.list_channels()
1390+
.into_iter()
1391+
.filter(|c| {
1392+
!anchor_channels_config.trusted_peers_no_reserve.contains(&c.counterparty.node_id)
1393+
&& c.channel_shutdown_state
1394+
.map_or(true, |s| s != ChannelShutdownState::ShutdownComplete)
1395+
&& c.channel_type
1396+
.as_ref()
1397+
.map_or(false, |t| t.requires_anchors_zero_fee_htlc_tx())
1398+
})
1399+
.count() as u64
1400+
* anchor_channels_config.per_channel_reserve_sats
1401+
})
1402+
}

src/payment/onchain.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Holds a payment handler allowing to send and receive on-chain payments.
22
3+
use crate::config::Config;
34
use crate::error::Error;
45
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
5-
use crate::types::Wallet;
6+
use crate::types::{ChannelManager, Wallet};
67

78
use bitcoin::{Address, Txid};
89

@@ -16,15 +17,17 @@ use std::sync::{Arc, RwLock};
1617
pub struct OnchainPayment {
1718
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
1819
wallet: Arc<Wallet>,
20+
channel_manager: Arc<ChannelManager>,
21+
config: Arc<Config>,
1922
logger: Arc<FilesystemLogger>,
2023
}
2124

2225
impl OnchainPayment {
2326
pub(crate) fn new(
2427
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, wallet: Arc<Wallet>,
25-
logger: Arc<FilesystemLogger>,
28+
channel_manager: Arc<ChannelManager>, config: Arc<Config>, logger: Arc<FilesystemLogger>,
2629
) -> Self {
27-
Self { runtime, wallet, logger }
30+
Self { runtime, wallet, channel_manager, config, logger }
2831
}
2932

3033
/// Retrieve a new on-chain/funding address.
@@ -35,6 +38,11 @@ impl OnchainPayment {
3538
}
3639

3740
/// Send an on-chain payment to the given address.
41+
///
42+
/// This will respect any on-chain reserve we need to keep, i.e., won't allow to cut into
43+
/// [`BalanceDetails::total_anchor_channels_reserve_sats`].
44+
///
45+
/// [`BalanceDetails::total_anchor_channels_reserve_sats`]: crate::BalanceDetails::total_anchor_channels_reserve_sats
3846
pub fn send_to_address(
3947
&self, address: &bitcoin::Address, amount_sats: u64,
4048
) -> Result<Txid, Error> {
@@ -43,15 +51,29 @@ impl OnchainPayment {
4351
return Err(Error::NotRunning);
4452
}
4553

46-
let cur_balance = self.wallet.get_balance()?;
47-
if cur_balance.get_spendable() < amount_sats {
48-
log_error!(self.logger, "Unable to send payment due to insufficient funds.");
54+
let cur_anchor_reserve_sats =
55+
crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
56+
let spendable_amount_sats =
57+
self.wallet.get_balances(cur_anchor_reserve_sats).map(|(_, s)| s).unwrap_or(0);
58+
59+
if spendable_amount_sats < amount_sats {
60+
log_error!(self.logger,
61+
"Unable to send payment due to insufficient funds. Available: {}sats, Required: {}sats",
62+
spendable_amount_sats, amount_sats
63+
);
4964
return Err(Error::InsufficientFunds);
5065
}
5166
self.wallet.send_to_address(address, Some(amount_sats))
5267
}
5368

5469
/// Send an on-chain payment to the given address, draining all the available funds.
70+
///
71+
/// This is useful if you have closed all channels and want to migrate funds to another
72+
/// on-chain wallet.
73+
///
74+
/// Please note that this will **not** retain any on-chain reserves, which might be potentially
75+
/// dangerous if you have open Anchor channels for which you can't trust the counterparty to
76+
/// spend the Anchor output after channel closure.
5577
pub fn send_all_to_address(&self, address: &bitcoin::Address) -> Result<Txid, Error> {
5678
let rt_lock = self.runtime.read().unwrap();
5779
if rt_lock.is_none() {

src/wallet.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,17 @@ where
169169
Ok(address_info.address)
170170
}
171171

172-
pub(crate) fn get_balance(&self) -> Result<bdk::Balance, Error> {
173-
Ok(self.inner.lock().unwrap().get_balance()?)
172+
pub(crate) fn get_balances(
173+
&self, total_anchor_channels_reserve_sats: u64,
174+
) -> Result<(u64, u64), Error> {
175+
let wallet_lock = self.inner.lock().unwrap();
176+
let (total, spendable) = wallet_lock.get_balance().map(|bal| {
177+
(
178+
bal.get_total(),
179+
bal.get_spendable().saturating_sub(total_anchor_channels_reserve_sats),
180+
)
181+
})?;
182+
Ok((total, spendable))
174183
}
175184

176185
/// Send funds to the given address.

0 commit comments

Comments
 (0)