Skip to content

Commit c222c41

Browse files
committed
Implement Holder HTLC claim chunking for 0FC channels
Otherwise, we could hit the max 10_000vB size limit on V3 transactions (BIP 431 rule 4).
1 parent 19a9dbd commit c222c41

File tree

3 files changed

+305
-121
lines changed

3 files changed

+305
-121
lines changed

lightning/src/events/bump_transaction/mod.rs

Lines changed: 169 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ use crate::util::logger::Logger;
4141
use bitcoin::amount::Amount;
4242
use bitcoin::consensus::Encodable;
4343
use bitcoin::constants::WITNESS_SCALE_FACTOR;
44+
use bitcoin::hashes::sha256::Hash as Sha256;
45+
use bitcoin::hashes::{Hash, HashEngine};
4446
use bitcoin::locktime::absolute::LockTime;
4547
use bitcoin::secp256k1;
4648
use bitcoin::secp256k1::ecdsa::Signature;
@@ -847,24 +849,13 @@ where
847849
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
848850
/// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
849851
async fn handle_htlc_resolution(
850-
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
851-
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: LockTime,
852+
&self, target_feerate_sat_per_1000_weight: u32, htlc_descriptors: &[HTLCDescriptor],
853+
tx_lock_time: LockTime,
852854
) -> Result<(), ()> {
853855
let channel_type = &htlc_descriptors[0]
854856
.channel_derivation_parameters
855857
.transaction_parameters
856858
.channel_type_features;
857-
let mut htlc_tx = Transaction {
858-
version: if channel_type.supports_anchor_zero_fee_commitments() {
859-
Version::non_standard(3)
860-
} else {
861-
Version::TWO
862-
},
863-
lock_time: tx_lock_time,
864-
input: vec![],
865-
output: vec![],
866-
};
867-
let mut must_spend = Vec::with_capacity(htlc_descriptors.len());
868859
let (htlc_success_witness_weight, htlc_timeout_witness_weight) =
869860
if channel_type.supports_anchor_zero_fee_commitments() {
870861
(
@@ -879,123 +870,181 @@ where
879870
} else {
880871
panic!("channel type should be either zero-fee HTLCs, or zero-fee commitments");
881872
};
882-
for htlc_descriptor in htlc_descriptors {
883-
let htlc_input = htlc_descriptor.unsigned_tx_input();
884-
must_spend.push(Input {
885-
outpoint: htlc_input.previous_output.clone(),
886-
previous_utxo: htlc_descriptor.previous_utxo(&self.secp),
887-
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT
888-
+ if htlc_descriptor.preimage.is_some() {
889-
htlc_success_witness_weight
890-
} else {
891-
htlc_timeout_witness_weight
892-
},
893-
});
894-
htlc_tx.input.push(htlc_input);
895-
let htlc_output = htlc_descriptor.tx_output(&self.secp);
896-
htlc_tx.output.push(htlc_output);
897-
}
898873

899-
log_debug!(
900-
self.logger,
901-
"Performing coin selection for HTLC transaction targeting {} sat/kW",
902-
target_feerate_sat_per_1000_weight
903-
);
874+
let max_weight = if channel_type.supports_anchor_zero_fee_commitments() {
875+
// Cap the size of transactions claiming `HolderHTLCOutput` in 0FC channels.
876+
// Otherwise, we could hit the max 10_000vB size limit on V3 transactions
877+
// (BIP 431 rule 4).
878+
chan_utils::TRUC_MAX_WEIGHT
879+
} else {
880+
u64::MAX
881+
};
882+
let mut broadcasted_htlcs = 0;
883+
let mut batch_size = htlc_descriptors.len() - broadcasted_htlcs;
884+
885+
while broadcasted_htlcs < htlc_descriptors.len() {
886+
let htlcs = &htlc_descriptors[broadcasted_htlcs..broadcasted_htlcs + batch_size];
887+
// Generate a new claim_id to map a user-provided utxo to this
888+
// particular set of HTLCs via `select_confirmed_utxos`.
889+
//
890+
// This matches the scheme used in `onchaintx.rs`, so for non-0fc-channels,
891+
// this should match the `ClaimId` of the claim generated in `onchaintx.rs`.
892+
let mut engine = Sha256::engine();
893+
for htlc in htlcs {
894+
engine.input(&htlc.commitment_txid.to_byte_array());
895+
engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes());
896+
}
897+
let utxo_id = ClaimId(Sha256::from_engine(engine).to_byte_array());
898+
log_info!(
899+
self.logger,
900+
"Batch transaction assigned to UTXO id {} contains {} HTLCs: {}",
901+
log_bytes!(utxo_id.0),
902+
htlcs.len(),
903+
log_iter!(htlcs.iter().map(|d| d.outpoint()))
904+
);
905+
let mut htlc_tx = Transaction {
906+
version: if channel_type.supports_anchor_zero_fee_commitments() {
907+
Version::non_standard(3)
908+
} else {
909+
Version::TWO
910+
},
911+
lock_time: tx_lock_time,
912+
input: vec![],
913+
output: vec![],
914+
};
915+
let mut must_spend = Vec::with_capacity(htlcs.len());
916+
for htlc_descriptor in htlcs {
917+
let htlc_input = htlc_descriptor.unsigned_tx_input();
918+
must_spend.push(Input {
919+
outpoint: htlc_input.previous_output.clone(),
920+
previous_utxo: htlc_descriptor.previous_utxo(&self.secp),
921+
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT
922+
+ if htlc_descriptor.preimage.is_some() {
923+
htlc_success_witness_weight
924+
} else {
925+
htlc_timeout_witness_weight
926+
},
927+
});
928+
htlc_tx.input.push(htlc_input);
929+
let htlc_output = htlc_descriptor.tx_output(&self.secp);
930+
htlc_tx.output.push(htlc_output);
931+
}
904932

905-
#[cfg(debug_assertions)]
906-
let must_spend_satisfaction_weight =
907-
must_spend.iter().map(|input| input.satisfaction_weight).sum::<u64>();
908-
#[cfg(debug_assertions)]
909-
let must_spend_amount =
910-
must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::<u64>();
933+
log_debug!(
934+
self.logger,
935+
"Performing coin selection for HTLC transaction targeting {} sat/kW",
936+
target_feerate_sat_per_1000_weight
937+
);
911938

912-
let coin_selection: CoinSelection = self
913-
.utxo_source
914-
.select_confirmed_utxos(
915-
claim_id,
916-
must_spend,
917-
&htlc_tx.output,
918-
target_feerate_sat_per_1000_weight,
919-
)
920-
.await?;
921-
922-
#[cfg(debug_assertions)]
923-
let input_satisfaction_weight: u64 =
924-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
925-
#[cfg(debug_assertions)]
926-
let total_satisfaction_weight = must_spend_satisfaction_weight + input_satisfaction_weight;
927-
#[cfg(debug_assertions)]
928-
let input_value: u64 =
929-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum();
930-
#[cfg(debug_assertions)]
931-
let total_input_amount = must_spend_amount + input_value;
932-
933-
self.process_coin_selection(&mut htlc_tx, &coin_selection);
934-
935-
// construct psbt
936-
let mut htlc_psbt = Psbt::from_unsigned_tx(htlc_tx).unwrap();
937-
// add witness_utxo to htlc inputs
938-
for (i, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
939-
debug_assert_eq!(
940-
htlc_psbt.unsigned_tx.input[i].previous_output,
941-
htlc_descriptor.outpoint()
939+
let must_spend_satisfaction_weight =
940+
must_spend.iter().map(|input| input.satisfaction_weight).sum::<u64>();
941+
#[cfg(debug_assertions)]
942+
let must_spend_amount =
943+
must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::<u64>();
944+
945+
let coin_selection: CoinSelection = self
946+
.utxo_source
947+
.select_confirmed_utxos(
948+
utxo_id,
949+
must_spend,
950+
&htlc_tx.output,
951+
target_feerate_sat_per_1000_weight,
952+
)
953+
.await?;
954+
955+
let input_satisfaction_weight: u64 =
956+
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum();
957+
let total_satisfaction_weight =
958+
must_spend_satisfaction_weight + input_satisfaction_weight;
959+
960+
#[cfg(debug_assertions)]
961+
let input_value: u64 =
962+
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value.to_sat()).sum();
963+
#[cfg(debug_assertions)]
964+
let total_input_amount = must_spend_amount + input_value;
965+
966+
self.process_coin_selection(&mut htlc_tx, &coin_selection);
967+
968+
let unsigned_tx_weight =
969+
htlc_tx.weight().to_wu() - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
970+
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
971+
if expected_signed_tx_weight >= max_weight {
972+
let extra_weight = expected_signed_tx_weight - max_weight;
973+
let htlcs_to_remove = (extra_weight / chan_utils::htlc_timeout_tx_weight(channel_type)
974+
// If we remove extra_weight / timeout_weight + 1 we sometimes still land above max_weight
975+
+ 2) as usize;
976+
batch_size = batch_size.checked_sub(htlcs_to_remove).ok_or(())?;
977+
continue;
978+
}
979+
broadcasted_htlcs += batch_size;
980+
batch_size = htlc_descriptors.len() - broadcasted_htlcs;
981+
982+
// construct psbt
983+
let mut htlc_psbt = Psbt::from_unsigned_tx(htlc_tx).unwrap();
984+
// add witness_utxo to htlc inputs
985+
for (i, htlc_descriptor) in htlcs.iter().enumerate() {
986+
debug_assert_eq!(
987+
htlc_psbt.unsigned_tx.input[i].previous_output,
988+
htlc_descriptor.outpoint()
989+
);
990+
htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp));
991+
}
992+
993+
// add witness_utxo to remaining inputs
994+
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
995+
// offset to skip the htlc inputs
996+
let index = idx + htlcs.len();
997+
debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
998+
if utxo.output.script_pubkey.is_witness_program() {
999+
htlc_psbt.inputs[index].witness_utxo = Some(utxo.output);
1000+
}
1001+
}
1002+
1003+
log_debug!(
1004+
self.logger,
1005+
"Signing HTLC transaction {}",
1006+
htlc_psbt.unsigned_tx.compute_txid()
9421007
);
943-
htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp));
944-
}
945-
// add witness_utxo to remaining inputs
946-
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
947-
// offset to skip the htlc inputs
948-
let index = idx + htlc_descriptors.len();
949-
debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
950-
if utxo.output.script_pubkey.is_witness_program() {
951-
htlc_psbt.inputs[index].witness_utxo = Some(utxo.output);
1008+
let mut htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?;
1009+
1010+
let mut signers = BTreeMap::new();
1011+
for (idx, htlc_descriptor) in htlcs.iter().enumerate() {
1012+
let keys_id = htlc_descriptor.channel_derivation_parameters.keys_id;
1013+
let signer = signers
1014+
.entry(keys_id)
1015+
.or_insert_with(|| self.signer_provider.derive_channel_signer(keys_id));
1016+
let htlc_sig = signer.sign_holder_htlc_transaction(
1017+
&htlc_tx,
1018+
idx,
1019+
htlc_descriptor,
1020+
&self.secp,
1021+
)?;
1022+
let witness_script = htlc_descriptor.witness_script(&self.secp);
1023+
htlc_tx.input[idx].witness =
1024+
htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
9521025
}
953-
}
9541026

955-
#[cfg(debug_assertions)]
956-
let unsigned_tx_weight = htlc_psbt.unsigned_tx.weight().to_wu()
957-
- (htlc_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
1027+
#[cfg(debug_assertions)]
1028+
{
1029+
let signed_tx_weight = htlc_tx.weight().to_wu();
1030+
// Our estimate should be within a 2% error margin of the actual weight and we should
1031+
// never underestimate.
1032+
assert!(expected_signed_tx_weight >= signed_tx_weight);
1033+
assert!(expected_signed_tx_weight * 98 / 100 <= signed_tx_weight);
9581034

959-
log_debug!(
960-
self.logger,
961-
"Signing HTLC transaction {}",
962-
htlc_psbt.unsigned_tx.compute_txid()
963-
);
964-
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?;
965-
966-
let mut signers = BTreeMap::new();
967-
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
968-
let keys_id = htlc_descriptor.channel_derivation_parameters.keys_id;
969-
let signer = signers
970-
.entry(keys_id)
971-
.or_insert_with(|| self.signer_provider.derive_channel_signer(keys_id));
972-
let htlc_sig =
973-
signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?;
974-
let witness_script = htlc_descriptor.witness_script(&self.secp);
975-
htlc_tx.input[idx].witness =
976-
htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
977-
}
1035+
let expected_signed_tx_fee =
1036+
fee_for_weight(target_feerate_sat_per_1000_weight, signed_tx_weight);
1037+
let signed_tx_fee = total_input_amount
1038+
- htlc_tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
1039+
// Our feerate should always be at least what we were seeking. It may overshoot if
1040+
// the coin selector burned funds to an OP_RETURN without a change output.
1041+
assert!(signed_tx_fee >= expected_signed_tx_fee);
1042+
}
9781043

979-
#[cfg(debug_assertions)]
980-
{
981-
let signed_tx_weight = htlc_tx.weight().to_wu();
982-
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
983-
// Our estimate should be within a 1% error margin of the actual weight and we should
984-
// never underestimate.
985-
assert!(expected_signed_tx_weight >= signed_tx_weight);
986-
assert!(expected_signed_tx_weight * 99 / 100 <= signed_tx_weight);
987-
988-
let expected_signed_tx_fee =
989-
fee_for_weight(target_feerate_sat_per_1000_weight, signed_tx_weight);
990-
let signed_tx_fee = total_input_amount
991-
- htlc_tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
992-
// Our feerate should always be at least what we were seeking. It may overshoot if
993-
// the coin selector burned funds to an OP_RETURN without a change output.
994-
assert!(signed_tx_fee >= expected_signed_tx_fee);
1044+
log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx));
1045+
self.broadcaster.broadcast_transactions(&[&htlc_tx]);
9951046
}
9961047

997-
log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx));
998-
self.broadcaster.broadcast_transactions(&[&htlc_tx]);
9991048
Ok(())
10001049
}
10011050

@@ -1046,7 +1095,6 @@ where
10461095
log_iter!(htlc_descriptors.iter().map(|d| d.outpoint()))
10471096
);
10481097
self.handle_htlc_resolution(
1049-
*claim_id,
10501098
*target_feerate_sat_per_1000_weight,
10511099
htlc_descriptors,
10521100
*tx_lock_time,

lightning/src/ln/chan_utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ pub const P2A_ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 1;
9595
/// The maximum value of a P2A anchor.
9696
pub const P2A_MAX_VALUE: u64 = 240;
9797

98+
/// The maximum weight of a TRUC transaction.
99+
pub const TRUC_MAX_WEIGHT: u64 = 10_000 * bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
100+
98101
/// The upper bound weight of an HTLC timeout input from a commitment transaction with keyed anchor outputs.
99102
pub const HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT: u64 = 288;
100103
/// The upper bound weight of an HTLC timeout input from a commitment transaction with a p2a anchor output.

0 commit comments

Comments
 (0)