Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 161
Add anchor support#141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Add anchor support #141
Changes from all commits
a2691e7db1b37319528025ba1b351887af80aaa8f1436f2e4File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -15,6 +15,7 @@ const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30; | ||
| const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10; | ||
| const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3; | ||
| const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Debug; | ||
| const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000; | ||
| // The 'stop gap' parameter used by BDK's wallet sync. This seems to configure the threshold | ||
| // 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; | ||
| /// | `trusted_peers_0conf` | [] | | ||
| /// | `probing_liquidity_limit_multiplier` | 3 | | ||
| /// | `log_level` | Debug | | ||
| /// | `anchor_channels_config` | Some(..) | | ||
| /// | ||
| /// See [`AnchorChannelsConfig`] for more information on its respective default values. | ||
| /// | ||
| /// [`Node`]: crate::Node | ||
| pub struct Config { | ||
| @@ -104,6 +108,23 @@ pub struct Config { | ||
| /// | ||
| /// Any messages below this level will be excluded from the logs. | ||
| pub log_level: LogLevel, | ||
| /// Configuration options pertaining to Anchor channels, i.e., channels for which the | ||
| /// `option_anchors_zero_fee_htlc_tx` channel type is negotiated. | ||
| /// | ||
| /// Please refer to [`AnchorChannelsConfig`] for further information on Anchor channels. | ||
| /// | ||
| /// If set to `Some`, we'll try to open new channels with Anchors enabled, i.e., new channels | ||
| /// will be negotiated with the `option_anchors_zero_fee_htlc_tx` channel type if supported by | ||
| /// the counterparty. Note that this won't prevent us from opening non-Anchor channels if the | ||
| /// counterparty doesn't support `option_anchors_zero_fee_htlc_tx`. If set to `None`, new | ||
| /// channels will be negotiated with the legacy `option_static_remotekey` channel type only. | ||
| /// | ||
| /// **Note:** If set to `None` *after* some Anchor channels have already been | ||
| /// opened, no dedicated emergency on-chain reserve will be maintained for these channels, | ||
| /// which can be dangerous if only insufficient funds are available at the time of channel | ||
| /// closure. We *will* however still try to get the Anchor spending transactions confirmed | ||
| /// on-chain with the funds available. | ||
| pub anchor_channels_config: Option<AnchorChannelsConfig>, | ||
| } | ||
| impl Default for Config { | ||
| @@ -120,6 +141,78 @@ impl Default for Config { | ||
| trusted_peers_0conf: Vec::new(), | ||
| probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, | ||
| log_level: DEFAULT_LOG_LEVEL, | ||
| anchor_channels_config: Some(AnchorChannelsConfig::default()), | ||
| } | ||
| } | ||
| } | ||
| /// Configuration options pertaining to 'Anchor' channels, i.e., channels for which the | ||
| /// `option_anchors_zero_fee_htlc_tx` channel type is negotiated. | ||
| /// | ||
| /// Prior to the introduction of Anchor channels, the on-chain fees paying for the transactions | ||
| /// issued on channel closure were pre-determined and locked-in at the time of the channel | ||
| /// opening. This required to estimate what fee rate would be sufficient to still have the | ||
| /// closing transactions be spendable on-chain (i.e., not be considered dust). This legacy | ||
| /// design of pre-anchor channels proved inadequate in the unpredictable, often turbulent, fee | ||
| /// markets we experience today. | ||
| /// | ||
| /// In contrast, Anchor channels allow to determine an adequate fee rate *at the time of channel | ||
| /// closure*, making them much more robust in the face of fee spikes. In turn, they require to | ||
| /// maintain a reserve of on-chain funds to have the channel closure transactions confirmed | ||
| /// on-chain, at least if the channel counterparty can't be trusted to do this for us. | ||
| /// | ||
| /// See [BOLT 3] for more technical details on Anchor channels. | ||
| /// | ||
| /// | ||
| /// ### Defaults | ||
| /// | ||
| /// | Parameter | Value | | ||
| /// |----------------------------|--------| | ||
| /// | `trusted_peers_no_reserve` | [] | | ||
| /// | `per_channel_reserve_sats` | 25000 | | ||
| /// | ||
| /// | ||
| /// [BOLT 3]: https://github.com/lightning/bolts/blob/master/03-transactions.md#htlc-timeout-and-htlc-success-transactions | ||
| #[derive(Debug, Clone)] | ||
| pub struct AnchorChannelsConfig { | ||
tnull marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// A list of peers that we trust to get the required channel closing transactions confirmed | ||
| /// on-chain. | ||
| /// | ||
| /// Channels with these peers won't count towards the retained on-chain reserve and we won't | ||
| /// take any action to get the required transactions confirmed ourselves. | ||
| /// | ||
| /// **Note:** Trusting the channel counterparty to take the necessary actions to get the | ||
| /// required Anchor spending and HTLC transactions confirmed on-chain is potentially insecure | ||
| /// as the channel may not be closed if they refuse to do so, potentially leaving the user | ||
| /// funds stuck *or* even allow the counterparty to steal any in-flight funds after the | ||
| /// corresponding HTLCs time out. | ||
| pub trusted_peers_no_reserve: Vec<PublicKey>, | ||
tnull marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// The amount of satoshis per anchors-negotiated channel with an untrusted peer that we keep | ||
| /// as an emergency reserve in our on-chain wallet. | ||
| /// | ||
| /// This allows for having the required Anchor output spending and HTLC transactions confirmed | ||
| /// when the channel is closed. | ||
| /// | ||
| /// If the channel peer is not marked as trusted via | ||
| /// [`AnchorChannelsConfig::trusted_peers_no_reserve`], we will always try to spend the Anchor | ||
| /// outputs with *any* on-chain funds available, i.e., the total reserve value as well as any | ||
| /// spendable funds available in the on-chain wallet. Therefore, this per-channel multiplier is | ||
| /// really a emergencey reserve that we maintain at all time to reduce reduce the risk of | ||
| /// insufficient funds at time of a channel closure. To this end, we will refuse to open | ||
| /// outbound or accept inbound channels if we don't have sufficient on-chain funds availble to | ||
| /// cover the additional reserve requirement. | ||
| /// | ||
| /// **Note:** Depending on the fee market at the time of closure, this reserve amount might or | ||
| /// might not suffice to successfully spend the Anchor output and have the HTLC transactions | ||
| /// confirmed on-chain, i.e., you may want to adjust this value accordingly. | ||
| pub per_channel_reserve_sats: u64, | ||
tnull marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| impl Default for AnchorChannelsConfig { | ||
| fn default() -> Self { | ||
| Self { | ||
| trusted_peers_no_reserve: Vec::new(), | ||
| per_channel_reserve_sats: DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS, | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| use crate::types::{DynStore, Sweeper, Wallet}; | ||
| use crate::{ | ||
| hex_utils, ChannelManager, Config, Error, NetworkGraph, PeerInfo, PeerStore, UserChannelId, | ||
| hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, NetworkGraph, PeerInfo, | ||
| PeerStore, UserChannelId, | ||
| }; | ||
| use crate::connection::ConnectionManager; | ||
| @@ -15,9 +16,10 @@ use crate::io::{ | ||
| EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE, | ||
| EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE, | ||
| }; | ||
| use crate::logger::{log_error, log_info, Logger}; | ||
| use crate::logger::{log_debug, log_error, log_info, Logger}; | ||
| use lightning::chain::chaininterface::ConfirmationTarget; | ||
| use lightning::events::bump_transaction::BumpTransactionEvent; | ||
| use lightning::events::{ClosureReason, PaymentPurpose}; | ||
| use lightning::events::{Event as LdkEvent, PaymentFailureReason}; | ||
| use lightning::impl_writeable_tlv_based_enum; | ||
| @@ -317,6 +319,7 @@ where | ||
| { | ||
| event_queue: Arc<EventQueue<L>>, | ||
| wallet: Arc<Wallet>, | ||
| bump_tx_event_handler: Arc<BumpTransactionEventHandler>, | ||
| channel_manager: Arc<ChannelManager>, | ||
| connection_manager: Arc<ConnectionManager<L>>, | ||
| output_sweeper: Arc<Sweeper>, | ||
| @@ -333,15 +336,17 @@ where | ||
| L::Target: Logger, | ||
| { | ||
| pub fn new( | ||
| event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>, | ||
| connection_manager: Arc<ConnectionManager<L>>, output_sweeper: Arc<Sweeper>, | ||
| network_graph: Arc<NetworkGraph>, payment_store: Arc<PaymentStore<L>>, | ||
| peer_store: Arc<PeerStore<L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, | ||
| logger: L, config: Arc<Config>, | ||
| event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>, | ||
| bump_tx_event_handler: Arc<BumpTransactionEventHandler>, | ||
| channel_manager: Arc<ChannelManager>, connection_manager: Arc<ConnectionManager<L>>, | ||
| output_sweeper: Arc<Sweeper>, network_graph: Arc<NetworkGraph>, | ||
| payment_store: Arc<PaymentStore<L>>, peer_store: Arc<PeerStore<L>>, | ||
| runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>, | ||
| ) -> Self { | ||
| Self { | ||
| event_queue, | ||
| wallet, | ||
| bump_tx_event_handler, | ||
| channel_manager, | ||
| connection_manager, | ||
| output_sweeper, | ||
| @@ -815,9 +820,67 @@ where | ||
| temporary_channel_id, | ||
| counterparty_node_id, | ||
| funding_satoshis, | ||
| channel_type: _, | ||
| channel_type, | ||
| push_msat: _, | ||
| } => { | ||
| let anchor_channel = channel_type.requires_anchors_zero_fee_htlc_tx(); | ||
jkczyz marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if anchor_channel { | ||
| if let Some(anchor_channels_config) = | ||
| self.config.anchor_channels_config.as_ref() | ||
| { | ||
| let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats( | ||
| &self.channel_manager, | ||
| &self.config, | ||
| ); | ||
| let spendable_amount_sats = self | ||
| .wallet | ||
| .get_spendable_amount_sats(cur_anchor_reserve_sats) | ||
| .unwrap_or(0); | ||
| let required_amount_sats = if anchor_channels_config | ||
| .trusted_peers_no_reserve | ||
| .contains(&counterparty_node_id) | ||
| { | ||
| 0 | ||
| } else { | ||
| anchor_channels_config.per_channel_reserve_sats | ||
| }; | ||
| if spendable_amount_sats < required_amount_sats { | ||
| log_error!( | ||
| self.logger, | ||
| "Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves.", | ||
| counterparty_node_id, | ||
| ); | ||
| self.channel_manager | ||
| .force_close_without_broadcasting_txn( | ||
| &temporary_channel_id, | ||
| &counterparty_node_id, | ||
| ) | ||
| .unwrap_or_else(|e| { | ||
| log_error!(self.logger, "Failed to reject channel: {:?}", e) | ||
| }); | ||
| return; | ||
| } | ||
| } else { | ||
| log_error!( | ||
| self.logger, | ||
| "Rejecting inbound channel from peer {} due to Anchor channels being disabled.", | ||
| counterparty_node_id, | ||
| ); | ||
| self.channel_manager | ||
| .force_close_without_broadcasting_txn( | ||
| &temporary_channel_id, | ||
| &counterparty_node_id, | ||
| ) | ||
| .unwrap_or_else(|e| { | ||
| log_error!(self.logger, "Failed to reject channel: {:?}", e) | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| let user_channel_id: u128 = rand::thread_rng().gen::<u128>(); | ||
| let allow_0conf = self.config.trusted_peers_0conf.contains(&counterparty_node_id); | ||
| let res = if allow_0conf { | ||
| @@ -838,8 +901,9 @@ where | ||
| Ok(()) => { | ||
| log_info!( | ||
| self.logger, | ||
| "Accepting inbound{} channel of {}sats from{} peer {}", | ||
| "Accepting inbound{}{} channel of {}sats from{} peer {}", | ||
| if allow_0conf { " 0conf" } else { "" }, | ||
| if anchor_channel { " Anchor" } else { "" }, | ||
| funding_satoshis, | ||
| if allow_0conf { " trusted" } else { "" }, | ||
| counterparty_node_id, | ||
| @@ -848,8 +912,9 @@ where | ||
| Err(e) => { | ||
| log_error!( | ||
| self.logger, | ||
| "Error while accepting inbound{} channel from{} peer {}: {:?}", | ||
| "Error while accepting inbound{}{} channel from{} peer {}: {:?}", | ||
| if allow_0conf { " 0conf" } else { "" }, | ||
| if anchor_channel { " Anchor" } else { "" }, | ||
| counterparty_node_id, | ||
| if allow_0conf { " trusted" } else { "" }, | ||
| e, | ||
| @@ -1018,7 +1083,6 @@ where | ||
| }, | ||
| LdkEvent::DiscardFunding { .. } => {}, | ||
| LdkEvent::HTLCIntercepted { .. } => {}, | ||
| LdkEvent::BumpTransaction(_) => {}, | ||
| LdkEvent::InvoiceRequestFailed { payment_id } => { | ||
| log_error!( | ||
| self.logger, | ||
| @@ -1062,6 +1126,35 @@ where | ||
| }); | ||
| } | ||
| }, | ||
| LdkEvent::BumpTransaction(bte) => { | ||
| let (channel_id, counterparty_node_id) = match bte { | ||
| BumpTransactionEvent::ChannelClose { | ||
| ref channel_id, | ||
| ref counterparty_node_id, | ||
| .. | ||
| } => (channel_id, counterparty_node_id), | ||
| BumpTransactionEvent::HTLCResolution { | ||
| ref channel_id, | ||
| ref counterparty_node_id, | ||
| .. | ||
| } => (channel_id, counterparty_node_id), | ||
| }; | ||
| if let Some(anchor_channels_config) = self.config.anchor_channels_config.as_ref() { | ||
| if anchor_channels_config | ||
| .trusted_peers_no_reserve | ||
| .contains(counterparty_node_id) | ||
| { | ||
| log_debug!(self.logger, | ||
| "Ignoring BumpTransactionEvent for channel {} due to trusted counterparty {}", | ||
| channel_id, counterparty_node_id | ||
| ); | ||
| return; | ||
| } | ||
| } | ||
| self.bump_tx_event_handler.handle_event(&bte); | ||
| }, | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider not starting up if anchor channels are opened and this is not set? If they wish to have this not set going forward, they'd have to wait for them to be closed and fully swept.