Skip to content

Commit 02b876d

Browse files
committed
Add debug assertions for weight estimates of bump transactions
This ensures our estimates are correct by never underestimating and only allowing overestimations by a margin of 1%.
1 parent ddd3a9c commit 02b876d

File tree

1 file changed

+53
-48
lines changed

1 file changed

+53
-48
lines changed

lightning/src/events/bump_transaction.rs

+53-48
Original file line numberDiff line numberDiff line change
@@ -668,31 +668,6 @@ where
668668
}
669669
}
670670

671-
/// Returns an unsigned transaction spending an anchor output of the commitment transaction, and
672-
/// any additional UTXOs sourced, to bump the commitment transaction's fee.
673-
fn build_anchor_tx(
674-
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
675-
commitment_tx: &Transaction, anchor_descriptor: &AnchorDescriptor,
676-
) -> Result<Transaction, ()> {
677-
let must_spend = vec![Input {
678-
outpoint: anchor_descriptor.outpoint,
679-
previous_utxo: anchor_descriptor.previous_utxo(),
680-
satisfaction_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
681-
}];
682-
let coin_selection = self.utxo_source.select_confirmed_utxos(
683-
claim_id, &must_spend, &[], target_feerate_sat_per_1000_weight,
684-
)?;
685-
686-
let mut tx = Transaction {
687-
version: 2,
688-
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
689-
input: vec![anchor_descriptor.unsigned_tx_input()],
690-
output: vec![],
691-
};
692-
self.process_coin_selection(&mut tx, coin_selection);
693-
Ok(tx)
694-
}
695-
696671
/// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
697672
/// transaction spending an anchor output of the commitment transaction to bump its fee and
698673
/// broadcasts them to the network as a package.
@@ -713,27 +688,55 @@ where
713688
FEERATE_FLOOR_SATS_PER_KW,
714689
);
715690

716-
let mut anchor_tx = self.build_anchor_tx(
717-
claim_id, anchor_target_feerate_sat_per_1000_weight, commitment_tx, anchor_descriptor,
691+
let must_spend = vec![Input {
692+
outpoint: anchor_descriptor.outpoint,
693+
previous_utxo: anchor_descriptor.previous_utxo(),
694+
satisfaction_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
695+
}];
696+
let coin_selection = self.utxo_source.select_confirmed_utxos(
697+
claim_id, &must_spend, &[], anchor_target_feerate_sat_per_1000_weight,
718698
)?;
699+
700+
let mut anchor_tx = Transaction {
701+
version: 2,
702+
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
703+
input: vec![anchor_descriptor.unsigned_tx_input()],
704+
output: vec![],
705+
};
706+
#[cfg(debug_assertions)]
707+
let total_satisfaction_weight =
708+
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>() +
709+
ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT;
710+
711+
self.process_coin_selection(&mut anchor_tx, coin_selection);
712+
719713
debug_assert_eq!(anchor_tx.output.len(), 1);
714+
#[cfg(debug_assertions)]
715+
let unsigned_tx_weight = anchor_tx.weight() as u64 - (anchor_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
720716

721717
self.utxo_source.sign_tx(&mut anchor_tx)?;
722718
let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
723719
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
724720
anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig);
725721

722+
#[cfg(debug_assertions)] {
723+
let signed_tx_weight = anchor_tx.weight() as u64;
724+
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
725+
// Our estimate should be within a 1% error margin of the actual weight.
726+
assert!(expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
727+
}
728+
726729
self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
727730
Ok(())
728731
}
729732

730-
/// Returns an unsigned, fee-bumped HTLC transaction, along with the set of signers required to
731-
/// fulfill the witness for each HTLC input within it.
732-
fn build_htlc_tx(
733+
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
734+
/// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
735+
fn handle_htlc_resolution(
733736
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
734737
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
735-
) -> Result<Transaction, ()> {
736-
let mut tx = Transaction {
738+
) -> Result<(), ()> {
739+
let mut htlc_tx = Transaction {
737740
version: 2,
738741
lock_time: tx_lock_time,
739742
input: vec![],
@@ -751,27 +754,22 @@ where
751754
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
752755
},
753756
});
754-
tx.input.push(htlc_input);
757+
htlc_tx.input.push(htlc_input);
755758
let htlc_output = htlc_descriptor.tx_output(&self.secp);
756-
tx.output.push(htlc_output);
759+
htlc_tx.output.push(htlc_output);
757760
}
758761

759762
let coin_selection = self.utxo_source.select_confirmed_utxos(
760-
claim_id, &must_spend, &tx.output, target_feerate_sat_per_1000_weight,
763+
claim_id, &must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight,
761764
)?;
762-
self.process_coin_selection(&mut tx, coin_selection);
763-
Ok(tx)
764-
}
765+
#[cfg(debug_assertions)]
766+
let total_satisfaction_weight =
767+
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>() +
768+
must_spend.iter().map(|input| input.satisfaction_weight).sum::<u64>();
769+
self.process_coin_selection(&mut htlc_tx, coin_selection);
765770

766-
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
767-
/// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
768-
fn handle_htlc_resolution(
769-
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
770-
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
771-
) -> Result<(), ()> {
772-
let mut htlc_tx = self.build_htlc_tx(
773-
claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
774-
)?;
771+
#[cfg(debug_assertions)]
772+
let unsigned_tx_weight = htlc_tx.weight() as u64 - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
775773

776774
self.utxo_source.sign_tx(&mut htlc_tx)?;
777775
let mut signers = BTreeMap::new();
@@ -783,6 +781,13 @@ where
783781
htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
784782
}
785783

784+
#[cfg(debug_assertions)] {
785+
let signed_tx_weight = htlc_tx.weight() as u64;
786+
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
787+
// Our estimate should be within a 1% error margin of the actual weight.
788+
assert!(expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
789+
}
790+
786791
self.broadcaster.broadcast_transactions(&[&htlc_tx]);
787792
Ok(())
788793
}
@@ -792,7 +797,7 @@ where
792797
match event {
793798
BumpTransactionEvent::ChannelClose {
794799
claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx,
795-
anchor_descriptor, commitment_tx_fee_satoshis, ..
800+
commitment_tx_fee_satoshis, anchor_descriptor, ..
796801
} => {
797802
if let Err(_) = self.handle_channel_close(
798803
*claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,

0 commit comments

Comments
 (0)