diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 2a6d3d23e80..b4cc6a302ae 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -12,6 +12,8 @@ use bitcoin::block::{Block, Header}; use bitcoin::constants::genesis_block; use bitcoin::hash_types::{BlockHash, Txid}; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::network::Network; use bitcoin::script::{Script, ScriptBuf}; use bitcoin::secp256k1::PublicKey; @@ -21,6 +23,7 @@ use crate::chain::transaction::{OutPoint, TransactionData}; use crate::impl_writeable_tlv_based; use crate::ln::types::ChannelId; use crate::sign::ecdsa::EcdsaChannelSigner; +use crate::sign::HTLCDescriptor; #[allow(unused_imports)] use crate::prelude::*; @@ -442,3 +445,20 @@ where /// This is not exported to bindings users as we just use [u8; 32] directly. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct ClaimId(pub [u8; 32]); + +impl ClaimId { + pub(crate) fn from_htlcs(htlcs: &[HTLCDescriptor]) -> ClaimId { + let mut engine = Sha256::engine(); + for htlc in htlcs { + engine.input(&htlc.commitment_txid.to_byte_array()); + engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes()); + } + ClaimId(Sha256::from_engine(engine).to_byte_array()) + } + pub(crate) fn step_with_bytes(&self, bytes: &[u8]) -> ClaimId { + let mut engine = Sha256::engine(); + engine.input(&self.0); + engine.input(bytes); + ClaimId(Sha256::from_engine(engine).to_byte_array()) + } +} diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 0d70f9d1201..b52cdb755d8 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -14,8 +14,7 @@ use bitcoin::amount::Amount; use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::Hash; use bitcoin::locktime::absolute::LockTime; use bitcoin::script::{Script, ScriptBuf}; use bitcoin::secp256k1; @@ -882,12 +881,7 @@ impl OnchainTxHandler { // claim, which will always be unique per request. Once a claim ID // is generated, it is assigned and remains unchanged, even if the // underlying set of HTLCs changes. - let mut engine = Sha256::engine(); - for htlc in htlcs { - engine.input(&htlc.commitment_txid.to_byte_array()); - engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes()); - } - ClaimId(Sha256::from_engine(engine).to_byte_array()) + ClaimId::from_htlcs(htlcs) }, }; debug_assert!(self.pending_claim_requests.get(&claim_id).is_none()); diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index 4d6220b8765..92fcea5a7c7 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -24,9 +24,10 @@ use crate::io_extras::sink; use crate::ln::chan_utils; use crate::ln::chan_utils::{ shared_anchor_script_pubkey, HTLCOutputInCommitment, ANCHOR_INPUT_WITNESS_WEIGHT, + BASE_INPUT_WEIGHT, BASE_TX_SIZE, EMPTY_SCRIPT_SIG_WEIGHT, EMPTY_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_P2A_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT, - P2A_ANCHOR_INPUT_WITNESS_WEIGHT, + P2WSH_TXOUT_WEIGHT, SEGWIT_MARKER_FLAG_WEIGHT, TRUC_CHILD_MAX_WEIGHT, TRUC_MAX_WEIGHT, }; use crate::ln::types::ChannelId; use crate::prelude::*; @@ -42,6 +43,7 @@ use bitcoin::amount::Amount; use bitcoin::consensus::Encodable; use bitcoin::constants::WITNESS_SCALE_FACTOR; use bitcoin::locktime::absolute::LockTime; +use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; use bitcoin::secp256k1; use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::{PublicKey, Secp256k1}; @@ -50,13 +52,6 @@ use bitcoin::{ OutPoint, Psbt, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash, Witness, }; -pub(crate) const EMPTY_SCRIPT_SIG_WEIGHT: u64 = - 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64; - -const BASE_INPUT_SIZE: u64 = 32 /* txid */ + 4 /* vout */ + 4 /* sequence */; - -pub(crate) const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64; - /// A descriptor used to sign for a commitment transaction's anchor output. #[derive(Clone, Debug, PartialEq, Eq)] pub struct AnchorDescriptor { @@ -132,12 +127,21 @@ pub enum BumpTransactionEvent { /// feerate of the commitment transaction is already sufficient, in which case the child anchor /// transaction is not needed and only the commitment transaction should be broadcast. /// + /// In zero-fee commitment channels, the commitment transaction and the anchor transaction + /// form a 1-parent-1-child package that conforms to BIP 431 (known as TRUC transactions). + /// The anchor transaction must be version 3, and its size must be no more than 1000 vB. + /// The anchor transaction is usually needed to bump the fee of the commitment transaction + /// as the commitment transaction is not explicitly assigned any fees. In those cases the + /// anchor transaction must be broadcast together with the commitment transaction as a + /// `child-with-parents` package (usually using the Bitcoin Core `submitpackage` RPC). + /// /// 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 [`EcdsaChannelSigner`] should be - /// re-derived through [`SignerProvider::derive_channel_signer`]. The anchor input signature + /// child anchor transaction. To sign its keyed-anchor input, an [`EcdsaChannelSigner`] should + /// be re-derived through [`SignerProvider::derive_channel_signer`]. The anchor input signature /// can be computed with [`EcdsaChannelSigner::sign_holder_keyed_anchor_input`], which can then /// be provided to [`build_keyed_anchor_input_witness`] along with the `funding_pubkey` to - /// obtain the full witness required to spend. + /// obtain the full witness required to spend. Note that no signature or witness data is + /// required to spend the keyless anchor used in zero-fee commitment channels. /// /// 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 @@ -188,14 +192,25 @@ pub enum BumpTransactionEvent { pending_htlcs: Vec, }, /// Indicates that a channel featuring anchor outputs has unilaterally closed on-chain by a - /// holder commitment transaction and its HTLC(s) need to be resolved on-chain. With the - /// zero-HTLC-transaction-fee variant of anchor outputs, the pre-signed HTLC - /// transactions have a zero fee, thus requiring additional inputs and/or outputs to be attached - /// for a timely confirmation within the chain. These additional inputs and/or outputs must be - /// appended to the resulting HTLC transaction to meet the target feerate. Failure to meet the - /// target feerate decreases the confirmation odds of the transaction, possibly resulting in a - /// loss of funds. Once the transaction meets the target feerate, it must be signed for and - /// broadcast by the consumer of the event. + /// holder commitment transaction and its HTLC(s) need to be resolved on-chain. In all such + /// channels, the pre-signed HTLC transactions have a zero fee, thus requiring additional + /// inputs and/or outputs to be attached for a timely confirmation within the chain. These + /// additional inputs and/or outputs must be appended to the resulting HTLC transaction to + /// meet the target feerate. Failure to meet the target feerate decreases the confirmation + /// odds of the transaction, possibly resulting in a loss of funds. Once the transaction + /// meets the target feerate, it must be signed for and broadcast by the consumer of the + /// event. + /// + /// In zero-fee commitment channels, you must set the version of the HTLC claim transaction + /// to version 3 as the counterparty's signature commits to the version of + /// the transaction. You must also make sure that this claim transaction does not grow + /// bigger than 10,000 vB, the maximum vsize of any TRUC transaction as specified in + /// BIP 431. It is possible for [`htlc_descriptors`] to be long enough such + /// that claiming all the HTLCs therein in a single transaction would exceed this limit. + /// In this case, you must claim all the HTLCs in [`htlc_descriptors`] using multiple + /// transactions. Finally, note that while HTLCs in zero-fee commitment channels no + /// longer have the 1 CSV lock, LDK will still emit this event only after the commitment + /// transaction has 1 confirmation. /// /// The consumer should be able to sign for any of the non-HTLC inputs added to the resulting /// HTLC transaction. To sign HTLC inputs, an [`EcdsaChannelSigner`] should be re-derived @@ -216,6 +231,7 @@ pub enum BumpTransactionEvent { /// /// [`EcdsaChannelSigner`]: crate::sign::ecdsa::EcdsaChannelSigner /// [`EcdsaChannelSigner::sign_holder_htlc_transaction`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_holder_htlc_transaction + /// [`htlc_descriptors`]: `BumpTransactionEvent::HTLCResolution::htlc_descriptors` HTLCResolution { /// The `channel_id` of the channel which has been closed. channel_id: ChannelId, @@ -351,6 +367,12 @@ pub trait CoinSelectionSource { /// provided, in which case a zero-value empty OP_RETURN output can be used instead. /// 3. Enough inputs must be selected/contributed for the resulting transaction (including the /// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`. + /// 4. The final transaction must have a weight smaller than `max_tx_weight`; if this + /// constraint can't be met, return an `Err`. In the case of counterparty-signed HTLC + /// transactions, we will remove a chunk of HTLCs and try your algorithm again. As for + /// anchor transactions, we will try your coin selection again with the same input-output + /// set when you call [`ChannelMonitor::rebroadcast_pending_claims`], as anchor transactions + /// cannot be downsized. /// /// Implementations must take note that [`Input::satisfaction_weight`] only tracks the weight of /// the input's `script_sig` and `witness`. Some wallets, like Bitcoin Core's, may require @@ -364,9 +386,11 @@ pub trait CoinSelectionSource { /// other claims, implementations must be willing to double spend their UTXOs. The choice of /// which UTXOs to double spend is left to the implementation, but it must strive to keep the /// set of other claims being double spent to a minimum. + /// + /// [`ChannelMonitor::rebroadcast_pending_claims`]: crate::chain::channelmonitor::ChannelMonitor::rebroadcast_pending_claims fn select_confirmed_utxos<'a>( &'a self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &'a [TxOut], - target_feerate_sat_per_1000_weight: u32, + target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64, ) -> AsyncResult<'a, CoinSelection>; /// Signs and provides the full witness for all inputs within the transaction known to the /// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]). @@ -436,7 +460,18 @@ where &self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool, tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32, preexisting_tx_weight: u64, input_amount_sat: Amount, target_amount_sat: Amount, + max_tx_weight: u64, ) -> Result { + // P2WSH and P2TR outputs are both the heaviest-weight standard outputs at 34 bytes + let max_coin_selection_weight = max_tx_weight + .checked_sub(preexisting_tx_weight + P2WSH_TXOUT_WEIGHT) + .ok_or_else(|| { + log_debug!( + self.logger, + "max_tx_weight is too small to accommodate the preexisting tx weight plus a P2WSH/P2TR output" + ); + })?; + let mut selected_amount; let mut total_fees; let mut selected_utxos; @@ -476,31 +511,67 @@ where } }) .collect::>(); - eligible_utxos.sort_unstable_by_key(|(utxo, _)| utxo.output.value); + eligible_utxos.sort_unstable_by_key(|(utxo, fee_to_spend_utxo)| { + utxo.output.value - *fee_to_spend_utxo + }); selected_amount = input_amount_sat; total_fees = Amount::from_sat(fee_for_weight( target_feerate_sat_per_1000_weight, preexisting_tx_weight, )); - selected_utxos = Vec::new(); + selected_utxos = VecDeque::new(); + // Invariant: `selected_utxos_weight` is never greater than `max_coin_selection_weight` + let mut selected_utxos_weight = 0; for (utxo, fee_to_spend_utxo) in eligible_utxos { if selected_amount >= target_amount_sat + total_fees { break; } + // First skip any UTXOs with prohibitive satisfaction weights + if BASE_INPUT_WEIGHT + utxo.satisfaction_weight > max_coin_selection_weight { + continue; + } + // If adding this UTXO to `selected_utxos` would push us over the + // `max_coin_selection_weight`, remove UTXOs from the front to make room + // for this new UTXO. + while selected_utxos_weight + BASE_INPUT_WEIGHT + utxo.satisfaction_weight + > max_coin_selection_weight + && !selected_utxos.is_empty() + { + let (smallest_value_after_spend_utxo, fee_to_spend_utxo): (Utxo, Amount) = + selected_utxos.pop_front().unwrap(); + selected_amount -= smallest_value_after_spend_utxo.output.value; + total_fees -= fee_to_spend_utxo; + selected_utxos_weight -= + BASE_INPUT_WEIGHT + smallest_value_after_spend_utxo.satisfaction_weight; + } selected_amount += utxo.output.value; total_fees += fee_to_spend_utxo; - selected_utxos.push(utxo.clone()); + selected_utxos_weight += BASE_INPUT_WEIGHT + utxo.satisfaction_weight; + selected_utxos.push_back((utxo.clone(), fee_to_spend_utxo)); } if selected_amount < target_amount_sat + total_fees { log_debug!( self.logger, - "Insufficient funds to meet target feerate {} sat/kW", - target_feerate_sat_per_1000_weight + "Insufficient funds to meet target feerate {} sat/kW while remaining under {} WU", + target_feerate_sat_per_1000_weight, + max_coin_selection_weight, ); return Err(()); } - for utxo in &selected_utxos { + // Once we've selected enough UTXOs to cover `target_amount_sat + total_fees`, + // we may be able to remove some small-value ones while still covering + // `target_amount_sat + total_fees`. + while !selected_utxos.is_empty() + && selected_amount - selected_utxos.front().unwrap().0.output.value + >= target_amount_sat + total_fees - selected_utxos.front().unwrap().1 + { + let (smallest_value_after_spend_utxo, fee_to_spend_utxo) = + selected_utxos.pop_front().unwrap(); + selected_amount -= smallest_value_after_spend_utxo.output.value; + total_fees -= fee_to_spend_utxo; + } + for (utxo, _) in &selected_utxos { locked_utxos.insert(utxo.outpoint, claim_id); } } @@ -521,7 +592,10 @@ where Some(TxOut { script_pubkey: change_script, value: change_output_amount }) }; - Ok(CoinSelection { confirmed_utxos: selected_utxos, change_output }) + Ok(CoinSelection { + confirmed_utxos: selected_utxos.into_iter().map(|(utxo, _)| utxo).collect(), + change_output, + }) } } @@ -533,12 +607,11 @@ where { fn select_confirmed_utxos<'a>( &'a self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &'a [TxOut], - target_feerate_sat_per_1000_weight: u32, + target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64, ) -> AsyncResult<'a, CoinSelection> { Box::pin(async move { let utxos = self.source.list_confirmed_utxos().await?; // TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0. - const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */; let total_output_size: u64 = must_pay_to .iter() .map( @@ -550,8 +623,9 @@ where let total_input_weight = (BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight; - let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight + - ((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64); + let preexisting_tx_weight = SEGWIT_MARKER_FLAG_WEIGHT + + total_input_weight + + ((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64); let input_amount_sat = must_spend.iter().map(|input| input.previous_utxo.value).sum(); let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum(); @@ -574,6 +648,7 @@ where preexisting_tx_weight, input_amount_sat, target_amount_sat, + max_tx_weight, ) .await; if attempt.is_ok() { @@ -676,7 +751,7 @@ where .transaction_parameters .channel_type_features; let anchor_input_witness_weight = if channel_type.supports_anchor_zero_fee_commitments() { - P2A_ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_WITNESS_WEIGHT } else { ANCHOR_INPUT_WITNESS_WEIGHT }; @@ -699,9 +774,10 @@ where // the anchor input. let mut anchor_utxo = anchor_descriptor.previous_utxo(); let commitment_tx_fee_sat = Amount::from_sat(commitment_tx_fee_sat); + let commitment_tx_weight = commitment_tx.weight().to_wu(); anchor_utxo.value += commitment_tx_fee_sat; let starting_package_and_fixed_input_satisfaction_weight = - commitment_tx.weight().to_wu() + anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT; + commitment_tx_weight + anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT; let mut package_and_fixed_input_satisfaction_weight = starting_package_and_fixed_input_satisfaction_weight; @@ -723,6 +799,14 @@ where must_spend, &[], package_target_feerate_sat_per_1000_weight, + if channel_type.supports_anchor_zero_fee_commitments() { + TRUC_CHILD_MAX_WEIGHT + } else { + MAX_STANDARD_TX_WEIGHT as u64 + } + // We added the commitment tx weight to the input satisfaction weight above, so + // increase the max_tx_weight by the same delta here. + + commitment_tx_weight, ) .await?; @@ -833,6 +917,15 @@ where assert!(package_fee >= expected_package_fee); } + #[cfg(debug_assertions)] + if channel_type.supports_anchor_zero_fee_commitments() { + assert!(commitment_tx.weight().to_wu() < TRUC_MAX_WEIGHT); + assert!(anchor_tx.weight().to_wu() < TRUC_CHILD_MAX_WEIGHT); + } else { + assert!(commitment_tx.weight().to_wu() < MAX_STANDARD_TX_WEIGHT as u64); + assert!(anchor_tx.weight().to_wu() < MAX_STANDARD_TX_WEIGHT as u64); + } + log_info!( self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}", @@ -854,17 +947,6 @@ where .channel_derivation_parameters .transaction_parameters .channel_type_features; - let mut htlc_tx = Transaction { - version: if channel_type.supports_anchor_zero_fee_commitments() { - Version::non_standard(3) - } else { - Version::TWO - }, - lock_time: tx_lock_time, - input: vec![], - output: vec![], - }; - let mut must_spend = Vec::with_capacity(htlc_descriptors.len()); let (htlc_success_witness_weight, htlc_timeout_witness_weight) = if channel_type.supports_anchor_zero_fee_commitments() { ( @@ -879,123 +961,213 @@ where } else { panic!("channel type should be either zero-fee HTLCs, or zero-fee commitments"); }; - for htlc_descriptor in htlc_descriptors { - let htlc_input = htlc_descriptor.unsigned_tx_input(); - must_spend.push(Input { - outpoint: htlc_input.previous_output.clone(), - previous_utxo: htlc_descriptor.previous_utxo(&self.secp), - satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT - + if htlc_descriptor.preimage.is_some() { - htlc_success_witness_weight - } else { - htlc_timeout_witness_weight - }, - }); - htlc_tx.input.push(htlc_input); - let htlc_output = htlc_descriptor.tx_output(&self.secp); - htlc_tx.output.push(htlc_output); - } - log_debug!( - self.logger, - "Performing coin selection for HTLC transaction targeting {} sat/kW", - target_feerate_sat_per_1000_weight - ); + let max_tx_weight = if channel_type.supports_anchor_zero_fee_commitments() { + // Cap the size of transactions claiming `HolderHTLCOutput` in 0FC channels. + // Otherwise, we could hit the max 10_000vB size limit on V3 transactions + // (BIP 431 rule 4). + TRUC_MAX_WEIGHT + } else { + // We should never hit this because HTLC-timeout transactions have a signed + // locktime, HTLC-success transactions do not, and we never aggregate + // packages with a signed locktime with packages that do not have a signed + // locktime. + // Hence in the worst case, we aggregate 483 success HTLC transactions, + // and 483 * 705 ~= 341_000, and 341_000 < 400_000. + MAX_STANDARD_TX_WEIGHT as u64 + }; + // A 1-input 1-output transaction, both p2wpkh is 438 WU. + // This is just an initial budget, we increase it further below in case the user can't satisfy it. + const USER_COINS_WEIGHT_BUDGET: u64 = 1000; + + let mut broadcasted_htlcs = 0; + let mut batch_size = htlc_descriptors.len() - broadcasted_htlcs; + let mut utxo_id = claim_id; + + while broadcasted_htlcs < htlc_descriptors.len() { + let mut htlc_tx = Transaction { + version: if channel_type.supports_anchor_zero_fee_commitments() { + Version::non_standard(3) + } else { + Version::TWO + }, + lock_time: tx_lock_time, + input: vec![], + output: vec![], + }; + let mut must_spend = Vec::with_capacity(htlc_descriptors.len() - broadcasted_htlcs); + let mut htlc_weight_sum = 0; + for htlc_descriptor in + &htlc_descriptors[broadcasted_htlcs..broadcasted_htlcs + batch_size] + { + let input_output_weight = if htlc_descriptor.preimage.is_some() { + chan_utils::aggregated_htlc_success_input_output_pair_weight(channel_type) + } else { + chan_utils::aggregated_htlc_timeout_input_output_pair_weight(channel_type) + }; + if htlc_weight_sum + input_output_weight >= max_tx_weight - USER_COINS_WEIGHT_BUDGET + { + break; + } + htlc_weight_sum += input_output_weight; + let htlc_input = htlc_descriptor.unsigned_tx_input(); + must_spend.push(Input { + outpoint: htlc_input.previous_output.clone(), + previous_utxo: htlc_descriptor.previous_utxo(&self.secp), + satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + + if htlc_descriptor.preimage.is_some() { + htlc_success_witness_weight + } else { + htlc_timeout_witness_weight + }, + }); + htlc_tx.input.push(htlc_input); + let htlc_output = htlc_descriptor.tx_output(&self.secp); + htlc_tx.output.push(htlc_output); + } + batch_size = htlc_tx.input.len(); + let selected_htlcs = + &htlc_descriptors[broadcasted_htlcs..broadcasted_htlcs + batch_size]; - #[cfg(debug_assertions)] - let must_spend_satisfaction_weight = - must_spend.iter().map(|input| input.satisfaction_weight).sum::(); - #[cfg(debug_assertions)] - let must_spend_amount = - must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::(); + log_info!( + self.logger, + "Batch transaction assigned to UTXO id {} contains {} HTLCs: {}", + log_bytes!(utxo_id.0), + batch_size, + log_iter!(selected_htlcs.iter().map(|d| d.outpoint())) + ); - let coin_selection: CoinSelection = self - .utxo_source - .select_confirmed_utxos( - claim_id, - must_spend, - &htlc_tx.output, - target_feerate_sat_per_1000_weight, - ) - .await?; - - #[cfg(debug_assertions)] - let input_satisfaction_weight: u64 = - coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum(); - #[cfg(debug_assertions)] - let total_satisfaction_weight = must_spend_satisfaction_weight + input_satisfaction_weight; - #[cfg(debug_assertions)] - let input_value: u64 = - coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum(); - #[cfg(debug_assertions)] - let total_input_amount = must_spend_amount + input_value; - - self.process_coin_selection(&mut htlc_tx, &coin_selection); - - // construct psbt - let mut htlc_psbt = Psbt::from_unsigned_tx(htlc_tx).unwrap(); - // add witness_utxo to htlc inputs - for (i, htlc_descriptor) in htlc_descriptors.iter().enumerate() { - debug_assert_eq!( - htlc_psbt.unsigned_tx.input[i].previous_output, - htlc_descriptor.outpoint() + log_debug!( + self.logger, + "Performing coin selection for HTLC transaction targeting {} sat/kW", + target_feerate_sat_per_1000_weight ); - htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp)); - } - // add witness_utxo to remaining inputs - for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() { - // offset to skip the htlc inputs - let index = idx + htlc_descriptors.len(); - debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint); - if utxo.output.script_pubkey.is_witness_program() { - htlc_psbt.inputs[index].witness_utxo = Some(utxo.output); + + #[cfg(debug_assertions)] + let must_spend_satisfaction_weight = + must_spend.iter().map(|input| input.satisfaction_weight).sum::(); + #[cfg(debug_assertions)] + let must_spend_amount = + must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::(); + + let coin_selection: CoinSelection = match self + .utxo_source + .select_confirmed_utxos( + utxo_id, + must_spend, + &htlc_tx.output, + target_feerate_sat_per_1000_weight, + max_tx_weight, + ) + .await + { + Ok(selection) => selection, + Err(()) => { + let htlcs_to_remove = USER_COINS_WEIGHT_BUDGET.div_ceil( + chan_utils::aggregated_htlc_timeout_input_output_pair_weight(channel_type), + ); + batch_size = batch_size.checked_sub(htlcs_to_remove as usize).ok_or(())?; + if batch_size == 0 { + return Err(()); + } + continue; + }, + }; + broadcasted_htlcs += batch_size; + batch_size = htlc_descriptors.len() - broadcasted_htlcs; + utxo_id = claim_id.step_with_bytes(&broadcasted_htlcs.to_be_bytes()); + + #[cfg(debug_assertions)] + let input_satisfaction_weight: u64 = + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum(); + #[cfg(debug_assertions)] + let total_satisfaction_weight = must_spend_satisfaction_weight + input_satisfaction_weight; + #[cfg(debug_assertions)] + let input_value: u64 = + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum(); + #[cfg(debug_assertions)] + let total_input_amount = must_spend_amount + input_value; + + self.process_coin_selection(&mut htlc_tx, &coin_selection); + + // construct psbt + let mut htlc_psbt = Psbt::from_unsigned_tx(htlc_tx).unwrap(); + // add witness_utxo to htlc inputs + for (i, htlc_descriptor) in selected_htlcs.iter().enumerate() { + debug_assert_eq!( + htlc_psbt.unsigned_tx.input[i].previous_output, + htlc_descriptor.outpoint() + ); + htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp)); } - } - #[cfg(debug_assertions)] - let unsigned_tx_weight = htlc_psbt.unsigned_tx.weight().to_wu() - - (htlc_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); + // add witness_utxo to remaining inputs + for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() { + // offset to skip the htlc inputs + let index = idx + selected_htlcs.len(); + debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint); + if utxo.output.script_pubkey.is_witness_program() { + htlc_psbt.inputs[index].witness_utxo = Some(utxo.output); + } + } - log_debug!( - self.logger, - "Signing HTLC transaction {}", - htlc_psbt.unsigned_tx.compute_txid() - ); - htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?; - - let mut signers = BTreeMap::new(); - for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { - let keys_id = htlc_descriptor.channel_derivation_parameters.keys_id; - let signer = signers - .entry(keys_id) - .or_insert_with(|| self.signer_provider.derive_channel_signer(keys_id)); - let htlc_sig = - signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?; - let witness_script = htlc_descriptor.witness_script(&self.secp); - htlc_tx.input[idx].witness = - htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script); - } + #[cfg(debug_assertions)] + let unsigned_tx_weight = htlc_psbt.unsigned_tx.weight().to_wu() + - (htlc_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); - #[cfg(debug_assertions)] - { - let signed_tx_weight = htlc_tx.weight().to_wu(); - let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight; - // Our estimate should be within a 1% error margin of the actual weight and we should - // never underestimate. - assert!(expected_signed_tx_weight >= signed_tx_weight); - assert!(expected_signed_tx_weight * 99 / 100 <= signed_tx_weight); - - let expected_signed_tx_fee = - fee_for_weight(target_feerate_sat_per_1000_weight, signed_tx_weight); - let signed_tx_fee = total_input_amount - - htlc_tx.output.iter().map(|output| output.value.to_sat()).sum::(); - // Our feerate should always be at least what we were seeking. It may overshoot if - // the coin selector burned funds to an OP_RETURN without a change output. - assert!(signed_tx_fee >= expected_signed_tx_fee); + log_debug!( + self.logger, + "Signing HTLC transaction {}", + htlc_psbt.unsigned_tx.compute_txid() + ); + htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?; + + let mut signers = BTreeMap::new(); + for (idx, htlc_descriptor) in selected_htlcs.iter().enumerate() { + let keys_id = htlc_descriptor.channel_derivation_parameters.keys_id; + let signer = signers + .entry(keys_id) + .or_insert_with(|| self.signer_provider.derive_channel_signer(keys_id)); + let htlc_sig = signer.sign_holder_htlc_transaction( + &htlc_tx, + idx, + htlc_descriptor, + &self.secp, + )?; + let witness_script = htlc_descriptor.witness_script(&self.secp); + htlc_tx.input[idx].witness = + htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script); + } + + #[cfg(debug_assertions)] + { + let signed_tx_weight = htlc_tx.weight().to_wu(); + let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight; + // Our estimate should be within a 2% error margin of the actual weight and we should + // never underestimate. + assert!(expected_signed_tx_weight >= signed_tx_weight); + assert!(expected_signed_tx_weight * 98 / 100 <= signed_tx_weight); + + let expected_signed_tx_fee = + fee_for_weight(target_feerate_sat_per_1000_weight, signed_tx_weight); + let signed_tx_fee = total_input_amount + - htlc_tx.output.iter().map(|output| output.value.to_sat()).sum::(); + // Our feerate should always be at least what we were seeking. It may overshoot if + // the coin selector burned funds to an OP_RETURN without a change output. + assert!(signed_tx_fee >= expected_signed_tx_fee); + } + + #[cfg(debug_assertions)] + if channel_type.supports_anchor_zero_fee_commitments() { + assert!(htlc_tx.weight().to_wu() < TRUC_MAX_WEIGHT); + } else { + assert!(htlc_tx.weight().to_wu() < MAX_STANDARD_TX_WEIGHT as u64); + } + + log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); + self.broadcaster.broadcast_transactions(&[&htlc_tx]); } - log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); - self.broadcaster.broadcast_transactions(&[&htlc_tx]); Ok(()) } @@ -1090,7 +1262,7 @@ mod tests { impl CoinSelectionSourceSync for TestCoinSelectionSource { fn select_confirmed_utxos( &self, _claim_id: ClaimId, must_spend: Vec, _must_pay_to: &[TxOut], - target_feerate_sat_per_1000_weight: u32, + target_feerate_sat_per_1000_weight: u32, _max_tx_weight: u64, ) -> Result { let mut expected_selects = self.expected_selects.lock().unwrap(); let (weight, value, feerate, res) = expected_selects.remove(0); diff --git a/lightning/src/events/bump_transaction/sync.rs b/lightning/src/events/bump_transaction/sync.rs index 7d407f4bb8d..50e8a8a7d23 100644 --- a/lightning/src/events/bump_transaction/sync.rs +++ b/lightning/src/events/bump_transaction/sync.rs @@ -102,13 +102,14 @@ where { fn select_confirmed_utxos( &self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &[TxOut], - target_feerate_sat_per_1000_weight: u32, + target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64, ) -> Result { let mut fut = self.wallet.select_confirmed_utxos( claim_id, must_spend, must_pay_to, target_feerate_sat_per_1000_weight, + max_tx_weight, ); let mut waker = dummy_waker(); let mut ctx = task::Context::from_waker(&mut waker); @@ -140,7 +141,7 @@ pub trait CoinSelectionSourceSync { /// A synchronous version of [`CoinSelectionSource::select_confirmed_utxos`]. fn select_confirmed_utxos( &self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &[TxOut], - target_feerate_sat_per_1000_weight: u32, + target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64, ) -> Result; /// A synchronous version of [`CoinSelectionSource::sign_psbt`]. @@ -169,13 +170,14 @@ where { fn select_confirmed_utxos<'a>( &'a self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &'a [TxOut], - target_feerate_sat_per_1000_weight: u32, + target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64, ) -> AsyncResult<'a, CoinSelection> { let coins = self.0.select_confirmed_utxos( claim_id, must_spend, must_pay_to, target_feerate_sat_per_1000_weight, + max_tx_weight, ); Box::pin(async move { coins }) } diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index d7a13d59e3d..cccbabbbd07 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1604,8 +1604,10 @@ pub enum Event { /// 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 unless the - /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`] config flag is set to true. + /// LDK does not currently generate this event unless either the + /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`] or the + /// [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`] config flags are set to + /// true. /// It is limited to the scope of channels with anchor outputs. /// /// # Failure Behavior and Persistence @@ -1613,6 +1615,7 @@ pub enum Event { /// returning `Err(ReplayEvent ())`), but will only be regenerated as needed after restarts. /// /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx + /// [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments BumpTransaction(BumpTransactionEvent), /// We received an onion message that is intended to be forwarded to a peer /// that is currently offline. This event will only be generated if the diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 373d0691564..4549731b59a 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -11,6 +11,7 @@ //! largely of interest for those implementing the traits on [`crate::sign`] by hand. use bitcoin::amount::Amount; +use bitcoin::constants::WITNESS_SCALE_FACTOR; use bitcoin::opcodes; use bitcoin::script::{Builder, Script, ScriptBuf}; use bitcoin::sighash; @@ -89,12 +90,18 @@ pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 114; #[cfg(not(feature = "grind_signatures"))] pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 115; -/// The weight of a P2A anchor witness. -pub const P2A_ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 1; +/// The weight of an empty witness; used to spend a P2A output. +pub const EMPTY_WITNESS_WEIGHT: u64 = 1; /// The maximum value of a P2A anchor. pub const P2A_MAX_VALUE: u64 = 240; +/// The maximum weight of a TRUC transaction, see BIP431. +pub const TRUC_MAX_WEIGHT: u64 = 10_000 * WITNESS_SCALE_FACTOR as u64; + +/// The maximum weight of a TRUC transaction with an unconfirmed TRUC ancestor, see BIP431. +pub const TRUC_CHILD_MAX_WEIGHT: u64 = 1000 * WITNESS_SCALE_FACTOR as u64; + /// The upper bound weight of an HTLC timeout input from a commitment transaction with keyed anchor outputs. pub const HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT: u64 = 288; /// The upper bound weight of an HTLC timeout input from a commitment transaction with a p2a anchor output. @@ -125,6 +132,15 @@ pub const FUNDING_TRANSACTION_WITNESS_WEIGHT: u64 = 1 + // number_of_witness_ele 1 + // witness_script_length MULTISIG_SCRIPT_SIZE; +pub(crate) const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */; +pub(crate) const SEGWIT_MARKER_FLAG_WEIGHT: u64 = 2; +pub(crate) const EMPTY_SCRIPT_SIG_WEIGHT: u64 = + 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64; +pub(crate) const BASE_INPUT_SIZE: u64 = 32 /* txid */ + 4 /* vout */ + 4 /* sequence */; +pub(crate) const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64; +pub(crate) const P2WSH_TXOUT_WEIGHT: u64 = + (8 /* value */ + 1 /* var_int */ + 34/* p2wsh spk */) * WITNESS_SCALE_FACTOR as u64; + /// Gets the weight for an HTLC-Success transaction. #[inline] #[rustfmt::skip] @@ -134,6 +150,18 @@ pub fn htlc_success_tx_weight(channel_type_features: &ChannelTypeFeatures) -> u6 if channel_type_features.supports_anchors_zero_fee_htlc_tx() { HTLC_SUCCESS_ANCHOR_TX_WEIGHT } else { HTLC_SUCCESS_TX_WEIGHT } } +/// Gets the weight of a single input-output pair in externally funded HTLC-success transactions +pub fn aggregated_htlc_success_input_output_pair_weight( + channel_type_features: &ChannelTypeFeatures, +) -> u64 { + let satisfaction_weight = if channel_type_features.supports_anchors_zero_fee_htlc_tx() { + EMPTY_SCRIPT_SIG_WEIGHT + HTLC_SUCCESS_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT + } else { + EMPTY_SCRIPT_SIG_WEIGHT + HTLC_SUCCESS_INPUT_P2A_ANCHOR_WITNESS_WEIGHT + }; + BASE_INPUT_WEIGHT + P2WSH_TXOUT_WEIGHT + satisfaction_weight +} + /// Gets the weight for an HTLC-Timeout transaction. #[inline] #[rustfmt::skip] @@ -143,6 +171,18 @@ pub fn htlc_timeout_tx_weight(channel_type_features: &ChannelTypeFeatures) -> u6 if channel_type_features.supports_anchors_zero_fee_htlc_tx() { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT } } +/// Gets the weight of a single input-output pair in externally funded HTLC-timeout transactions +pub fn aggregated_htlc_timeout_input_output_pair_weight( + channel_type_features: &ChannelTypeFeatures, +) -> u64 { + let satisfaction_weight = if channel_type_features.supports_anchors_zero_fee_htlc_tx() { + EMPTY_SCRIPT_SIG_WEIGHT + HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT + } else { + EMPTY_SCRIPT_SIG_WEIGHT + HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT + }; + BASE_INPUT_WEIGHT + P2WSH_TXOUT_WEIGHT + satisfaction_weight +} + /// Describes the type of HTLC claim as determined by analyzing the witness. #[derive(PartialEq, Eq)] pub enum HTLCClaim { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 5344818a195..e4634bd61e3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -36,7 +36,6 @@ use crate::chain::channelmonitor::{ }; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::chain::BestBlock; -use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::events::{ClosureReason, FundingInfo}; use crate::ln::chan_utils; use crate::ln::chan_utils::{ @@ -44,7 +43,7 @@ use crate::ln::chan_utils::{ selected_commitment_sat_per_1000_weight, ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, CounterpartyChannelTransactionParameters, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HolderCommitmentTransaction, - FUNDING_TRANSACTION_WITNESS_WEIGHT, + BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT, FUNDING_TRANSACTION_WITNESS_WEIGHT, }; use crate::ln::channel_state::{ ChannelShutdownState, CounterpartyForwardingInfo, InboundHTLCDetails, InboundHTLCStateDetails, diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 6bde99bb59b..884e71784f7 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -21,7 +21,9 @@ use crate::events::{ ClaimedHTLC, ClosureReason, Event, HTLCHandlingFailureType, PaidBolt12Invoice, PathFailure, PaymentFailureReason, PaymentPurpose, }; -use crate::ln::chan_utils::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC}; +use crate::ln::chan_utils::{ + commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, TRUC_MAX_WEIGHT, +}; use crate::ln::channelmanager::{ AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, RecipientOnionFields, MIN_CLTV_EXPIRY_DELTA, @@ -58,6 +60,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash as _; use bitcoin::locktime::absolute::{LockTime, LOCK_TIME_THRESHOLD}; use bitcoin::network::Network; +use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; use bitcoin::pow::CompactTarget; use bitcoin::script::ScriptBuf; use bitcoin::secp256k1::{PublicKey, SecretKey}; @@ -400,12 +403,18 @@ fn do_connect_block_without_consistency_checks<'a, 'b, 'c, 'd>( } pub fn provide_anchor_reserves<'a, 'b, 'c>(nodes: &[Node<'a, 'b, 'c>]) -> Transaction { + provide_anchor_utxo_reserves(nodes, 1, Amount::ONE_BTC) +} + +pub fn provide_anchor_utxo_reserves<'a, 'b, 'c>( + nodes: &[Node<'a, 'b, 'c>], utxos: usize, amount: Amount, +) -> Transaction { let mut output = Vec::with_capacity(nodes.len()); for node in nodes { - output.push(TxOut { - value: Amount::ONE_BTC, - script_pubkey: node.wallet_source.get_change_script().unwrap(), - }); + let script_pubkey = node.wallet_source.get_change_script().unwrap(); + for _ in 0..utxos { + output.push(TxOut { value: amount, script_pubkey: script_pubkey.clone() }); + } } let tx = Transaction { version: TxVersion::TWO, @@ -1941,6 +1950,11 @@ pub fn update_nodes_with_chan_announce<'a, 'b, 'c, 'd>( pub fn do_check_spends Option>( tx: &Transaction, get_output: F, ) { + if tx.version == TxVersion::non_standard(3) { + assert!(tx.weight().to_wu() <= TRUC_MAX_WEIGHT); + } else { + assert!(tx.weight().to_wu() <= MAX_STANDARD_TX_WEIGHT as u64); + } let mut p2a_output_below_dust = false; let mut has_p2a_output = false; for outp in tx.output.iter() { diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs index cc90e104aa1..1c00b8a2f44 100644 --- a/lightning/src/ln/funding.rs +++ b/lightning/src/ln/funding.rs @@ -12,7 +12,8 @@ use bitcoin::{Amount, ScriptBuf, SignedAmount, TxOut}; use bitcoin::{Script, Sequence, Transaction, Weight}; -use crate::events::bump_transaction::{Utxo, EMPTY_SCRIPT_SIG_WEIGHT}; +use crate::events::bump_transaction::Utxo; +use crate::ln::chan_utils::EMPTY_SCRIPT_SIG_WEIGHT; use crate::prelude::Vec; use crate::sign::{P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT}; diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 3c683fcc5ee..164fabba4c0 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -26,8 +26,9 @@ use bitcoin::{ }; use crate::chain::chaininterface::fee_for_weight; -use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; -use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; +use crate::ln::chan_utils::{ + BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT, FUNDING_TRANSACTION_WITNESS_WEIGHT, +}; use crate::ln::channel::{FundingNegotiationContext, TOTAL_BITCOIN_SUPPLY_SATOSHIS}; use crate::ln::funding::FundingTxInput; use crate::ln::msgs; diff --git a/lightning/src/ln/zero_fee_commitment_tests.rs b/lightning/src/ln/zero_fee_commitment_tests.rs index 9d915785c0f..b4fd9e0be88 100644 --- a/lightning/src/ln/zero_fee_commitment_tests.rs +++ b/lightning/src/ln/zero_fee_commitment_tests.rs @@ -1,5 +1,15 @@ -use crate::ln::chan_utils::shared_anchor_script_pubkey; +use crate::events::{ClosureReason, Event}; +use crate::ln::chan_utils; +use crate::ln::chan_utils::{ + BASE_INPUT_WEIGHT, BASE_TX_SIZE, EMPTY_SCRIPT_SIG_WEIGHT, EMPTY_WITNESS_WEIGHT, + P2WSH_TXOUT_WEIGHT, SEGWIT_MARKER_FLAG_WEIGHT, TRUC_CHILD_MAX_WEIGHT, +}; use crate::ln::functional_test_utils::*; +use crate::ln::msgs::BaseMessageHandler; +use crate::prelude::*; + +use bitcoin::constants::WITNESS_SCALE_FACTOR; +use bitcoin::Amount; #[test] fn test_p2a_anchor_values_under_trims_and_rounds() { @@ -46,10 +56,10 @@ fn test_p2a_anchor_values_under_trims_and_rounds() { )* let txn = get_local_commitment_txn!(nodes[0], chan_id); assert_eq!(txn.len(), 1); - assert_eq!(txn[0].output.iter().find(|output| output.script_pubkey == shared_anchor_script_pubkey()).unwrap().value.to_sat(), $expected_p2a_value_sat); + assert_eq!(txn[0].output.iter().find(|output| output.script_pubkey == chan_utils::shared_anchor_script_pubkey()).unwrap().value.to_sat(), $expected_p2a_value_sat); let txn = get_local_commitment_txn!(nodes[1], chan_id); assert_eq!(txn.len(), 1); - assert_eq!(txn[0].output.iter().find(|output| output.script_pubkey == shared_anchor_script_pubkey()).unwrap().value.to_sat(), $expected_p2a_value_sat); + assert_eq!(txn[0].output.iter().find(|output| output.script_pubkey == chan_utils::shared_anchor_script_pubkey()).unwrap().value.to_sat(), $expected_p2a_value_sat); for hash in node_0_1_hashes { fail_payment(&nodes[0], &[&nodes[1]], hash); } @@ -92,3 +102,338 @@ fn test_p2a_anchor_values_under_trims_and_rounds() { p2a_value_test!([353_000], [353_001], 240); p2a_value_test!([353_001], [353_001], 240); } + +#[test] +fn test_htlc_claim_chunking() { + // Assert we split an overall HolderHTLCOutput claim into constituent + // HTLC claim transactions such that each transaction is under TRUC_MAX_WEIGHT. + // Assert we reduce the number of HTLCs in a batch transaction by 2 if the + // coin selection algorithm fails to meet the target weight. + // Assert the claim_id of the first batch transaction is the claim + // id assigned to the overall claim. + // Assert we give up bumping a HTLC transaction once the batch size is + // 0 or negative. + // + // Route a bunch of HTLCs, force close the channel, assert two HTLC transactions + // get broadcasted, confirm only one of them, assert a new one gets broadcasted + // to sweep the remaining HTLCs, confirm a block without that transaction while + // dropping all available coin selection utxos, and assert we give up creating + // another HTLC transaction when handling the third HTLC bump. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut user_cfg = test_default_channel_config(); + user_cfg.channel_handshake_config.our_htlc_minimum_msat = 1; + user_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + user_cfg.channel_handshake_config.our_max_accepted_htlcs = 114; + user_cfg.manually_accept_inbound_channels = true; + + let configs = [Some(user_cfg.clone()), Some(user_cfg)]; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &configs); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let coinbase_tx = provide_anchor_utxo_reserves(&nodes, 50, Amount::from_sat(500)); + + const CHAN_CAPACITY: u64 = 10_000_000; + let (_, _, chan_id, _funding_tx) = create_announced_chan_between_nodes_with_value( + &nodes, + 0, + 1, + CHAN_CAPACITY, + (CHAN_CAPACITY / 2) * 1000, + ); + + let mut node_1_preimages = Vec::new(); + const NONDUST_HTLC_AMT_MSAT: u64 = 1_000_000; + for _ in 0..75 { + let (preimage, payment_hash, _, _) = + route_payment(&nodes[0], &[&nodes[1]], NONDUST_HTLC_AMT_MSAT); + node_1_preimages.push((preimage, payment_hash)); + } + let node_0_commit_tx = get_local_commitment_txn!(nodes[0], chan_id); + assert_eq!(node_0_commit_tx.len(), 1); + assert_eq!(node_0_commit_tx[0].output.len(), 75 + 2 + 1); + let node_1_commit_tx = get_local_commitment_txn!(nodes[1], chan_id); + assert_eq!(node_1_commit_tx.len(), 1); + assert_eq!(node_1_commit_tx[0].output.len(), 75 + 2 + 1); + + for (preimage, payment_hash) in node_1_preimages { + nodes[1].node.claim_funds(preimage); + check_added_monitors!(nodes[1], 1); + expect_payment_claimed!(nodes[1], payment_hash, NONDUST_HTLC_AMT_MSAT); + } + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); + + mine_transaction(&nodes[0], &node_1_commit_tx[0]); + mine_transaction(&nodes[1], &node_1_commit_tx[0]); + + let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::BumpTransaction(bump_event) => { + nodes[1].bump_tx_handler.handle_event(&bump_event); + }, + _ => panic!("Unexpected event"), + } + + let htlc_claims = nodes[1].tx_broadcaster.txn_broadcast(); + assert_eq!(htlc_claims.len(), 2); + + check_spends!(htlc_claims[0], node_1_commit_tx[0], coinbase_tx); + check_spends!(htlc_claims[1], node_1_commit_tx[0], coinbase_tx); + + assert_eq!(htlc_claims[0].input.len(), 71); + assert_eq!(htlc_claims[0].output.len(), 51); + assert_eq!(htlc_claims[1].input.len(), 34); + assert_eq!(htlc_claims[1].output.len(), 24); + + check_closed_broadcast!(nodes[0], true); + check_added_monitors!(nodes[0], 1); + check_closed_event!( + nodes[0], + 1, + ClosureReason::CommitmentTxConfirmed, + [nodes[1].node.get_our_node_id()], + CHAN_CAPACITY + ); + assert!(nodes[0].node.list_channels().is_empty()); + check_closed_broadcast!(nodes[1], true); + check_added_monitors!(nodes[1], 1); + check_closed_event!( + nodes[1], + 1, + ClosureReason::CommitmentTxConfirmed, + [nodes[0].node.get_our_node_id()], + CHAN_CAPACITY + ); + assert!(nodes[1].node.list_channels().is_empty()); + assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); + assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); + + mine_transaction(&nodes[1], &htlc_claims[0]); + + let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::BumpTransaction(bump_event) => { + nodes[1].bump_tx_handler.handle_event(&bump_event); + }, + _ => panic!("Unexpected event"), + } + + let fresh_htlc_claims = nodes[1].tx_broadcaster.txn_broadcast(); + assert_eq!(fresh_htlc_claims.len(), 1); + check_spends!(fresh_htlc_claims[0], node_1_commit_tx[0], coinbase_tx); + // We are targeting a higher feerate here, + // so we need more utxos here compared to `htlc_claims[1]` above. + assert_eq!(fresh_htlc_claims[0].input.len(), 37); + assert_eq!(fresh_htlc_claims[0].output.len(), 25); + + let log_entries = nodes[1].logger.lines.lock().unwrap(); + let batch_tx_id_assignments: Vec<_> = log_entries + .keys() + .map(|key| &key.1) + .filter(|log_msg| log_msg.contains("Batch transaction assigned to UTXO id")) + .collect(); + assert_eq!(batch_tx_id_assignments.len(), 7); + + let mut unique_claim_ids: Vec<(&str, u8)> = Vec::new(); + for claim_id in batch_tx_id_assignments + .iter() + .map(|assignment| assignment.split_whitespace().nth(6).unwrap()) + { + if let Some((_, count)) = unique_claim_ids.iter_mut().find(|(id, _count)| &claim_id == id) { + *count += 1; + } else { + unique_claim_ids.push((claim_id, 1)); + } + } + unique_claim_ids.sort_unstable_by_key(|(_id, count)| *count); + assert_eq!(unique_claim_ids.len(), 2); + let (og_claim_id, og_claim_id_count) = unique_claim_ids.pop().unwrap(); + assert_eq!(og_claim_id_count, 6); + assert_eq!(unique_claim_ids.pop().unwrap().1, 1); + + let handling_htlc_bumps: Vec<_> = log_entries + .keys() + .map(|key| &key.1) + .filter(|log_msg| log_msg.contains("Handling HTLC bump")) + .map(|log_msg| { + log_msg + .split_whitespace() + .nth(5) + .unwrap() + .trim_matches(|c: char| c.is_ascii_punctuation()) + }) + .collect(); + assert_eq!(handling_htlc_bumps.len(), 2); + assert_eq!(handling_htlc_bumps[0], og_claim_id); + assert_eq!(handling_htlc_bumps[1], og_claim_id); + + let mut batch_sizes: Vec = batch_tx_id_assignments + .iter() + .map(|assignment| assignment.split_whitespace().nth(8).unwrap().parse().unwrap()) + .collect(); + batch_sizes.sort_unstable(); + batch_sizes.reverse(); + assert_eq!(batch_sizes.len(), 7); + assert_eq!(batch_sizes.pop().unwrap(), 24); + assert_eq!(batch_sizes.pop().unwrap(), 24); + for i in (51..=59).step_by(2) { + assert_eq!(batch_sizes.pop().unwrap(), i); + } + drop(log_entries); + + nodes[1].wallet_source.clear_utxos(); + nodes[1].chain_monitor.chain_monitor.rebroadcast_pending_claims(); + + let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::BumpTransaction(bump_event) => { + nodes[1].bump_tx_handler.handle_event(&bump_event); + }, + _ => panic!("Unexpected event"), + } + + nodes[1].logger.assert_log( + "lightning::events::bump_transaction", + format!( + "Failed bumping HTLC transaction fee for commitment {}", + node_1_commit_tx[0].compute_txid() + ), + 1, + ); +} + +#[test] +fn test_anchor_tx_too_big() { + // Assert all V3 anchor tx transactions are below TRUC_CHILD_MAX_WEIGHT. + // + // Provide a bunch of small utxos, fail to bump the commitment, + // then provide a single big-value utxo, and successfully broadcast + // the commitment. + const FEERATE: u32 = 500; + let chanmon_cfgs = create_chanmon_cfgs(2); + { + let mut feerate_lock = chanmon_cfgs[1].fee_estimator.sat_per_kw.lock().unwrap(); + *feerate_lock = FEERATE; + } + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut user_cfg = test_default_channel_config(); + user_cfg.channel_handshake_config.our_htlc_minimum_msat = 1; + user_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + user_cfg.channel_handshake_config.our_max_accepted_htlcs = 114; + user_cfg.manually_accept_inbound_channels = true; + + let configs = [Some(user_cfg.clone()), Some(user_cfg)]; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &configs); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + + let _coinbase_tx_a = provide_anchor_utxo_reserves(&nodes, 50, Amount::from_sat(500)); + + const CHAN_CAPACITY: u64 = 10_000_000; + let (_, _, chan_id, _funding_tx) = create_announced_chan_between_nodes_with_value( + &nodes, + 0, + 1, + CHAN_CAPACITY, + (CHAN_CAPACITY / 2) * 1000, + ); + + let mut node_1_preimages = Vec::new(); + const NONDUST_HTLC_AMT_MSAT: u64 = 1_000_000; + for _ in 0..50 { + let (preimage, payment_hash, _, _) = + route_payment(&nodes[0], &[&nodes[1]], NONDUST_HTLC_AMT_MSAT); + node_1_preimages.push((preimage, payment_hash)); + } + let commitment_tx = get_local_commitment_txn!(nodes[1], chan_id).pop().unwrap(); + let commitment_txid = commitment_tx.compute_txid(); + + let message = "Channel force-closed".to_owned(); + nodes[1] + .node + .force_close_broadcasting_latest_txn(&chan_id, &node_a_id, message.clone()) + .unwrap(); + check_added_monitors(&nodes[1], 1); + check_closed_broadcast!(nodes[1], true); + + let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; + check_closed_event!(nodes[1], 1, reason, [node_a_id], CHAN_CAPACITY); + + let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::BumpTransaction(bump_event) => { + nodes[1].bump_tx_handler.handle_event(&bump_event); + }, + _ => panic!("Unexpected event"), + } + assert!(nodes[1].tx_broadcaster.txn_broadcast().is_empty()); + let max_coin_selection_weight = TRUC_CHILD_MAX_WEIGHT + - BASE_TX_SIZE * WITNESS_SCALE_FACTOR as u64 + - SEGWIT_MARKER_FLAG_WEIGHT + - BASE_INPUT_WEIGHT + - EMPTY_SCRIPT_SIG_WEIGHT + - EMPTY_WITNESS_WEIGHT + - P2WSH_TXOUT_WEIGHT; + nodes[1].logger.assert_log( + "lightning::events::bump_transaction", + format!( + "Insufficient funds to meet target feerate {} sat/kW while remaining under {} WU", + FEERATE, max_coin_selection_weight + ), + 4, + ); + nodes[1].logger.assert_log( + "lightning::events::bump_transaction", + format!("Failed bumping commitment transaction fee for {}", commitment_txid), + 1, + ); + + let coinbase_tx_b = provide_anchor_reserves(&nodes); + + nodes[1].chain_monitor.chain_monitor.rebroadcast_pending_claims(); + + let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::BumpTransaction(bump_event) => { + nodes[1].bump_tx_handler.handle_event(&bump_event); + }, + _ => panic!("Unexpected event"), + } + let txns = nodes[1].tx_broadcaster.txn_broadcast(); + assert_eq!(txns.len(), 2); + check_spends!(txns[1], txns[0], coinbase_tx_b); + assert!(txns[1].weight().to_wu() < TRUC_CHILD_MAX_WEIGHT); + + assert_eq!(txns[0].compute_txid(), commitment_txid); + assert_eq!(txns[1].input.len(), 2); + assert_eq!(txns[1].output.len(), 1); + nodes[1].logger.assert_log( + "lightning::events::bump_transaction", + format!( + "Insufficient funds to meet target feerate {} sat/kW while remaining under {} WU", + FEERATE, max_coin_selection_weight + ), + 4, + ); + nodes[1].logger.assert_log( + "lightning::events::bump_transaction", + format!("Failed bumping commitment transaction fee for {}", txns[0].compute_txid()), + 1, + ); + nodes[1].logger.assert_log( + "lightning::events::bump_transaction", + format!( + "Broadcasting anchor transaction {} to bump channel close with txid {}", + txns[1].compute_txid(), + txns[0].compute_txid() + ), + 1, + ); +} diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index aacc38e366a..f0f1b8a84cc 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -2155,6 +2155,10 @@ impl TestWalletSource { self.utxos.lock().unwrap().retain(|utxo| utxo.outpoint != outpoint); } + pub fn clear_utxos(&self) { + self.utxos.lock().unwrap().clear(); + } + pub fn sign_tx( &self, mut tx: Transaction, ) -> Result {