diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 2a6d3d23e80..d0aa9c542b0 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,14 @@ 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()) + } +} 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..cb991cbfc4e 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -364,6 +364,8 @@ 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. + /// + /// Note that in 0FC channels, the anchor transaction has a max size of 1000vB. 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, @@ -833,6 +835,12 @@ where assert!(package_fee >= expected_package_fee); } + #[cfg(debug_assertions)] + if channel_type.supports_anchor_zero_fee_commitments() { + assert!(commitment_tx.weight().to_wu() < chan_utils::MAX_TRUC_WEIGHT); + assert!(anchor_tx.weight().to_wu() < chan_utils::UNCONF_ANCESTOR_MAX_TRUC_WEIGHT); + } + log_info!( self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}", @@ -854,17 +862,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 +876,203 @@ 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_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). + chan_utils::MAX_TRUC_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. + chan_utils::MAX_STANDARD_TX_WEIGHT + }; + // A 1-input 1-output transaction, both p2wpkh is 438 WU. + // If the coins go above this limit, it's ok we pop a few HTLCs and try again. + const USER_COINS_WEIGHT_BUDGET: u64 = 1000; - #[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 mut broadcasted_htlcs = 0; + let mut batch_size = htlc_descriptors.len() - broadcasted_htlcs; - 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() - ); - 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); + 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::htlc_success_input_output_pair_weight(channel_type) + } else { + chan_utils::htlc_timeout_input_output_pair_weight(channel_type) + }; + if htlc_weight_sum + input_output_weight >= max_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]; + + let utxo_id = if broadcasted_htlcs == 0 { + claim_id + } else { + // Generate a new claim_id to map a user-provided utxo to this + // particular set of HTLCs via `select_confirmed_utxos`. + ClaimId::from_htlcs(&selected_htlcs) + }; - #[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); + 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())) + ); - 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); - } + log_debug!( + self.logger, + "Performing coin selection for HTLC transaction targeting {} sat/kW", + target_feerate_sat_per_1000_weight + ); - #[cfg(debug_assertions)] - { - let signed_tx_weight = htlc_tx.weight().to_wu(); + 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 = self + .utxo_source + .select_confirmed_utxos( + utxo_id, + must_spend, + &htlc_tx.output, + target_feerate_sat_per_1000_weight, + ) + .await?; + + let input_satisfaction_weight: u64 = + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum(); + 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); + + let unsigned_tx_weight = + htlc_tx.weight().to_wu() - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); 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); + if expected_signed_tx_weight >= max_weight { + let extra_weight = expected_signed_tx_weight - max_weight; + let htlcs_to_remove = extra_weight + / chan_utils::htlc_timeout_input_output_pair_weight(channel_type) + + 1; + batch_size = batch_size.checked_sub(htlcs_to_remove as usize).ok_or(())?; + continue; + } + broadcasted_htlcs += batch_size; + batch_size = htlc_descriptors.len() - broadcasted_htlcs; + + // 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)); + } + + // 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() + ); + let mut 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(); + // 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); + } + + 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(()) } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 373d0691564..5c5664440fa 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; @@ -95,6 +96,15 @@ pub const P2A_ANCHOR_INPUT_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 MAX_TRUC_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 UNCONF_ANCESTOR_MAX_TRUC_WEIGHT: u64 = 1000 * WITNESS_SCALE_FACTOR as u64; + +/// The maximum weight of any standard transaction, see bitcoin/src/policy/policy.h +pub const MAX_STANDARD_TX_WEIGHT: u64 = 400_000; + /// 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. @@ -134,6 +144,15 @@ 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 HTLC-success transactions +pub fn htlc_success_input_output_pair_weight(channel_type_features: &ChannelTypeFeatures) -> u64 { + const TXIN_WEIGHT: u64 = 41 * WITNESS_SCALE_FACTOR as u64; + const TXOUT_WEIGHT: u64 = 43 * WITNESS_SCALE_FACTOR as u64; + let witness_weight = + if channel_type_features.supports_anchors_zero_fee_htlc_tx() { 327 } else { 324 }; + TXIN_WEIGHT + TXOUT_WEIGHT + witness_weight +} + /// Gets the weight for an HTLC-Timeout transaction. #[inline] #[rustfmt::skip] @@ -143,6 +162,15 @@ 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 HTLC-timeout transactions +pub fn htlc_timeout_input_output_pair_weight(channel_type_features: &ChannelTypeFeatures) -> u64 { + const TXIN_WEIGHT: u64 = 41 * WITNESS_SCALE_FACTOR as u64; + const TXOUT_WEIGHT: u64 = 43 * WITNESS_SCALE_FACTOR as u64; + let witness_weight = + if channel_type_features.supports_anchors_zero_fee_htlc_tx() { 288 } else { 285 }; + TXIN_WEIGHT + TXOUT_WEIGHT + witness_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/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 6bde99bb59b..fbef5d2fe09 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, MAX_TRUC_WEIGHT, +}; use crate::ln::channelmanager::{ AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, RecipientOnionFields, MIN_CLTV_EXPIRY_DELTA, @@ -1941,6 +1943,9 @@ 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() <= MAX_TRUC_WEIGHT); + } 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/zero_fee_commitment_tests.rs b/lightning/src/ln/zero_fee_commitment_tests.rs index 9d915785c0f..8b6f57ab278 100644 --- a/lightning/src/ln/zero_fee_commitment_tests.rs +++ b/lightning/src/ln/zero_fee_commitment_tests.rs @@ -1,5 +1,7 @@ -use crate::ln::chan_utils::shared_anchor_script_pubkey; +use crate::events::{ClosureReason, Event}; +use crate::ln::chan_utils; use crate::ln::functional_test_utils::*; +use crate::ln::msgs::BaseMessageHandler; #[test] fn test_p2a_anchor_values_under_trims_and_rounds() { @@ -46,10 +48,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 +94,130 @@ 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() { + 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_reserves(&nodes); + + 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(), 60); + assert_eq!(htlc_claims[0].output.len(), 60); + assert_eq!(htlc_claims[1].input.len(), 17); + assert_eq!(htlc_claims[1].output.len(), 17); + + 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], htlc_claims[0]); + assert_eq!(fresh_htlc_claims[0].input.len(), 17); + assert_eq!(fresh_htlc_claims[0].output.len(), 17); + + let log_entries = &nodes[1].logger.lines.lock().unwrap(); + let mut keys: Vec<_> = log_entries + .keys() + .filter(|key| key.1.contains("Batch transaction assigned to UTXO id")) + .collect(); + assert_eq!(keys.len(), 3); + keys.sort_unstable(); + assert_eq!(keys[0].1.split_whitespace().nth(6), keys[1].1.split_whitespace().nth(6)); + assert_ne!(keys[0].1.split_whitespace().nth(6), keys[2].1.split_whitespace().nth(6)); + let values = [ + *log_entries.get(keys[0]).unwrap(), + *log_entries.get(keys[1]).unwrap(), + *log_entries.get(keys[2]).unwrap(), + ]; + assert!(values.iter().all(|&value| value == 1)); +}