@@ -262,6 +262,19 @@ impl_writeable_tlv_based!(HTLCUpdate, {
262262 ( 4 , payment_preimage, option) ,
263263} ) ;
264264
265+ /// A signed justice transaction ready for broadcast or watchtower submission.
266+ ///
267+ /// Returned by [`ChannelMonitor::get_justice_txs`].
268+ #[ derive( Clone , Debug ) ]
269+ pub struct JusticeTransaction {
270+ /// The fully signed justice transaction.
271+ pub tx : Transaction ,
272+ /// The txid of the revoked counterparty commitment transaction.
273+ pub revoked_commitment_txid : Txid ,
274+ /// The commitment number of the revoked commitment transaction.
275+ pub commitment_number : u64 ,
276+ }
277+
265278/// If an output goes from claimable only by us to claimable by us or our counterparty within this
266279/// many blocks, we consider it pinnable for the purposes of aggregating claims in a single
267280/// transaction.
@@ -1372,6 +1385,13 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
13721385 /// we now provide the transaction outright.
13731386 initial_counterparty_commitment_tx : Option < CommitmentTransaction > ,
13741387
1388+ /// The latest counterparty commitment transaction(s), stored so that justice
1389+ /// transactions can be built and signed in a single call via [`ChannelMonitor::get_justice_txs`].
1390+ /// Contains the current and previous counterparty commitment(s). With splicing,
1391+ /// there may be multiple entries per commitment number (one per funding scope).
1392+ /// Pruned to remove entries more than one revocation old.
1393+ latest_counterparty_commitment_txs : Vec < CommitmentTransaction > ,
1394+
13751395 /// The first block height at which we had no remaining claimable balances.
13761396 balances_empty_height : Option < u32 > ,
13771397
@@ -1755,6 +1775,7 @@ pub(crate) fn write_chanmon_internal<Signer: EcdsaChannelSigner, W: Writer>(
17551775 ( 34 , channel_monitor. alternative_funding_confirmed, option) ,
17561776 ( 35 , channel_monitor. is_manual_broadcast, required) ,
17571777 ( 37 , channel_monitor. funding_seen_onchain, required) ,
1778+ ( 39 , channel_monitor. latest_counterparty_commitment_txs, optional_vec) ,
17581779 } ) ;
17591780
17601781 Ok ( ( ) )
@@ -1960,6 +1981,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19601981 counterparty_node_id : counterparty_node_id,
19611982 initial_counterparty_commitment_info : None ,
19621983 initial_counterparty_commitment_tx : None ,
1984+ latest_counterparty_commitment_txs : Vec :: new ( ) ,
19631985 balances_empty_height : None ,
19641986
19651987 failed_back_htlc_ids : new_hash_set ( ) ,
@@ -2271,6 +2293,25 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22712293 self . inner . lock ( ) . unwrap ( ) . sign_to_local_justice_tx ( justice_tx, input_idx, value, commitment_number)
22722294 }
22732295
2296+ /// Returns signed justice transactions for all revoked counterparty commitment
2297+ /// transactions that this monitor knows about.
2298+ ///
2299+ /// This is a convenience method that combines the functionality of
2300+ /// [`Self::counterparty_commitment_txs_from_update`], building justice transactions,
2301+ /// and [`Self::sign_to_local_justice_tx`] into a single call, eliminating the need
2302+ /// for callers to track intermediate state.
2303+ ///
2304+ /// `feerate_per_kw` is used for the justice transaction fee calculation.
2305+ /// `destination_script` is the script where swept funds will be sent.
2306+ ///
2307+ /// Returns a list of [`JusticeTransaction`]s, each containing a fully signed
2308+ /// transaction and metadata about the revoked commitment it punishes.
2309+ pub fn get_justice_txs (
2310+ & self , feerate_per_kw : u64 , destination_script : ScriptBuf ,
2311+ ) -> Vec < JusticeTransaction > {
2312+ self . inner . lock ( ) . unwrap ( ) . get_justice_txs ( feerate_per_kw, destination_script)
2313+ }
2314+
22742315 pub ( crate ) fn get_min_seen_secret ( & self ) -> u64 {
22752316 self . inner . lock ( ) . unwrap ( ) . get_min_seen_secret ( )
22762317 }
@@ -3483,6 +3524,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34833524 self . provide_latest_counterparty_commitment_tx ( commitment_tx. trust ( ) . txid ( ) , Vec :: new ( ) , commitment_tx. commitment_number ( ) ,
34843525 commitment_tx. per_commitment_point ( ) ) ;
34853526 // Soon, we will only populate this field
3527+ self . latest_counterparty_commitment_txs = vec ! [ commitment_tx. clone( ) ] ;
34863528 self . initial_counterparty_commitment_tx = Some ( commitment_tx) ;
34873529 }
34883530
@@ -4284,8 +4326,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
42844326 }
42854327 }
42864328
4287- #[ cfg( debug_assertions) ] {
4288- self . counterparty_commitment_txs_from_update ( updates) ;
4329+ let new_commitment_txs = self . counterparty_commitment_txs_from_update ( updates) ;
4330+ if !new_commitment_txs. is_empty ( ) {
4331+ self . latest_counterparty_commitment_txs . extend ( new_commitment_txs) ;
4332+ }
4333+ // Prune commitment txs that are two or more revocations old. We keep one
4334+ // revocation depth so that get_justice_txs can sign the just-revoked
4335+ // commitment during this update_persisted_channel call.
4336+ if self . latest_counterparty_commitment_txs . len ( ) > 1 {
4337+ let current = self . current_counterparty_commitment_number ;
4338+ self . latest_counterparty_commitment_txs . retain ( |tx| {
4339+ // Commitment numbers count down. Keep entries within 1 of current
4340+ // (current and the one just prior, which may have just been revoked).
4341+ // Also keep anything with a matching number (splicing).
4342+ tx. commitment_number ( ) <= current + 1
4343+ } ) ;
42894344 }
42904345
42914346 self . latest_update_id = updates. update_id ;
@@ -4563,6 +4618,52 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
45634618 Ok ( justice_tx)
45644619 }
45654620
4621+ fn get_justice_txs (
4622+ & self , feerate_per_kw : u64 , destination_script : ScriptBuf ,
4623+ ) -> Vec < JusticeTransaction > {
4624+ let mut result = Vec :: new ( ) ;
4625+
4626+ for commitment_tx in & self . latest_counterparty_commitment_txs {
4627+ let commitment_number = commitment_tx. commitment_number ( ) ;
4628+
4629+ // Check if we have the revocation secret (i.e., this commitment is revoked)
4630+ let _secret = match self . get_secret ( commitment_number) {
4631+ Some ( s) => s,
4632+ None => continue ,
4633+ } ;
4634+
4635+ let trusted = commitment_tx. trust ( ) ;
4636+ let output_idx = match trusted. revokeable_output_index ( ) {
4637+ Some ( idx) => idx,
4638+ None => continue ,
4639+ } ;
4640+
4641+ let built = trusted. built_transaction ( ) ;
4642+ let value = built. transaction . output [ output_idx] . value ;
4643+ let txid = built. txid ;
4644+
4645+ let justice_tx = match trusted
4646+ . build_to_local_justice_tx ( feerate_per_kw, destination_script. clone ( ) )
4647+ {
4648+ Ok ( tx) => tx,
4649+ Err ( _) => continue ,
4650+ } ;
4651+
4652+ match self . sign_to_local_justice_tx ( justice_tx, 0 , value. to_sat ( ) , commitment_number) {
4653+ Ok ( signed_tx) => {
4654+ result. push ( JusticeTransaction {
4655+ tx : signed_tx,
4656+ revoked_commitment_txid : txid,
4657+ commitment_number,
4658+ } ) ;
4659+ } ,
4660+ Err ( _) => continue ,
4661+ }
4662+ }
4663+
4664+ result
4665+ }
4666+
45664667 /// Can only fail if idx is < get_min_seen_secret
45674668 fn get_secret ( & self , idx : u64 ) -> Option < [ u8 ; 32 ] > {
45684669 self . commitment_secrets . get_secret ( idx)
@@ -6521,6 +6622,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65216622 let mut alternative_funding_confirmed = None ;
65226623 let mut is_manual_broadcast = RequiredWrapper ( None ) ;
65236624 let mut funding_seen_onchain = RequiredWrapper ( None ) ;
6625+ let mut latest_counterparty_commitment_txs: Option < Vec < CommitmentTransaction > > = Some ( Vec :: new ( ) ) ;
65246626 read_tlv_fields ! ( reader, {
65256627 ( 1 , funding_spend_confirmed, option) ,
65266628 ( 3 , htlcs_resolved_on_chain, optional_vec) ,
@@ -6543,6 +6645,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65436645 ( 34 , alternative_funding_confirmed, option) ,
65446646 ( 35 , is_manual_broadcast, ( default_value, false ) ) ,
65456647 ( 37 , funding_seen_onchain, ( default_value, true ) ) ,
6648+ ( 39 , latest_counterparty_commitment_txs, optional_vec) ,
65466649 } ) ;
65476650 // Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so
65486651 // we can use it to determine if this monitor was last written by LDK 0.1 or later.
@@ -6714,6 +6817,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
67146817 counterparty_node_id : counterparty_node_id. unwrap_or ( dummy_node_id) ,
67156818 initial_counterparty_commitment_info,
67166819 initial_counterparty_commitment_tx,
6820+ latest_counterparty_commitment_txs : latest_counterparty_commitment_txs. unwrap_or_default ( ) ,
67176821 balances_empty_height,
67186822 failed_back_htlc_ids : new_hash_set ( ) ,
67196823
0 commit comments