Skip to content

Commit d383ac4

Browse files
committed
Handle under-coin-selecting due to an added OP_RETURN output
When we do coin selection for channel close anchor spends, we may do coin selection targeting exactly the input values we need. However, if coin selection does not include a change output, we may add an OP_RETURN output, which may cause us to end up with less fee than we wanted on the resulting package. Here we address this issue by running coin selection twice - first without seeking the extra weight of the OP_RETURN output, and again if we find that we under-selected.
1 parent ff00c63 commit d383ac4

File tree

1 file changed

+91
-72
lines changed

1 file changed

+91
-72
lines changed

lightning/src/events/bump_transaction.rs

Lines changed: 91 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -617,85 +617,104 @@ where
617617
let mut anchor_utxo = anchor_descriptor.previous_utxo();
618618
let commitment_tx_fee_sat = Amount::from_sat(commitment_tx_fee_sat);
619619
anchor_utxo.value += commitment_tx_fee_sat;
620-
let must_spend = vec![Input {
621-
outpoint: anchor_descriptor.outpoint,
622-
previous_utxo: anchor_utxo,
623-
satisfaction_weight: commitment_tx.weight().to_wu() + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
624-
}];
625-
#[cfg(debug_assertions)]
626-
let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::<Amount>();
627-
628-
log_debug!(self.logger, "Performing coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
629-
package_target_feerate_sat_per_1000_weight);
630-
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
631-
claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight,
632-
)?;
633-
634-
let mut anchor_tx = Transaction {
635-
version: Version::TWO,
636-
lock_time: LockTime::ZERO, // TODO: Use next best height.
637-
input: vec![anchor_descriptor.unsigned_tx_input()],
638-
output: vec![],
639-
};
640-
641-
#[cfg(debug_assertions)]
642-
let total_satisfaction_weight = ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT +
643-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>();
644-
#[cfg(debug_assertions)]
645-
let total_input_amount = must_spend_amount +
646-
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum();
647-
648-
self.process_coin_selection(&mut anchor_tx, &coin_selection);
649-
let anchor_txid = anchor_tx.compute_txid();
620+
let starting_package_and_fixed_input_satisfaction_weight =
621+
commitment_tx.weight().to_wu() + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT;
622+
let mut package_and_fixed_input_satisfaction_weight =
623+
starting_package_and_fixed_input_satisfaction_weight;
624+
625+
loop {
626+
let must_spend = vec![Input {
627+
outpoint: anchor_descriptor.outpoint,
628+
previous_utxo: anchor_utxo.clone(),
629+
satisfaction_weight: package_and_fixed_input_satisfaction_weight,
630+
}];
631+
let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::<Amount>();
632+
633+
log_debug!(self.logger, "Performing coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
634+
package_target_feerate_sat_per_1000_weight);
635+
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
636+
claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight,
637+
)?;
638+
639+
let mut anchor_tx = Transaction {
640+
version: Version::TWO,
641+
lock_time: LockTime::ZERO, // TODO: Use next best height.
642+
input: vec![anchor_descriptor.unsigned_tx_input()],
643+
output: vec![],
644+
};
650645

651-
// construct psbt
652-
let mut anchor_psbt = Psbt::from_unsigned_tx(anchor_tx).unwrap();
653-
// add witness_utxo to anchor input
654-
anchor_psbt.inputs[0].witness_utxo = Some(anchor_descriptor.previous_utxo());
655-
// add witness_utxo to remaining inputs
656-
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
657-
// add 1 to skip the anchor input
658-
let index = idx + 1;
659-
debug_assert_eq!(anchor_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
660-
if utxo.output.script_pubkey.is_witness_program() {
661-
anchor_psbt.inputs[index].witness_utxo = Some(utxo.output);
646+
let total_satisfaction_weight = ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT +
647+
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>();
648+
let total_input_amount = must_spend_amount +
649+
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum();
650+
651+
self.process_coin_selection(&mut anchor_tx, &coin_selection);
652+
let anchor_txid = anchor_tx.compute_txid();
653+
654+
// construct psbt
655+
let mut anchor_psbt = Psbt::from_unsigned_tx(anchor_tx).unwrap();
656+
// add witness_utxo to anchor input
657+
anchor_psbt.inputs[0].witness_utxo = Some(anchor_descriptor.previous_utxo());
658+
// add witness_utxo to remaining inputs
659+
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
660+
// add 1 to skip the anchor input
661+
let index = idx + 1;
662+
debug_assert_eq!(anchor_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
663+
if utxo.output.script_pubkey.is_witness_program() {
664+
anchor_psbt.inputs[index].witness_utxo = Some(utxo.output);
665+
}
662666
}
663-
}
664-
665-
debug_assert_eq!(anchor_psbt.unsigned_tx.output.len(), 1);
666-
#[cfg(debug_assertions)]
667-
let unsigned_tx_weight = anchor_psbt.unsigned_tx.weight().to_wu() - (anchor_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
668667

669-
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
670-
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?;
668+
debug_assert_eq!(anchor_psbt.unsigned_tx.output.len(), 1);
669+
let unsigned_tx_weight = anchor_psbt.unsigned_tx.weight().to_wu() - (anchor_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
671670

672-
let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
673-
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
674-
anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig);
671+
let package_fee = total_input_amount -
672+
anchor_psbt.unsigned_tx.output.iter().map(|output| output.value).sum();
673+
let package_weight = unsigned_tx_weight + total_satisfaction_weight + commitment_tx.weight().to_wu();
674+
if package_fee.to_sat() * 1000 / package_weight < package_target_feerate_sat_per_1000_weight.into() {
675+
// On the first iteration of the loop, we may undershoot the target feerate because
676+
// we had to add an OP_RETURN output in `process_coin_selection` which we didn't
677+
// select sufficient coins for. Here we detect that case and go around again
678+
// seeking additional weight.
679+
if package_and_fixed_input_satisfaction_weight == starting_package_and_fixed_input_satisfaction_weight {
680+
debug_assert!(anchor_psbt.unsigned_tx.output[0].script_pubkey.is_op_return(),
681+
"Coin selection failed to select sufficient coins for its change output");
682+
package_and_fixed_input_satisfaction_weight += anchor_psbt.unsigned_tx.output[0].weight().to_wu();
683+
continue;
684+
} else {
685+
debug_assert!(false, "Coin selection failed to select sufficient coins");
686+
}
687+
}
675688

676-
#[cfg(debug_assertions)] {
677-
let signed_tx_weight = anchor_tx.weight().to_wu();
678-
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
679-
// Our estimate should be within a 1% error margin of the actual weight and we should
680-
// never underestimate.
681-
assert!(expected_signed_tx_weight >= signed_tx_weight &&
682-
expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
689+
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
690+
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?;
691+
692+
let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
693+
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
694+
anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig);
695+
696+
#[cfg(debug_assertions)] {
697+
let signed_tx_weight = anchor_tx.weight().to_wu();
698+
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
699+
// Our estimate should be within a 1% error margin of the actual weight and we should
700+
// never underestimate.
701+
assert!(expected_signed_tx_weight >= signed_tx_weight &&
702+
expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
703+
704+
let expected_package_fee = Amount::from_sat(fee_for_weight(package_target_feerate_sat_per_1000_weight,
705+
signed_tx_weight + commitment_tx.weight().to_wu()));
706+
// Our fee should be within a 5% error margin of the expected fee based on the
707+
// feerate and transaction weight and we should never pay less than required.
708+
let fee_error_margin = expected_package_fee * 5 / 100;
709+
assert!(package_fee >= expected_package_fee &&
710+
package_fee - fee_error_margin <= expected_package_fee);
711+
}
683712

684-
let expected_package_fee = Amount::from_sat(fee_for_weight(package_target_feerate_sat_per_1000_weight,
685-
signed_tx_weight + commitment_tx.weight().to_wu()));
686-
let package_fee = total_input_amount -
687-
anchor_tx.output.iter().map(|output| output.value).sum();
688-
// Our fee should be within a 5% error margin of the expected fee based on the
689-
// feerate and transaction weight and we should never pay less than required.
690-
let fee_error_margin = expected_package_fee * 5 / 100;
691-
assert!(package_fee >= expected_package_fee &&
692-
package_fee - fee_error_margin <= expected_package_fee);
713+
log_info!(self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}",
714+
anchor_txid, commitment_tx.compute_txid());
715+
self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
716+
return Ok(());
693717
}
694-
695-
log_info!(self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}",
696-
anchor_txid, commitment_tx.compute_txid());
697-
self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
698-
Ok(())
699718
}
700719

701720
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a

0 commit comments

Comments
 (0)