Skip to content

Commit 2562f52

Browse files
authored
Merge pull request #141 from tnull/2023-07-add-anchor-support
Add anchor support
2 parents 1eab306 + 436f2e4 commit 2562f52

File tree

15 files changed

+767
-97
lines changed

15 files changed

+767
-97
lines changed

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

+3-3
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

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ dictionary Config {
1515
sequence<PublicKey> trusted_peers_0conf;
1616
u64 probing_liquidity_limit_multiplier;
1717
LogLevel log_level;
18+
AnchorChannelsConfig? anchor_channels_config;
19+
};
20+
21+
dictionary AnchorChannelsConfig {
22+
sequence<PublicKey> trusted_peers_no_reserve;
23+
u64 per_channel_reserve_sats;
1824
};
1925

2026
interface Builder {
@@ -66,6 +72,8 @@ interface Node {
6672
[Throws=NodeError]
6773
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
6874
[Throws=NodeError]
75+
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
76+
[Throws=NodeError]
6977
void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config);
7078
[Throws=NodeError]
7179
void sync_wallets();
@@ -348,6 +356,7 @@ interface PendingSweepBalance {
348356
dictionary BalanceDetails {
349357
u64 total_onchain_balance_sats;
350358
u64 spendable_onchain_balance_sats;
359+
u64 total_anchor_channels_reserve_sats;
351360
u64 total_lightning_balance_sats;
352361
sequence<LightningBalance> lightning_balances;
353362
sequence<PendingSweepBalance> pending_balances_from_channel_closures;

docker-compose-cln.yml

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ services:
6363
"--bitcoin-rpcuser=user",
6464
"--bitcoin-rpcpassword=pass",
6565
"--regtest",
66+
"--experimental-anchors",
6667
]
6768
ports:
6869
- "19846:19846"

src/balance.rs

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

src/builder.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -693,12 +693,11 @@ fn build_with_store_internal(
693693
// for inbound channels.
694694
let mut user_config = UserConfig::default();
695695
user_config.channel_handshake_limits.force_announced_channel_preference = false;
696-
697-
if !config.trusted_peers_0conf.is_empty() {
698-
// Manually accept inbound channels if we expect 0conf channel requests, avoid
699-
// generating the events otherwise.
700-
user_config.manually_accept_inbound_channels = true;
701-
}
696+
user_config.manually_accept_inbound_channels = true;
697+
// Note the channel_handshake_config will be overwritten in `connect_open_channel`, but we
698+
// still set a default here.
699+
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
700+
config.anchor_channels_config.is_some();
702701

703702
if liquidity_source_config.and_then(|lsc| lsc.lsps2_service.as_ref()).is_some() {
704703
// Generally allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll

src/config.rs

+93
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30;
1515
const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10;
1616
const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3;
1717
const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Debug;
18+
const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000;
1819

1920
// The 'stop gap' parameter used by BDK's wallet sync. This seems to configure the threshold
2021
// number of derivation indexes after which BDK stops looking for new scripts belonging to the wallet.
@@ -62,6 +63,9 @@ pub(crate) const WALLET_KEYS_SEED_LEN: usize = 64;
6263
/// | `trusted_peers_0conf` | [] |
6364
/// | `probing_liquidity_limit_multiplier` | 3 |
6465
/// | `log_level` | Debug |
66+
/// | `anchor_channels_config` | Some(..) |
67+
///
68+
/// See [`AnchorChannelsConfig`] for more information on its respective default values.
6569
///
6670
/// [`Node`]: crate::Node
6771
pub struct Config {
@@ -104,6 +108,23 @@ pub struct Config {
104108
///
105109
/// Any messages below this level will be excluded from the logs.
106110
pub log_level: LogLevel,
111+
/// Configuration options pertaining to Anchor channels, i.e., channels for which the
112+
/// `option_anchors_zero_fee_htlc_tx` channel type is negotiated.
113+
///
114+
/// Please refer to [`AnchorChannelsConfig`] for further information on Anchor channels.
115+
///
116+
/// If set to `Some`, we'll try to open new channels with Anchors enabled, i.e., new channels
117+
/// will be negotiated with the `option_anchors_zero_fee_htlc_tx` channel type if supported by
118+
/// the counterparty. Note that this won't prevent us from opening non-Anchor channels if the
119+
/// counterparty doesn't support `option_anchors_zero_fee_htlc_tx`. If set to `None`, new
120+
/// channels will be negotiated with the legacy `option_static_remotekey` channel type only.
121+
///
122+
/// **Note:** If set to `None` *after* some Anchor channels have already been
123+
/// opened, no dedicated emergency on-chain reserve will be maintained for these channels,
124+
/// which can be dangerous if only insufficient funds are available at the time of channel
125+
/// closure. We *will* however still try to get the Anchor spending transactions confirmed
126+
/// on-chain with the funds available.
127+
pub anchor_channels_config: Option<AnchorChannelsConfig>,
107128
}
108129

109130
impl Default for Config {
@@ -120,6 +141,78 @@ impl Default for Config {
120141
trusted_peers_0conf: Vec::new(),
121142
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
122143
log_level: DEFAULT_LOG_LEVEL,
144+
anchor_channels_config: Some(AnchorChannelsConfig::default()),
145+
}
146+
}
147+
}
148+
149+
/// Configuration options pertaining to 'Anchor' channels, i.e., channels for which the
150+
/// `option_anchors_zero_fee_htlc_tx` channel type is negotiated.
151+
///
152+
/// Prior to the introduction of Anchor channels, the on-chain fees paying for the transactions
153+
/// issued on channel closure were pre-determined and locked-in at the time of the channel
154+
/// opening. This required to estimate what fee rate would be sufficient to still have the
155+
/// closing transactions be spendable on-chain (i.e., not be considered dust). This legacy
156+
/// design of pre-anchor channels proved inadequate in the unpredictable, often turbulent, fee
157+
/// markets we experience today.
158+
///
159+
/// In contrast, Anchor channels allow to determine an adequate fee rate *at the time of channel
160+
/// closure*, making them much more robust in the face of fee spikes. In turn, they require to
161+
/// maintain a reserve of on-chain funds to have the channel closure transactions confirmed
162+
/// on-chain, at least if the channel counterparty can't be trusted to do this for us.
163+
///
164+
/// See [BOLT 3] for more technical details on Anchor channels.
165+
///
166+
///
167+
/// ### Defaults
168+
///
169+
/// | Parameter | Value |
170+
/// |----------------------------|--------|
171+
/// | `trusted_peers_no_reserve` | [] |
172+
/// | `per_channel_reserve_sats` | 25000 |
173+
///
174+
///
175+
/// [BOLT 3]: https://github.com/lightning/bolts/blob/master/03-transactions.md#htlc-timeout-and-htlc-success-transactions
176+
#[derive(Debug, Clone)]
177+
pub struct AnchorChannelsConfig {
178+
/// A list of peers that we trust to get the required channel closing transactions confirmed
179+
/// on-chain.
180+
///
181+
/// Channels with these peers won't count towards the retained on-chain reserve and we won't
182+
/// take any action to get the required transactions confirmed ourselves.
183+
///
184+
/// **Note:** Trusting the channel counterparty to take the necessary actions to get the
185+
/// required Anchor spending and HTLC transactions confirmed on-chain is potentially insecure
186+
/// as the channel may not be closed if they refuse to do so, potentially leaving the user
187+
/// funds stuck *or* even allow the counterparty to steal any in-flight funds after the
188+
/// corresponding HTLCs time out.
189+
pub trusted_peers_no_reserve: Vec<PublicKey>,
190+
/// The amount of satoshis per anchors-negotiated channel with an untrusted peer that we keep
191+
/// as an emergency reserve in our on-chain wallet.
192+
///
193+
/// This allows for having the required Anchor output spending and HTLC transactions confirmed
194+
/// when the channel is closed.
195+
///
196+
/// If the channel peer is not marked as trusted via
197+
/// [`AnchorChannelsConfig::trusted_peers_no_reserve`], we will always try to spend the Anchor
198+
/// outputs with *any* on-chain funds available, i.e., the total reserve value as well as any
199+
/// spendable funds available in the on-chain wallet. Therefore, this per-channel multiplier is
200+
/// really a emergencey reserve that we maintain at all time to reduce reduce the risk of
201+
/// insufficient funds at time of a channel closure. To this end, we will refuse to open
202+
/// outbound or accept inbound channels if we don't have sufficient on-chain funds availble to
203+
/// cover the additional reserve requirement.
204+
///
205+
/// **Note:** Depending on the fee market at the time of closure, this reserve amount might or
206+
/// might not suffice to successfully spend the Anchor output and have the HTLC transactions
207+
/// confirmed on-chain, i.e., you may want to adjust this value accordingly.
208+
pub per_channel_reserve_sats: u64,
209+
}
210+
211+
impl Default for AnchorChannelsConfig {
212+
fn default() -> Self {
213+
Self {
214+
trusted_peers_no_reserve: Vec::new(),
215+
per_channel_reserve_sats: DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS,
123216
}
124217
}
125218
}

src/event.rs

+104-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::types::{DynStore, Sweeper, Wallet};
22

33
use crate::{
4-
hex_utils, ChannelManager, Config, Error, NetworkGraph, PeerInfo, PeerStore, UserChannelId,
4+
hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, NetworkGraph, PeerInfo,
5+
PeerStore, UserChannelId,
56
};
67

78
use crate::connection::ConnectionManager;
@@ -15,9 +16,10 @@ use crate::io::{
1516
EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
1617
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
1718
};
18-
use crate::logger::{log_error, log_info, Logger};
19+
use crate::logger::{log_debug, log_error, log_info, Logger};
1920

2021
use lightning::chain::chaininterface::ConfirmationTarget;
22+
use lightning::events::bump_transaction::BumpTransactionEvent;
2123
use lightning::events::{ClosureReason, PaymentPurpose};
2224
use lightning::events::{Event as LdkEvent, PaymentFailureReason};
2325
use lightning::impl_writeable_tlv_based_enum;
@@ -317,6 +319,7 @@ where
317319
{
318320
event_queue: Arc<EventQueue<L>>,
319321
wallet: Arc<Wallet>,
322+
bump_tx_event_handler: Arc<BumpTransactionEventHandler>,
320323
channel_manager: Arc<ChannelManager>,
321324
connection_manager: Arc<ConnectionManager<L>>,
322325
output_sweeper: Arc<Sweeper>,
@@ -333,15 +336,17 @@ where
333336
L::Target: Logger,
334337
{
335338
pub fn new(
336-
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>,
337-
connection_manager: Arc<ConnectionManager<L>>, output_sweeper: Arc<Sweeper>,
338-
network_graph: Arc<NetworkGraph>, payment_store: Arc<PaymentStore<L>>,
339-
peer_store: Arc<PeerStore<L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
340-
logger: L, config: Arc<Config>,
339+
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>,
340+
bump_tx_event_handler: Arc<BumpTransactionEventHandler>,
341+
channel_manager: Arc<ChannelManager>, connection_manager: Arc<ConnectionManager<L>>,
342+
output_sweeper: Arc<Sweeper>, network_graph: Arc<NetworkGraph>,
343+
payment_store: Arc<PaymentStore<L>>, peer_store: Arc<PeerStore<L>>,
344+
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
341345
) -> Self {
342346
Self {
343347
event_queue,
344348
wallet,
349+
bump_tx_event_handler,
345350
channel_manager,
346351
connection_manager,
347352
output_sweeper,
@@ -815,9 +820,67 @@ where
815820
temporary_channel_id,
816821
counterparty_node_id,
817822
funding_satoshis,
818-
channel_type: _,
823+
channel_type,
819824
push_msat: _,
820825
} => {
826+
let anchor_channel = channel_type.requires_anchors_zero_fee_htlc_tx();
827+
828+
if anchor_channel {
829+
if let Some(anchor_channels_config) =
830+
self.config.anchor_channels_config.as_ref()
831+
{
832+
let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(
833+
&self.channel_manager,
834+
&self.config,
835+
);
836+
let spendable_amount_sats = self
837+
.wallet
838+
.get_spendable_amount_sats(cur_anchor_reserve_sats)
839+
.unwrap_or(0);
840+
841+
let required_amount_sats = if anchor_channels_config
842+
.trusted_peers_no_reserve
843+
.contains(&counterparty_node_id)
844+
{
845+
0
846+
} else {
847+
anchor_channels_config.per_channel_reserve_sats
848+
};
849+
850+
if spendable_amount_sats < required_amount_sats {
851+
log_error!(
852+
self.logger,
853+
"Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves.",
854+
counterparty_node_id,
855+
);
856+
self.channel_manager
857+
.force_close_without_broadcasting_txn(
858+
&temporary_channel_id,
859+
&counterparty_node_id,
860+
)
861+
.unwrap_or_else(|e| {
862+
log_error!(self.logger, "Failed to reject channel: {:?}", e)
863+
});
864+
return;
865+
}
866+
} else {
867+
log_error!(
868+
self.logger,
869+
"Rejecting inbound channel from peer {} due to Anchor channels being disabled.",
870+
counterparty_node_id,
871+
);
872+
self.channel_manager
873+
.force_close_without_broadcasting_txn(
874+
&temporary_channel_id,
875+
&counterparty_node_id,
876+
)
877+
.unwrap_or_else(|e| {
878+
log_error!(self.logger, "Failed to reject channel: {:?}", e)
879+
});
880+
return;
881+
}
882+
}
883+
821884
let user_channel_id: u128 = rand::thread_rng().gen::<u128>();
822885
let allow_0conf = self.config.trusted_peers_0conf.contains(&counterparty_node_id);
823886
let res = if allow_0conf {
@@ -838,8 +901,9 @@ where
838901
Ok(()) => {
839902
log_info!(
840903
self.logger,
841-
"Accepting inbound{} channel of {}sats from{} peer {}",
904+
"Accepting inbound{}{} channel of {}sats from{} peer {}",
842905
if allow_0conf { " 0conf" } else { "" },
906+
if anchor_channel { " Anchor" } else { "" },
843907
funding_satoshis,
844908
if allow_0conf { " trusted" } else { "" },
845909
counterparty_node_id,
@@ -848,8 +912,9 @@ where
848912
Err(e) => {
849913
log_error!(
850914
self.logger,
851-
"Error while accepting inbound{} channel from{} peer {}: {:?}",
915+
"Error while accepting inbound{}{} channel from{} peer {}: {:?}",
852916
if allow_0conf { " 0conf" } else { "" },
917+
if anchor_channel { " Anchor" } else { "" },
853918
counterparty_node_id,
854919
if allow_0conf { " trusted" } else { "" },
855920
e,
@@ -1018,7 +1083,6 @@ where
10181083
},
10191084
LdkEvent::DiscardFunding { .. } => {},
10201085
LdkEvent::HTLCIntercepted { .. } => {},
1021-
LdkEvent::BumpTransaction(_) => {},
10221086
LdkEvent::InvoiceRequestFailed { payment_id } => {
10231087
log_error!(
10241088
self.logger,
@@ -1062,6 +1126,35 @@ where
10621126
});
10631127
}
10641128
},
1129+
LdkEvent::BumpTransaction(bte) => {
1130+
let (channel_id, counterparty_node_id) = match bte {
1131+
BumpTransactionEvent::ChannelClose {
1132+
ref channel_id,
1133+
ref counterparty_node_id,
1134+
..
1135+
} => (channel_id, counterparty_node_id),
1136+
BumpTransactionEvent::HTLCResolution {
1137+
ref channel_id,
1138+
ref counterparty_node_id,
1139+
..
1140+
} => (channel_id, counterparty_node_id),
1141+
};
1142+
1143+
if let Some(anchor_channels_config) = self.config.anchor_channels_config.as_ref() {
1144+
if anchor_channels_config
1145+
.trusted_peers_no_reserve
1146+
.contains(counterparty_node_id)
1147+
{
1148+
log_debug!(self.logger,
1149+
"Ignoring BumpTransactionEvent for channel {} due to trusted counterparty {}",
1150+
channel_id, counterparty_node_id
1151+
);
1152+
return;
1153+
}
1154+
}
1155+
1156+
self.bump_tx_event_handler.handle_event(&bte);
1157+
},
10651158
}
10661159
}
10671160
}

0 commit comments

Comments
 (0)