Skip to content

Commit ae05cc0

Browse files
committed
Track HolderCommitmentTransaction in HolderFundingOutput
The `ChannelMonitor` and `OnchainTxHandler` have historically been tied together, often tracking some of the same state twice. As we introduce support for splices in the `ChannelMonitor`, we'd like to avoid leaking some of those details to the `OnchainTxHandler`. Ultimately, the `OnchainTxHandler` should stand on its own and support claiming funds from multiple `ChannelMonitor`s, allowing us to save on fees by batching aggregatable claims across multiple in-flight closing channels. Once splices are supported, we may run into cases where we are attempting to claim an output from a specific `FundingScope`, while also having an additional pending `FundingScope` for a splice. If the pending splice confirms over the output claim, we need to cancel the claim and re-offer it with the set of relevant parameters in the new `FundingScope`. This commit tracks the `HolderCommitmentTransaction` for the specific `FundingScope` the `HolderFundingOutput` claim originated from. This allows us to remove the dependency on `OnchainTxHandler` when obtaining the current `HolderCommitmentTransaction` and ensures any alternative commitment transaction state due to splicing does not leak into the `OnchainTxHandler`. As a result, we can now include all the information required to emit a `BumpTransactionEvent::ChannelClose` within its intermediate representation `ClaimEvent::BumpCommitment`, as opposed to relying on the `ChannelMonitor` to provide it.
1 parent 125f6e3 commit ae05cc0

File tree

3 files changed

+88
-49
lines changed

3 files changed

+88
-49
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3278,6 +3278,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
32783278

32793279
fn generate_claimable_outpoints_and_watch_outputs(&mut self, reason: ClosureReason) -> (Vec<PackageTemplate>, Vec<TransactionOutputs>) {
32803280
let funding_outp = HolderFundingOutput::build(
3281+
self.funding.current_holder_commitment.tx.clone(),
32813282
self.funding.redeem_script.clone(),
32823283
self.funding.channel_parameters.channel_value_satoshis,
32833284
self.channel_type_features().clone(),
@@ -3530,16 +3531,12 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35303531
for (claim_id, claim_event) in pending_claim_events {
35313532
match claim_event {
35323533
ClaimEvent::BumpCommitment {
3533-
package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_output_idx,
3534+
package_target_feerate_sat_per_1000_weight, commitment_tx,
3535+
commitment_tx_fee_satoshis, pending_htlcs, anchor_output_idx,
35343536
} => {
35353537
let channel_id = self.channel_id;
35363538
let counterparty_node_id = self.counterparty_node_id;
35373539
let commitment_txid = commitment_tx.compute_txid();
3538-
debug_assert_eq!(self.funding.current_holder_commitment.tx.trust().txid(), commitment_txid);
3539-
let pending_htlcs = self.funding.current_holder_commitment.tx.trust().htlcs().to_vec();
3540-
let channel_value_satoshis = self.funding.channel_parameters.channel_value_satoshis;
3541-
let commitment_tx_fee_satoshis = channel_value_satoshis -
3542-
commitment_tx.output.iter().fold(0u64, |sum, output| sum + output.value.to_sat());
35433540
ret.push(Event::BumpTransaction(BumpTransactionEvent::ChannelClose {
35443541
channel_id,
35453542
counterparty_node_id,
@@ -3550,7 +3547,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35503547
anchor_descriptor: AnchorDescriptor {
35513548
channel_derivation_parameters: ChannelDerivationParameters {
35523549
keys_id: self.channel_keys_id,
3553-
value_satoshis: channel_value_satoshis,
3550+
value_satoshis: self.funding.channel_parameters.channel_value_satoshis,
35543551
transaction_parameters: self.funding.channel_parameters.clone(),
35553552
},
35563553
outpoint: BitcoinOutPoint {

lightning/src/chain/onchaintx.rs

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ pub(crate) enum ClaimEvent {
193193
BumpCommitment {
194194
package_target_feerate_sat_per_1000_weight: u32,
195195
commitment_tx: Transaction,
196+
commitment_tx_fee_satoshis: u64,
197+
pending_htlcs: Vec<HTLCOutputInCommitment>,
196198
anchor_output_idx: u32,
197199
},
198200
/// Event yielded to signal that the commitment transaction has confirmed and its HTLCs must be
@@ -641,37 +643,55 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
641643
// which require external funding.
642644
let mut inputs = cached_request.inputs();
643645
debug_assert_eq!(inputs.len(), 1);
644-
let tx = match cached_request.maybe_finalize_untractable_package(self, logger) {
645-
Some(tx) => tx,
646-
None => return None,
647-
};
646+
648647
if !cached_request.requires_external_funding() {
649-
return Some((new_timer, 0, OnchainClaim::Tx(tx)));
648+
return cached_request.maybe_finalize_untractable_package(self, logger)
649+
.map(|tx| (new_timer, 0, OnchainClaim::Tx(tx)))
650650
}
651+
651652
return inputs.find_map(|input| match input {
652653
// Commitment inputs with anchors support are the only untractable inputs supported
653654
// thus far that require external funding.
654655
PackageSolvingData::HolderFundingOutput(output) => {
655-
debug_assert_eq!(tx.0.compute_txid(), self.holder_commitment.trust().txid(),
656-
"Holder commitment transaction mismatch");
656+
let maybe_signed_commitment_tx = output.get_maybe_signed_commitment_tx(self);
657+
let tx = if maybe_signed_commitment_tx.is_fully_signed() {
658+
maybe_signed_commitment_tx.0
659+
} else {
660+
// We couldn't sign the commitment as the signer was unavailable, but we
661+
// should still retry it later. We return the unsigned transaction anyway to
662+
// register the claim.
663+
return Some((new_timer, 0, OnchainClaim::Tx(maybe_signed_commitment_tx)));
664+
};
665+
666+
let holder_commitment = output.commitment_tx.as_ref()
667+
.unwrap_or(self.current_holder_commitment_tx());
657668

669+
let input_amount_sat = if let Some(funding_amount) = output.funding_amount {
670+
funding_amount
671+
} else {
672+
debug_assert!(false, "Funding amount should always exist for anchor-based claims");
673+
self.channel_value_satoshis
674+
};
675+
676+
let fee_sat = input_amount_sat - tx.output.iter()
677+
.map(|output| output.value.to_sat()).sum::<u64>();
678+
let commitment_tx_feerate_sat_per_1000_weight =
679+
compute_feerate_sat_per_1000_weight(fee_sat, tx.weight().to_wu());
658680
let package_target_feerate_sat_per_1000_weight = cached_request
659681
.compute_package_feerate(fee_estimator, conf_target, feerate_strategy);
660-
if let Some(input_amount_sat) = output.funding_amount {
661-
let fee_sat = input_amount_sat - tx.0.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
662-
let commitment_tx_feerate_sat_per_1000_weight =
663-
compute_feerate_sat_per_1000_weight(fee_sat, tx.0.weight().to_wu());
664-
if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
665-
log_debug!(logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW",
666-
tx.0.compute_txid(), commitment_tx_feerate_sat_per_1000_weight,
667-
package_target_feerate_sat_per_1000_weight);
668-
return Some((new_timer, 0, OnchainClaim::Tx(tx.clone())));
669-
}
682+
if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
683+
log_debug!(logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW",
684+
tx.compute_txid(), commitment_tx_feerate_sat_per_1000_weight,
685+
package_target_feerate_sat_per_1000_weight);
686+
// The commitment transaction already meets the required feerate and doesn't
687+
// need a CPFP. We still want to return something other than the event to
688+
// register the claim.
689+
return Some((new_timer, 0, OnchainClaim::Tx(MaybeSignedTransaction(tx))));
670690
}
671691

672692
// We'll locate an anchor output we can spend within the commitment transaction.
673693
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
674-
match chan_utils::get_keyed_anchor_output(&tx.0, funding_pubkey) {
694+
match chan_utils::get_keyed_anchor_output(&tx, funding_pubkey) {
675695
// An anchor output was found, so we should yield a funding event externally.
676696
Some((idx, _)) => {
677697
// TODO: Use a lower confirmation target when both our and the
@@ -681,7 +701,9 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
681701
package_target_feerate_sat_per_1000_weight as u64,
682702
OnchainClaim::Event(ClaimEvent::BumpCommitment {
683703
package_target_feerate_sat_per_1000_weight,
684-
commitment_tx: tx.0.clone(),
704+
commitment_tx: tx,
705+
pending_htlcs: holder_commitment.htlcs().to_vec(),
706+
commitment_tx_fee_satoshis: fee_sat,
685707
anchor_output_idx: idx,
686708
}),
687709
))
@@ -690,7 +712,7 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
690712
// attempt to broadcast the transaction with its current fee rate and hope
691713
// it confirms. This is essentially the same behavior as a commitment
692714
// transaction without anchor outputs.
693-
None => Some((new_timer, 0, OnchainClaim::Tx(tx.clone()))),
715+
None => Some((new_timer, 0, OnchainClaim::Tx(MaybeSignedTransaction(tx)))),
694716
}
695717
},
696718
_ => {
@@ -1193,17 +1215,6 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
11931215
self.prev_holder_commitment = Some(replace(&mut self.holder_commitment, tx));
11941216
}
11951217

1196-
pub(crate) fn get_unsigned_holder_commitment_tx(&self) -> &Transaction {
1197-
&self.holder_commitment.trust().built_transaction().transaction
1198-
}
1199-
1200-
pub(crate) fn get_maybe_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> MaybeSignedTransaction {
1201-
let tx = self.signer.sign_holder_commitment(&self.channel_transaction_parameters, &self.holder_commitment, &self.secp_ctx)
1202-
.map(|sig| self.holder_commitment.add_holder_sig(funding_redeemscript, sig))
1203-
.unwrap_or_else(|_| self.get_unsigned_holder_commitment_tx().clone());
1204-
MaybeSignedTransaction(tx)
1205-
}
1206-
12071218
#[cfg(any(test, feature="_test_utils", feature="unsafe_revoked_tx_signing"))]
12081219
pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction {
12091220
let sig = self.signer.unsafe_sign_holder_commitment(&self.channel_transaction_parameters, &self.holder_commitment, &self.secp_ctx).expect("sign holder commitment");
@@ -1410,7 +1421,7 @@ mod tests {
14101421
let logger = TestLogger::new();
14111422

14121423
// Request claiming of each HTLC on the holder's commitment, with current block height 1.
1413-
let holder_commit_txid = tx_handler.get_unsigned_holder_commitment_tx().compute_txid();
1424+
let holder_commit_txid = tx_handler.current_holder_commitment_tx().trust().txid();
14141425
let mut requests = Vec::new();
14151426
for (htlc, _) in htlcs {
14161427
requests.push(PackageTemplate::build_package(

lightning/src/chain/package.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ use bitcoin::transaction::{TxOut,TxIn, Transaction};
2020
use bitcoin::transaction::OutPoint as BitcoinOutPoint;
2121
use bitcoin::script::{Script, ScriptBuf};
2222
use bitcoin::hash_types::Txid;
23-
use bitcoin::secp256k1::{SecretKey,PublicKey};
23+
use bitcoin::secp256k1::{SecretKey, PublicKey};
2424
use bitcoin::sighash::EcdsaSighashType;
2525
use bitcoin::transaction::Version;
2626

2727
use crate::types::payment::PaymentPreimage;
28-
use crate::ln::chan_utils::{self, TxCreationKeys, HTLCOutputInCommitment};
28+
use crate::ln::chan_utils::{
29+
self, HolderCommitmentTransaction, TxCreationKeys, HTLCOutputInCommitment,
30+
};
2931
use crate::types::features::ChannelTypeFeatures;
3032
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint};
3133
use crate::ln::channelmanager::MIN_CLTV_EXPIRY_DELTA;
@@ -439,20 +441,42 @@ impl Readable for HolderHTLCOutput {
439441
/// Note that on upgrades, some features of existing outputs may be missed.
440442
#[derive(Clone, PartialEq, Eq)]
441443
pub(crate) struct HolderFundingOutput {
442-
funding_redeemscript: ScriptBuf,
444+
pub(crate) funding_redeemscript: ScriptBuf,
443445
pub(crate) funding_amount: Option<u64>,
444446
channel_type_features: ChannelTypeFeatures,
447+
pub(crate) commitment_tx: Option<HolderCommitmentTransaction>,
445448
}
446449

447450

448451
impl HolderFundingOutput {
449-
pub(crate) fn build(funding_redeemscript: ScriptBuf, funding_amount: u64, channel_type_features: ChannelTypeFeatures) -> Self {
452+
pub(crate) fn build(
453+
commitment_tx: HolderCommitmentTransaction, funding_redeemscript: ScriptBuf,
454+
funding_amount: u64, channel_type_features: ChannelTypeFeatures,
455+
) -> Self {
450456
HolderFundingOutput {
451457
funding_redeemscript,
452458
funding_amount: Some(funding_amount),
453459
channel_type_features,
460+
commitment_tx: Some(commitment_tx),
454461
}
455462
}
463+
464+
pub(crate) fn get_maybe_signed_commitment_tx<Signer: EcdsaChannelSigner>(
465+
&self, onchain_tx_handler: &mut OnchainTxHandler<Signer>,
466+
) -> MaybeSignedTransaction {
467+
let channel_parameters = &onchain_tx_handler.channel_transaction_parameters;
468+
let commitment_tx = self.commitment_tx.as_ref()
469+
.unwrap_or(onchain_tx_handler.current_holder_commitment_tx());
470+
let maybe_signed_tx = onchain_tx_handler.signer
471+
.sign_holder_commitment(channel_parameters, commitment_tx, &onchain_tx_handler.secp_ctx)
472+
.map(|holder_sig| {
473+
commitment_tx.add_holder_sig(&self.funding_redeemscript, holder_sig)
474+
})
475+
.unwrap_or_else(|_| {
476+
commitment_tx.trust().built_transaction().transaction.clone()
477+
});
478+
MaybeSignedTransaction(maybe_signed_tx)
479+
}
456480
}
457481

458482
impl Writeable for HolderFundingOutput {
@@ -463,6 +487,7 @@ impl Writeable for HolderFundingOutput {
463487
(1, self.channel_type_features, required),
464488
(2, legacy_deserialization_prevention_marker, option),
465489
(3, self.funding_amount, option),
490+
(5, self.commitment_tx, option), // Added in 0.2
466491
});
467492
Ok(())
468493
}
@@ -474,20 +499,23 @@ impl Readable for HolderFundingOutput {
474499
let mut _legacy_deserialization_prevention_marker: Option<()> = None;
475500
let mut channel_type_features = None;
476501
let mut funding_amount = None;
502+
let mut commitment_tx = None;
477503

478504
read_tlv_fields!(reader, {
479505
(0, funding_redeemscript, required),
480506
(1, channel_type_features, option),
481507
(2, _legacy_deserialization_prevention_marker, option),
482508
(3, funding_amount, option),
509+
(5, commitment_tx, option), // Added in 0.2.
483510
});
484511

485512
verify_channel_type_features(&channel_type_features, None)?;
486513

487514
Ok(Self {
488515
funding_redeemscript: funding_redeemscript.0.unwrap(),
489516
channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()),
490-
funding_amount
517+
funding_amount,
518+
commitment_tx,
491519
})
492520
}
493521
}
@@ -716,7 +744,7 @@ impl PackageSolvingData {
716744
onchain_handler.get_maybe_signed_htlc_tx(outpoint, &outp.preimage)
717745
}
718746
PackageSolvingData::HolderFundingOutput(ref outp) => {
719-
Some(onchain_handler.get_maybe_signed_holder_tx(&outp.funding_redeemscript))
747+
Some(outp.get_maybe_signed_commitment_tx(onchain_handler))
720748
}
721749
_ => { panic!("API Error!"); }
722750
}
@@ -1406,7 +1434,7 @@ where
14061434
mod tests {
14071435
use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageTemplate, PackageSolvingData, RevokedHTLCOutput, RevokedOutput, WEIGHT_REVOKED_OUTPUT, weight_offered_htlc, weight_received_htlc, feerate_bump};
14081436
use crate::chain::Txid;
1409-
use crate::ln::chan_utils::HTLCOutputInCommitment;
1437+
use crate::ln::chan_utils::{HolderCommitmentTransaction, HTLCOutputInCommitment};
14101438
use crate::types::payment::{PaymentPreimage, PaymentHash};
14111439
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint};
14121440

@@ -1508,9 +1536,12 @@ mod tests {
15081536
}
15091537

15101538
macro_rules! dumb_funding_output {
1511-
() => {
1512-
PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build(ScriptBuf::new(), 0, ChannelTypeFeatures::only_static_remote_key()))
1513-
}
1539+
() => {{
1540+
let commitment_tx = HolderCommitmentTransaction::dummy(0, &mut Vec::new());
1541+
PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build(
1542+
commitment_tx, ScriptBuf::new(), 0, ChannelTypeFeatures::only_static_remote_key()
1543+
))
1544+
}}
15141545
}
15151546

15161547
#[test]

0 commit comments

Comments
 (0)