From 892f8fe843434bdc782442631626148b845b02e2 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 31 Aug 2022 11:39:47 -0700 Subject: [PATCH] Yield channel close bump events --- lightning/src/chain/channelmonitor.rs | 93 +++++++++++++++++++++---- lightning/src/chain/onchaintx.rs | 9 +++ lightning/src/ln/chan_utils.rs | 2 +- lightning/src/util/events.rs | 97 ++++++++++++++++++++++++++- 4 files changed, 187 insertions(+), 14 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 920cbd060..7c6b48d14 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -21,8 +21,7 @@ //! ChannelMonitors to get out of the HSM and onto monitoring devices. use bitcoin::blockdata::block::BlockHeader; -use bitcoin::blockdata::transaction::{TxOut,Transaction}; -use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint; +use bitcoin::blockdata::transaction::{OutPoint as BitcoinOutPoint, TxOut, Transaction}; use bitcoin::blockdata::script::{Script, Builder}; use bitcoin::blockdata::opcodes; @@ -44,6 +43,8 @@ use chain::{BestBlock, WatchedOutput}; use chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator}; use chain::transaction::{OutPoint, TransactionData}; use chain::keysinterface::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, Sign, KeysInterface}; +#[cfg(anchors)] +use chain::onchaintx::ClaimEvent; use chain::onchaintx::OnchainTxHandler; use chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageSolvingData, PackageTemplate, RevokedOutput, RevokedHTLCOutput}; use chain::Filter; @@ -51,6 +52,8 @@ use util::logger::Logger; use util::ser::{Readable, ReadableArgs, MaybeReadable, Writer, Writeable, U48, OptionDeserWrapper}; use util::byte_utils; use util::events::Event; +#[cfg(anchors)] +use util::events::{AnchorDescriptor, BumpTransactionEvent}; use prelude::*; use core::{cmp, mem}; @@ -263,6 +266,20 @@ impl_writeable_tlv_based!(HolderSignedTx, { (14, htlc_outputs, vec_type) }); +#[cfg(anchors)] +impl HolderSignedTx { + fn non_dust_htlcs(&self) -> Vec { + self.htlc_outputs.iter().filter_map(|(htlc, _, _)| { + if let Some(_) = htlc.transaction_output_index { + Some(htlc.clone()) + } else { + None + } + }) + .collect() + } +} + /// We use this to track static counterparty commitment transaction data and to generate any /// justice or 2nd-stage preimage/timeout transactions. #[derive(PartialEq, Eq)] @@ -1221,7 +1238,7 @@ impl ChannelMonitor { B::Target: BroadcasterInterface, L::Target: Logger, { - self.inner.lock().unwrap().broadcast_latest_holder_commitment_txn(broadcaster, logger) + self.inner.lock().unwrap().broadcast_latest_holder_commitment_txn(broadcaster, logger); } /// Updates a ChannelMonitor on the basis of some new information provided by the Channel @@ -2222,6 +2239,7 @@ impl ChannelMonitorImpl { panic!("Attempted to apply ChannelMonitorUpdates out of order, check the update_id before passing an update to update_monitor!"); } let mut ret = Ok(()); + let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&*fee_estimator); for update in updates.updates.iter() { match update { ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs } => { @@ -2239,7 +2257,6 @@ impl ChannelMonitorImpl { }, ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage } => { log_trace!(logger, "Updating ChannelMonitor with payment preimage"); - let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&*fee_estimator); self.provide_payment_preimage(&PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner()), &payment_preimage, broadcaster, &bounded_fee_estimator, logger) }, ChannelMonitorUpdateStep::CommitmentSecret { idx, secret } => { @@ -2255,6 +2272,25 @@ impl ChannelMonitorImpl { self.lockdown_from_offchain = true; if *should_broadcast { self.broadcast_latest_holder_commitment_txn(broadcaster, logger); + // If the channel supports anchor outputs, we'll need to emit an external + // event to be consumed such that a child transaction is broadcast with a + // high enough feerate for the parent commitment transaction to confirm. + if self.onchain_tx_handler.opt_anchors() { + let funding_output = HolderFundingOutput::build( + self.funding_redeemscript.clone(), self.channel_value_satoshis, + self.onchain_tx_handler.opt_anchors(), + ); + let best_block_height = self.best_block.height(); + let commitment_package = PackageTemplate::build_package( + self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, + PackageSolvingData::HolderFundingOutput(funding_output), + best_block_height, false, best_block_height, + ); + self.onchain_tx_handler.update_claims_view( + &[], vec![commitment_package], best_block_height, best_block_height, + broadcaster, &bounded_fee_estimator, logger, + ); + } } else if !self.holder_tx_signed { log_error!(logger, "WARNING: You have a potentially-unsafe holder commitment transaction available to broadcast"); log_error!(logger, " in channel monitor for channel {}!", log_bytes!(self.funding_info.0.to_channel_id())); @@ -2309,6 +2345,34 @@ impl ChannelMonitorImpl { pub fn get_and_clear_pending_events(&mut self) -> Vec { let mut ret = Vec::new(); mem::swap(&mut ret, &mut self.pending_events); + #[cfg(anchors)] + for claim_event in self.onchain_tx_handler.get_and_clear_pending_claim_events().drain(..) { + match claim_event { + ClaimEvent::BumpCommitment { + package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_output_idx, + } => { + let commitment_txid = commitment_tx.txid(); + debug_assert_eq!(self.current_holder_commitment_tx.txid, commitment_txid); + let pending_htlcs = self.current_holder_commitment_tx.non_dust_htlcs(); + let commitment_tx_fee_satoshis = self.channel_value_satoshis - + commitment_tx.output.iter().fold(0u64, |sum, output| sum + output.value); + ret.push(Event::BumpTransaction(BumpTransactionEvent::ChannelClose { + package_target_feerate_sat_per_1000_weight, + commitment_tx, + commitment_tx_fee_satoshis, + anchor_descriptor: AnchorDescriptor { + channel_keys_id: self.channel_keys_id, + channel_value_satoshis: self.channel_value_satoshis, + outpoint: BitcoinOutPoint { + txid: commitment_txid, + vout: anchor_output_idx, + }, + }, + pending_htlcs, + })); + }, + } + } ret } @@ -2890,15 +2954,20 @@ impl ChannelMonitorImpl { self.pending_monitor_events.push(MonitorEvent::CommitmentTxConfirmed(self.funding_info.0)); let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript); self.holder_tx_signed = true; - // Because we're broadcasting a commitment transaction, we should construct the package - // assuming it gets confirmed in the next block. Sadly, we have code which considers - // "not yet confirmed" things as discardable, so we cannot do that here. - let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height()); - let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx); - if !new_outputs.is_empty() { - watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs)); + // We can't broadcast our HTLC transactions while the commitment transaction is + // unconfirmed. We'll delay doing so until we detect the confirmed commitment in + // `transactions_confirmed`. + if !self.onchain_tx_handler.opt_anchors() { + // Because we're broadcasting a commitment transaction, we should construct the package + // assuming it gets confirmed in the next block. Sadly, we have code which considers + // "not yet confirmed" things as discardable, so we cannot do that here. + let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height()); + let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx); + if !new_outputs.is_empty() { + watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs)); + } + claimable_outpoints.append(&mut new_outpoints); } - claimable_outpoints.append(&mut new_outpoints); } // Find which on-chain events have reached their confirmation threshold. diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index c8874b7ac..875f4d896 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -44,6 +44,8 @@ use alloc::collections::BTreeMap; use core::cmp; use core::ops::Deref; use core::mem::replace; +#[cfg(anchors)] +use core::mem::swap; use bitcoin::hashes::Hash; const MAX_ALLOC_SIZE: usize = 64*1024; @@ -409,6 +411,13 @@ impl OnchainTxHandler { self.holder_commitment.to_broadcaster_value_sat() } + #[cfg(anchors)] + pub(crate) fn get_and_clear_pending_claim_events(&mut self) -> Vec { + let mut ret = HashMap::new(); + swap(&mut ret, &mut self.pending_claim_events); + ret.into_iter().map(|(_, event)| event).collect::>() + } + /// Lightning security model (i.e being able to redeem/timeout HTLC or penalize counterparty /// onchain) lays on the assumption of claim transactions getting confirmed before timelock /// expiration (CSV or CLTV following cases). In case of high-fee spikes, claim tx may get stuck diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index e1bceb3ce..13b632e7b 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -526,8 +526,8 @@ pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, contest_delay: u1 res } -#[derive(Clone, PartialEq, Eq)] /// Information about an HTLC as it appears in a commitment transaction +#[derive(Clone, Debug, PartialEq, Eq)] pub struct HTLCOutputInCommitment { /// Whether the HTLC was "offered" (ie outbound in relation to this commitment transaction). /// Note that this is not the same as whether it is ountbound *from us*. To determine that you diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index 455263ee2..20f1c5b78 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -15,6 +15,7 @@ //! few other things. use chain::keysinterface::SpendableOutputDescriptor; +use ln::chan_utils::HTLCOutputInCommitment; use ln::channelmanager::PaymentId; use ln::channel::FUNDING_CONF_DEADLINE_BLOCKS; use ln::features::ChannelTypeFeatures; @@ -25,7 +26,7 @@ use routing::gossip::NetworkUpdate; use util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, VecReadWrapper, VecWriteWrapper}; use routing::router::{RouteHop, RouteParameters}; -use bitcoin::{PackedLockTime, Transaction}; +use bitcoin::{PackedLockTime, Transaction, OutPoint}; use bitcoin::blockdata::script::Script; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -196,6 +197,84 @@ impl_writeable_tlv_based_enum_upgradable!(HTLCDestination, } ); +/// A descriptor used to sign for a commitment transaction's anchor output. +#[derive(Clone, Debug)] +pub struct AnchorDescriptor { + /// A unique identifier used along with `channel_value_satoshis` to re-derive the + /// [`InMemorySigner`] required to sign `input`. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + pub channel_keys_id: [u8; 32], + /// The value in satoshis of the channel we're attempting to spend the anchor output of. This is + /// used along with `channel_keys_id` to re-derive the [`InMemorySigner`] required to sign + /// `input`. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + pub channel_value_satoshis: u64, + /// The transaction input's outpoint corresponding to the commitment transaction's anchor + /// output. + pub outpoint: OutPoint, +} + +/// Represents the different types of transactions, originating from LDK, to be bumped. +#[derive(Clone, Debug)] +pub enum BumpTransactionEvent { + /// Indicates that a channel featuring anchor outputs is to be closed by broadcasting the local + /// commitment transaction. Since commitment transactions have a static feerate pre-agreed upon, + /// they may need additional fees to be attached through a child transaction using the popular + /// [Child-Pays-For-Parent](https://bitcoinops.org/en/topics/cpfp) fee bumping technique. This + /// child transaction must include the anchor input described within `anchor_descriptor` along + /// with additional inputs to meet the target feerate. Failure to meet the target feerate + /// decreases the confirmation odds of the transaction package (which includes the commitment + /// and child anchor transactions), possibly resulting in a loss of funds. Once the transaction + /// is constructed, it must be fully signed for and broadcasted by the consumer of the event + /// along with the `commitment_tx` enclosed. Note that the `commitment_tx` must always be + /// broadcast first, as the child anchor transaction depends on it. + /// + /// The consumer should be able to sign for any of the additional inputs included within the + /// child anchor transaction. To sign its anchor input, an [`InMemorySigner`] should be + /// re-derived through [`KeysManager::derive_channel_keys`] with the help of + /// [`AnchorDescriptor::channel_keys_id`] and [`AnchorDescriptor::channel_value_satoshis`]. + /// + /// It is possible to receive more than one instance of this event if a valid child anchor + /// transaction is never broadcast or is but not with a sufficient fee to be mined. Care should + /// be taken by the consumer of the event to ensure any future iterations of the child anchor + /// transaction adhere to the [Replace-By-Fee + /// rules](https://github.com/bitcoin/bitcoin/blob/master/doc/policy/mempool-replacements.md) + /// for fee bumps to be accepted into the mempool, and eventually the chain. As the frequency of + /// these events is not user-controlled, users may ignore/drop the event if they are no longer + /// able to commit external confirmed funds to the child anchor transaction. + /// + /// The set of `pending_htlcs` on the commitment transaction to be broadcast can be inspected to + /// determine whether a significant portion of the channel's funds are allocated to HTLCs, + /// enabling users to make their own decisions regarding the importance of the commitment + /// transaction's confirmation. Note that this is not required, but simply exists as an option + /// for users to override LDK's behavior. On commitments with no HTLCs (indicated by those with + /// an empty `pending_htlcs`), confirmation of the commitment transaction can be considered to + /// be not urgent. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + /// [`KeysManager::derive_channel_keys`]: crate::chain::keysinterface::KeysManager::derive_channel_keys + ChannelClose { + /// The target feerate that the transaction package, which consists of the commitment + /// transaction and the to-be-crafted child anchor transaction, must meet. + package_target_feerate_sat_per_1000_weight: u32, + /// The channel's commitment transaction to bump the fee of. This transaction should be + /// broadcast along with the anchor transaction constructed as a result of consuming this + /// event. + commitment_tx: Transaction, + /// The absolute fee in satoshis of the commitment transaction. This can be used along the + /// with weight of the commitment transaction to determine its feerate. + commitment_tx_fee_satoshis: u64, + /// The descriptor to sign the anchor input of the anchor transaction constructed as a + /// result of consuming this event. + anchor_descriptor: AnchorDescriptor, + /// The set of pending HTLCs on the commitment transaction that need to be resolved once the + /// commitment transaction confirms. + pending_htlcs: Vec, + }, +} + /// An Event which you should probably take some action in response to. /// /// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use @@ -602,6 +681,13 @@ pub enum Event { /// Destination of the HTLC that failed to be processed. failed_next_destination: HTLCDestination, }, + #[cfg(anchors)] + /// Indicates that a transaction originating from LDK needs to have its fee bumped. This event + /// requires confirmed external funds to be readily available to spend. + /// + /// LDK does not currently generate this event. It is limited to the scope of channels with + /// anchor outputs, which will be introduced in a future release. + BumpTransaction(BumpTransactionEvent), } impl Writeable for Event { @@ -753,6 +839,15 @@ impl Writeable for Event { (2, failed_next_destination, required), }) }, + #[cfg(anchors)] + &Event::BumpTransaction(ref event)=> { + 27u8.write(writer)?; + match event { + // We never write the ChannelClose events as they'll be replayed upon restarting + // anyway if the commitment transaction remains unconfirmed. + BumpTransactionEvent::ChannelClose { .. } => {} + } + } // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. -- 2.39.5