@@ -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,27 @@ 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 ,
2311+ feerate_per_kw : u64 ,
2312+ destination_script : ScriptBuf ,
2313+ ) -> Vec < JusticeTransaction > {
2314+ self . inner . lock ( ) . unwrap ( ) . get_justice_txs ( feerate_per_kw, destination_script)
2315+ }
2316+
22742317 pub ( crate ) fn get_min_seen_secret ( & self ) -> u64 {
22752318 self . inner . lock ( ) . unwrap ( ) . get_min_seen_secret ( )
22762319 }
@@ -3483,6 +3526,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34833526 self . provide_latest_counterparty_commitment_tx ( commitment_tx. trust ( ) . txid ( ) , Vec :: new ( ) , commitment_tx. commitment_number ( ) ,
34843527 commitment_tx. per_commitment_point ( ) ) ;
34853528 // Soon, we will only populate this field
3529+ self . latest_counterparty_commitment_txs = vec ! [ commitment_tx. clone( ) ] ;
34863530 self . initial_counterparty_commitment_tx = Some ( commitment_tx) ;
34873531 }
34883532
@@ -4284,8 +4328,21 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
42844328 }
42854329 }
42864330
4287- #[ cfg( debug_assertions) ] {
4288- self . counterparty_commitment_txs_from_update ( updates) ;
4331+ let new_commitment_txs = self . counterparty_commitment_txs_from_update ( updates) ;
4332+ if !new_commitment_txs. is_empty ( ) {
4333+ self . latest_counterparty_commitment_txs . extend ( new_commitment_txs) ;
4334+ }
4335+ // Prune commitment txs that are two or more revocations old. We keep one
4336+ // revocation depth so that get_justice_txs can sign the just-revoked
4337+ // commitment during this update_persisted_channel call.
4338+ if self . latest_counterparty_commitment_txs . len ( ) > 1 {
4339+ let current = self . current_counterparty_commitment_number ;
4340+ self . latest_counterparty_commitment_txs . retain ( |tx| {
4341+ // Commitment numbers count down. Keep entries within 1 of current
4342+ // (current and the one just prior, which may have just been revoked).
4343+ // Also keep anything with a matching number (splicing).
4344+ tx. commitment_number ( ) <= current + 1
4345+ } ) ;
42894346 }
42904347
42914348 self . latest_update_id = updates. update_id ;
@@ -4563,6 +4620,48 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
45634620 Ok ( justice_tx)
45644621 }
45654622
4623+ fn get_justice_txs ( & self , feerate_per_kw : u64 , destination_script : ScriptBuf ) -> 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. build_to_local_justice_tx ( feerate_per_kw, destination_script. clone ( ) ) {
4646+ Ok ( tx) => tx,
4647+ Err ( _) => continue ,
4648+ } ;
4649+
4650+ match self . sign_to_local_justice_tx ( justice_tx, 0 , value. to_sat ( ) , commitment_number) {
4651+ Ok ( signed_tx) => {
4652+ result. push ( JusticeTransaction {
4653+ tx : signed_tx,
4654+ revoked_commitment_txid : txid,
4655+ commitment_number,
4656+ } ) ;
4657+ } ,
4658+ Err ( _) => continue ,
4659+ }
4660+ }
4661+
4662+ result
4663+ }
4664+
45664665 /// Can only fail if idx is < get_min_seen_secret
45674666 fn get_secret ( & self , idx : u64 ) -> Option < [ u8 ; 32 ] > {
45684667 self . commitment_secrets . get_secret ( idx)
@@ -6521,6 +6620,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65216620 let mut alternative_funding_confirmed = None ;
65226621 let mut is_manual_broadcast = RequiredWrapper ( None ) ;
65236622 let mut funding_seen_onchain = RequiredWrapper ( None ) ;
6623+ let mut latest_counterparty_commitment_txs: Option < Vec < CommitmentTransaction > > = Some ( Vec :: new ( ) ) ;
65246624 read_tlv_fields ! ( reader, {
65256625 ( 1 , funding_spend_confirmed, option) ,
65266626 ( 3 , htlcs_resolved_on_chain, optional_vec) ,
@@ -6543,6 +6643,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65436643 ( 34 , alternative_funding_confirmed, option) ,
65446644 ( 35 , is_manual_broadcast, ( default_value, false ) ) ,
65456645 ( 37 , funding_seen_onchain, ( default_value, true ) ) ,
6646+ ( 39 , latest_counterparty_commitment_txs, optional_vec) ,
65466647 } ) ;
65476648 // Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so
65486649 // we can use it to determine if this monitor was last written by LDK 0.1 or later.
@@ -6714,6 +6815,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
67146815 counterparty_node_id : counterparty_node_id. unwrap_or ( dummy_node_id) ,
67156816 initial_counterparty_commitment_info,
67166817 initial_counterparty_commitment_tx,
6818+ latest_counterparty_commitment_txs : latest_counterparty_commitment_txs. unwrap_or_default ( ) ,
67176819 balances_empty_height,
67186820 failed_back_htlc_ids : new_hash_set ( ) ,
67196821
0 commit comments