diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 810de80da95..e20687a2965 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -22,8 +22,10 @@ use bitcoin::amount::Amount; use bitcoin::block::Header; +use bitcoin::locktime::absolute::LockTime; use bitcoin::script::{Script, ScriptBuf}; -use bitcoin::transaction::{OutPoint as BitcoinOutPoint, Transaction, TxOut}; +use bitcoin::transaction::{OutPoint as BitcoinOutPoint, Transaction, TxIn, TxOut, Version}; +use bitcoin::{Sequence, Witness}; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::hashes::sha256::Hash as Sha256; @@ -31,6 +33,7 @@ use bitcoin::hashes::Hash; use bitcoin::ecdsa::Signature as BitcoinSignature; use bitcoin::secp256k1::{self, ecdsa::Signature, PublicKey, Secp256k1, SecretKey}; +use bitcoin::sighash::EcdsaSighashType; use crate::chain; use crate::chain::chaininterface::{ @@ -47,7 +50,7 @@ use crate::events::bump_transaction::{AnchorDescriptor, BumpTransactionEvent}; use crate::events::{ClosureReason, Event, EventHandler, ReplayEvent}; use crate::ln::chan_utils::{ self, ChannelTransactionParameters, CommitmentTransaction, CounterpartyCommitmentSecrets, - HTLCClaim, HTLCOutputInCommitment, HolderCommitmentTransaction, + HTLCClaim, HTLCOutputInCommitment, HolderCommitmentTransaction, TxCreationKeys, }; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::channel_keys::{ @@ -141,6 +144,20 @@ impl ChannelMonitorUpdate { pub fn renegotiated_funding_data(&self) -> impl Iterator + '_ { self.internal_renegotiated_funding_data() } + + /// Returns `true` if this update contains counterparty commitment data + /// relevant to a watchtower (a new commitment or a revocation secret). + pub fn updates_watchtower_state(&self) -> bool { + self.updates.iter().any(|step| { + matches!( + step, + ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. } + | ChannelMonitorUpdateStep::LatestCounterpartyCommitment { .. } + | ChannelMonitorUpdateStep::CommitmentSecret { .. } + | ChannelMonitorUpdateStep::RenegotiatedFunding { .. } + ) + }) + } } /// LDK prior to 0.1 used this constant as the [`ChannelMonitorUpdate::update_id`] for any @@ -262,6 +279,17 @@ impl_writeable_tlv_based!(HTLCUpdate, { (4, payment_preimage, option), }); +/// A signed justice transaction ready for broadcast or watchtower submission. +#[derive(Clone, Debug)] +pub struct JusticeTransaction { + /// The fully signed justice transaction. + pub tx: Transaction, + /// The txid of the revoked counterparty commitment transaction. + pub revoked_commitment_txid: Txid, + /// The commitment number of the revoked commitment transaction. + pub commitment_number: u64, +} + /// If an output goes from claimable only by us to claimable by us or our counterparty within this /// many blocks, we consider it pinnable for the purposes of aggregating claims in a single /// transaction. @@ -1166,6 +1194,11 @@ struct FundingScope { // transaction for which we have deleted claim information on some watchtowers. current_holder_commitment_tx: HolderCommitmentTransaction, prev_holder_commitment_tx: Option, + + /// The current counterparty commitment transaction, stored for justice tx signing. + cur_counterparty_commitment_tx: Option, + /// The previous counterparty commitment transaction, stored for justice tx signing. + prev_counterparty_commitment_tx: Option, } impl FundingScope { @@ -1194,6 +1227,8 @@ impl_writeable_tlv_based!(FundingScope, { (7, current_holder_commitment_tx, required), (9, prev_holder_commitment_tx, option), (11, counterparty_claimable_outpoints, required), + (13, cur_counterparty_commitment_tx, option), + (15, prev_counterparty_commitment_tx, option), }); #[derive(Clone, PartialEq)] @@ -1756,6 +1791,8 @@ pub(crate) fn write_chanmon_internal( (35, channel_monitor.is_manual_broadcast, required), (37, channel_monitor.funding_seen_onchain, required), (39, channel_monitor.best_block.previous_blocks, required), + (43, channel_monitor.funding.cur_counterparty_commitment_tx, option), + (45, channel_monitor.funding.prev_counterparty_commitment_tx, option), }); Ok(()) @@ -1905,6 +1942,9 @@ impl ChannelMonitor { current_holder_commitment_tx: initial_holder_commitment_tx, prev_holder_commitment_tx: None, + + cur_counterparty_commitment_tx: None, + prev_counterparty_commitment_tx: None, }, pending_funding: vec![], @@ -2272,6 +2312,28 @@ impl ChannelMonitor { self.inner.lock().unwrap().sign_to_local_justice_tx(justice_tx, input_idx, value, commitment_number) } + /// Returns signed justice transactions for all revoked counterparty commitments + /// currently stored in this monitor. + /// + /// Call this after persisting the monitor when + /// [`ChannelMonitorUpdate::updates_watchtower_state`] returns `true`. Also call on + /// startup for each loaded monitor to recover any justice transactions not yet + /// delivered to a watchtower. + /// + /// To avoid losing justice data when the watchtower is unreachable, the + /// [`Persist`] implementation should delay completing monitor updates until + /// previously obtained justice transactions have been delivered. + /// + /// This method is read-only and returns the same results on repeated calls + /// for the same monitor state. + /// + /// [`Persist`]: crate::chain::chainmonitor::Persist + pub fn get_pending_justice_txs( + &self, feerate_per_kw: u64, destination_script: ScriptBuf, + ) -> Vec { + self.inner.lock().unwrap().get_pending_justice_txs(feerate_per_kw, destination_script) + } + pub(crate) fn get_min_seen_secret(&self) -> u64 { self.inner.lock().unwrap().get_min_seen_secret() } @@ -3486,6 +3548,7 @@ impl ChannelMonitorImpl { self.provide_latest_counterparty_commitment_tx(commitment_tx.trust().txid(), Vec::new(), commitment_tx.commitment_number(), commitment_tx.per_commitment_point()); // Soon, we will only populate this field + self.funding.cur_counterparty_commitment_tx = Some(commitment_tx.clone()); self.initial_counterparty_commitment_tx = Some(commitment_tx); } @@ -3563,6 +3626,12 @@ impl ChannelMonitorImpl { current_funding_commitment_tx.commitment_number(), current_funding_commitment_tx.per_commitment_point(), ); + // Safe to overwrite prev: CommitmentSecret and new counterparty commitments + // are always in separate ChannelMonitorUpdates, so the Persist callback has + // a chance to call get_pending_justice_txs between revocation and rotation. + self.funding.prev_counterparty_commitment_tx = + self.funding.cur_counterparty_commitment_tx.take(); + self.funding.cur_counterparty_commitment_tx = Some(current_funding_commitment_tx.clone()); for (pending_funding, commitment_tx) in self.pending_funding.iter_mut().zip(commitment_txs.iter().skip(1)) @@ -3574,6 +3643,13 @@ impl ChannelMonitorImpl { pending_funding .counterparty_claimable_outpoints .insert(commitment_txid, htlcs_for_commitment(commitment_tx)); + if !pending_funding.prev_counterparty_commitment_tx.as_ref().is_some_and(|p| { + self.commitment_secrets.get_secret(p.commitment_number()).is_some() + }) { + pending_funding.prev_counterparty_commitment_tx = + pending_funding.cur_counterparty_commitment_tx.take(); + } + pending_funding.cur_counterparty_commitment_tx = Some(commitment_tx.clone()); } Ok(()) @@ -4025,6 +4101,9 @@ impl ChannelMonitorImpl { counterparty_claimable_outpoints, current_holder_commitment_tx: alternative_holder_commitment_tx.clone(), prev_holder_commitment_tx: None, + + cur_counterparty_commitment_tx: None, + prev_counterparty_commitment_tx: None, }; let alternative_funding_outpoint = alternative_funding.funding_outpoint(); @@ -4294,8 +4373,21 @@ impl ChannelMonitorImpl { } } - #[cfg(debug_assertions)] { - self.counterparty_commitment_txs_from_update(updates); + // Populate cur/prev for the LatestCounterpartyCommitmentTXInfo path, which + // doesn't go through update_counterparty_commitment_data. + for commitment_tx in self.counterparty_commitment_txs_from_update(updates) { + let txid = commitment_tx.trust().built_transaction().txid; + let funding = core::iter::once(&mut self.funding) + .chain(self.pending_funding.iter_mut()) + .find(|f| f.current_counterparty_commitment_txid == Some(txid)); + if let Some(funding) = funding { + if funding.cur_counterparty_commitment_tx.as_ref() + .map(|c| c.trust().built_transaction().txid) != Some(txid) + { + funding.prev_counterparty_commitment_tx = funding.cur_counterparty_commitment_tx.take(); + funding.cur_counterparty_commitment_tx = Some(commitment_tx); + } + } } self.latest_update_id = updates.update_id; @@ -4742,6 +4834,167 @@ impl ChannelMonitorImpl { self.commitment_secrets.get_secret(idx) } + /// Returns signed justice transactions for all revoked counterparty commitments + /// currently stored in this monitor. Checks both cur and prev per funding scope. + fn get_pending_justice_txs( + &self, feerate_per_kw: u64, destination_script: ScriptBuf, + ) -> Vec { + let mut result = Vec::new(); + for funding in core::iter::once(&self.funding).chain(self.pending_funding.iter()) { + for tx in + [&funding.prev_counterparty_commitment_tx, &funding.cur_counterparty_commitment_tx] + .into_iter() + .flatten() + { + if self.commitment_secrets.get_secret(tx.commitment_number()).is_some() { + result.extend(self.try_sign_justice_txs( + tx, + feerate_per_kw, + destination_script.clone(), + )); + } + } + } + result + } + + fn try_sign_justice_txs( + &self, commitment_tx: &CommitmentTransaction, feerate_per_kw: u64, + destination_script: ScriptBuf, + ) -> Vec { + let commitment_number = commitment_tx.commitment_number(); + let secret = match self.get_secret(commitment_number) { + Some(s) => s, + None => return Vec::new(), + }; + let per_commitment_key = match SecretKey::from_slice(&secret) { + Ok(k) => k, + Err(_) => return Vec::new(), + }; + + let trusted = commitment_tx.trust(); + let built = trusted.built_transaction(); + let txid = built.txid; + let mut result = Vec::new(); + + // to_local justice tx + if let Some(output_idx) = trusted.revokeable_output_index() { + let value = built.transaction.output[output_idx].value; + if let Ok(justice_tx) = + trusted.build_to_local_justice_tx(feerate_per_kw, destination_script.clone()) + { + if let Ok(signed) = + self.sign_to_local_justice_tx(justice_tx, 0, value.to_sat(), commitment_number) + { + result.push(JusticeTransaction { + tx: signed, + revoked_commitment_txid: txid, + commitment_number, + }); + } + } + } + + // HTLC justice txs + let channel_parameters = core::iter::once(&self.funding) + .chain(&self.pending_funding) + .find(|funding| funding.counterparty_claimable_outpoints.contains_key(&txid)) + .map(|funding| &funding.channel_parameters); + if let Some(channel_parameters) = channel_parameters { + let per_commitment_point = + PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); + let directed = channel_parameters.as_counterparty_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + &per_commitment_point, + directed.broadcaster_pubkeys(), + directed.countersignatory_pubkeys(), + &self.onchain_tx_handler.secp_ctx, + ); + + for htlc in commitment_tx.nondust_htlcs() { + if let Some(output_index) = htlc.transaction_output_index { + let htlc_value = built.transaction.output[output_index as usize].value; + let witness_script = chan_utils::get_htlc_redeemscript( + htlc, + &channel_parameters.channel_type_features, + &keys, + ); + + // Build a spending tx for this HTLC output + let input = vec![TxIn { + previous_output: bitcoin::OutPoint { txid, vout: output_index }, + script_sig: ScriptBuf::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::new(), + }]; + let weight_estimate = if htlc.offered { + crate::chain::package::weight_revoked_offered_htlc( + &channel_parameters.channel_type_features, + ) + } else { + crate::chain::package::weight_revoked_received_htlc( + &channel_parameters.channel_type_features, + ) + }; + let fee = Amount::from_sat(crate::chain::chaininterface::fee_for_weight( + feerate_per_kw as u32, + // Base tx weight + witness weight + Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: input.clone(), + output: vec![TxOut { + script_pubkey: destination_script.clone(), + value: htlc_value, + }], + } + .weight() + .to_wu() + weight_estimate, + )); + let output_value = match htlc_value.checked_sub(fee) { + Some(v) => v, + None => continue, // Dust, skip + }; + + let mut justice_tx = Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input, + output: vec![TxOut { + script_pubkey: destination_script.clone(), + value: output_value, + }], + }; + + if let Ok(sig) = self.onchain_tx_handler.signer.sign_justice_revoked_htlc( + channel_parameters, + &justice_tx, + 0, + htlc_value.to_sat(), + &per_commitment_key, + htlc, + &self.onchain_tx_handler.secp_ctx, + ) { + let mut ser_sig = sig.serialize_der().to_vec(); + ser_sig.push(EcdsaSighashType::All as u8); + justice_tx.input[0].witness.push(ser_sig); + justice_tx.input[0] + .witness + .push(keys.revocation_key.to_public_key().serialize().to_vec()); + justice_tx.input[0].witness.push(witness_script.into_bytes()); + result.push(JusticeTransaction { + tx: justice_tx, + revoked_commitment_txid: txid, + commitment_number, + }); + } + } + } + } + + result + } + fn get_min_seen_secret(&self) -> u64 { self.commitment_secrets.get_min_seen_secret() } @@ -6696,6 +6949,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut is_manual_broadcast = RequiredWrapper(None); let mut funding_seen_onchain = RequiredWrapper(None); let mut best_block_previous_blocks = None; + let mut cur_counterparty_commitment_tx: Option = None; + let mut prev_counterparty_commitment_tx_deser: Option = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -6719,6 +6974,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (35, is_manual_broadcast, (default_value, false)), (37, funding_seen_onchain, (default_value, true)), (39, best_block_previous_blocks, option), // Added and always set in 0.3 + (43, cur_counterparty_commitment_tx, option), + (45, prev_counterparty_commitment_tx_deser, option), }); if let Some(previous_blocks) = best_block_previous_blocks { best_block.previous_blocks = previous_blocks; @@ -6837,6 +7094,9 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP current_holder_commitment_tx, prev_holder_commitment_tx, + + cur_counterparty_commitment_tx, + prev_counterparty_commitment_tx: prev_counterparty_commitment_tx_deser, }, pending_funding: pending_funding.unwrap_or(vec![]), is_manual_broadcast: is_manual_broadcast.0.unwrap(), diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 7ed46922d8a..6946088fd47 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -12,7 +12,7 @@ //! claim outputs on-chain. use crate::chain; -use crate::chain::chaininterface::LowerBoundedFeeEstimator; +use crate::chain::chaininterface::{LowerBoundedFeeEstimator, FEERATE_FLOOR_SATS_PER_KW}; use crate::chain::channelmonitor; use crate::chain::channelmonitor::{ Balance, ANTI_REORG_DELAY, CLTV_CLAIM_BUFFER, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, @@ -1095,6 +1095,119 @@ fn do_test_forming_justice_tx_from_monitor_updates(broadcast_initial_commitment: assert_eq!(total_claimable_balance, expected_claimable_balance); } +#[xtest(feature = "_externalize_tests")] +pub fn test_justice_tx_crash_recovery() { + // Verify that get_pending_justice_txs returns justice txs for the most + // recently revoked counterparty commitment, enabling crash recovery. + let chanmon_cfgs = create_chanmon_cfgs(2); + let destination_script = chanmon_cfgs[1].keys_manager.get_destination_script([0; 32]).unwrap(); + let persisters = [ + WatchtowerPersister::new( + chanmon_cfgs[0].keys_manager.get_destination_script([0; 32]).unwrap(), + ), + WatchtowerPersister::new(destination_script.clone()), + ]; + let node_cfgs = create_node_cfgs_with_persisters(2, &chanmon_cfgs, persisters.iter().collect()); + let legacy_cfg = test_legacy_channel_config(); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[Some(legacy_cfg.clone()), Some(legacy_cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let (_, _, channel_id, _) = create_announced_chan_between_nodes(&nodes, 0, 1); + + // Move to a non-initial commitment + send_payment(&nodes[0], &[&nodes[1]], 5_000_000); + + // Capture the commitment that will be revoked + let revoked_local_txn = get_local_commitment_txn!(nodes[0], channel_id); + assert_eq!(revoked_local_txn.len(), 1); + let revoked_txid = revoked_local_txn[0].compute_txid(); + + // Revoke it + send_payment(&nodes[0], &[&nodes[1]], 5_000_000); + + // The persister should have a justice tx for this revoked commitment + assert!(persisters[1].justice_tx(channel_id, &revoked_txid).is_some()); + + // get_pending_justice_txs should also return it, since prev still holds + // the revoked commitment data (cloned, not consumed by signing). + let pending = { + let monitor = get_monitor!(nodes[1], channel_id); + monitor.get_pending_justice_txs(FEERATE_FLOOR_SATS_PER_KW as u64, destination_script) + }; + assert!(!pending.is_empty()); + assert!(!pending.is_empty()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_justice_tx_idempotent() { + // Verify that get_pending_justice_txs returns consistent results across + // multiple calls (data is cloned, not consumed). + let chanmon_cfgs = create_chanmon_cfgs(2); + let destination_script = chanmon_cfgs[1].keys_manager.get_destination_script([0; 32]).unwrap(); + let persisters = [ + WatchtowerPersister::new( + chanmon_cfgs[0].keys_manager.get_destination_script([0; 32]).unwrap(), + ), + WatchtowerPersister::new(destination_script.clone()), + ]; + let node_cfgs = create_node_cfgs_with_persisters(2, &chanmon_cfgs, persisters.iter().collect()); + let legacy_cfg = test_legacy_channel_config(); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[Some(legacy_cfg.clone()), Some(legacy_cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let (_, _, channel_id, _) = create_announced_chan_between_nodes(&nodes, 0, 1); + + send_payment(&nodes[0], &[&nodes[1]], 5_000_000); + + let revoked_local_txn = get_local_commitment_txn!(nodes[0], channel_id); + assert_eq!(revoked_local_txn.len(), 1); + + send_payment(&nodes[0], &[&nodes[1]], 5_000_000); + + // Call get_pending_justice_txs twice and verify both return the same results. + let (first, second) = { + let monitor = get_monitor!(nodes[1], channel_id); + let first = monitor + .get_pending_justice_txs(FEERATE_FLOOR_SATS_PER_KW as u64, destination_script.clone()); + let second = + monitor.get_pending_justice_txs(FEERATE_FLOOR_SATS_PER_KW as u64, destination_script); + (first, second) + }; + assert_eq!(first.len(), second.len()); + assert!(!first.is_empty()); + for (a, b) in first.iter().zip(second.iter()) { + assert_eq!(a.revoked_commitment_txid, b.revoked_commitment_txid); + assert_eq!(a.commitment_number, b.commitment_number); + assert_eq!(a.tx.compute_txid(), b.tx.compute_txid()); + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_updates_watchtower_state() { + use crate::chain::channelmonitor::{ChannelMonitorUpdate, ChannelMonitorUpdateStep}; + + // Verify updates_watchtower_state returns true for commitment-relevant steps. + let commitment_update = ChannelMonitorUpdate { + updates: vec![ChannelMonitorUpdateStep::CommitmentSecret { idx: 0, secret: [0u8; 32] }], + update_id: 0, + channel_id: None, + }; + assert!(commitment_update.updates_watchtower_state()); + + // A preimage-only update is not watchtower-relevant. + let preimage_update = ChannelMonitorUpdate { + updates: vec![ChannelMonitorUpdateStep::PaymentPreimage { + payment_preimage: crate::types::payment::PaymentPreimage([0u8; 32]), + payment_info: None, + }], + update_id: 1, + channel_id: None, + }; + assert!(!preimage_update.updates_watchtower_state()); +} + #[xtest(feature = "_externalize_tests")] pub fn claim_htlc_outputs() { // Node revoked old state, htlcs haven't time out yet, claim them in shared justice tx diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 57f9ba6b22f..d2207862da2 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -22,8 +22,6 @@ use crate::chain::channelmonitor::{ use crate::chain::transaction::OutPoint; use crate::chain::BestBlock; use crate::chain::WatchedOutput; -#[cfg(any(test, feature = "_externalize_tests"))] -use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager; use crate::ln::inbound_payment::ExpandedKey; @@ -736,36 +734,20 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { } } -#[cfg(any(test, feature = "_externalize_tests"))] -struct JusticeTxData { - justice_tx: Transaction, - value: Amount, - commitment_number: u64, -} - #[cfg(any(test, feature = "_externalize_tests"))] pub(crate) struct WatchtowerPersister { persister: TestPersister, - /// Upon a new commitment_signed, we'll get a - /// ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo. We'll store the justice tx - /// amount, and commitment number so we can build the justice tx after our counterparty - /// revokes it. - unsigned_justice_tx_data: Mutex>>, - /// After receiving a revoke_and_ack for a commitment number, we'll form and store the justice - /// tx which would be used to provide a watchtower with the data it needs. - watchtower_state: Mutex>>, + /// Signed justice transactions keyed by channel and revoked commitment txid. + watchtower_state: Mutex>>>, destination_script: ScriptBuf, } #[cfg(any(test, feature = "_externalize_tests"))] impl WatchtowerPersister { pub(crate) fn new(destination_script: ScriptBuf) -> Self { - let unsigned_justice_tx_data = Mutex::new(new_hash_map()); - let watchtower_state = Mutex::new(new_hash_map()); WatchtowerPersister { persister: TestPersister::new(), - unsigned_justice_tx_data, - watchtower_state, + watchtower_state: Mutex::new(new_hash_map()), destination_script, } } @@ -777,26 +759,8 @@ impl WatchtowerPersister { .lock() .unwrap() .get(&channel_id) - .unwrap() - .get(commitment_txid) - .cloned() - } - - fn form_justice_data_from_commitment( - &self, counterparty_commitment_tx: &CommitmentTransaction, - ) -> Option { - let trusted_tx = counterparty_commitment_tx.trust(); - let output_idx = trusted_tx.revokeable_output_index()?; - let built_tx = trusted_tx.built_transaction(); - let value = built_tx.transaction.output[output_idx as usize].value; - let justice_tx = trusted_tx - .build_to_local_justice_tx( - FEERATE_FLOOR_SATS_PER_KW as u64, - self.destination_script.clone(), - ) - .ok()?; - let commitment_number = counterparty_commitment_tx.commitment_number(); - Some(JusticeTxData { justice_tx, value, commitment_number }) + .and_then(|m| m.get(commitment_txid)) + .and_then(|txs| txs.first().cloned()) } } @@ -807,12 +771,6 @@ impl Persist for WatchtowerPers ) -> chain::ChannelMonitorUpdateStatus { let res = self.persister.persist_new_channel(monitor_name, data); - assert!(self - .unsigned_justice_tx_data - .lock() - .unwrap() - .insert(data.channel_id(), VecDeque::new()) - .is_none()); assert!(self .watchtower_state .lock() @@ -820,18 +778,6 @@ impl Persist for WatchtowerPers .insert(data.channel_id(), new_hash_map()) .is_none()); - let initial_counterparty_commitment_tx = - data.initial_counterparty_commitment_tx().expect("First and only call expects Some"); - if let Some(justice_data) = - self.form_justice_data_from_commitment(&initial_counterparty_commitment_tx) - { - self.unsigned_justice_tx_data - .lock() - .unwrap() - .get_mut(&data.channel_id()) - .unwrap() - .push_back(justice_data); - } res } @@ -842,37 +788,20 @@ impl Persist for WatchtowerPers let res = self.persister.update_persisted_channel(monitor_name, update, data); if let Some(update) = update { - let commitment_txs = data.counterparty_commitment_txs_from_update(update); - let justice_datas = commitment_txs - .into_iter() - .filter_map(|commitment_tx| self.form_justice_data_from_commitment(&commitment_tx)); - let mut channels_justice_txs = self.unsigned_justice_tx_data.lock().unwrap(); - let channel_state = channels_justice_txs.get_mut(&data.channel_id()).unwrap(); - channel_state.extend(justice_datas); - - while let Some(JusticeTxData { justice_tx, value, commitment_number }) = - channel_state.front() - { - let input_idx = 0; - let commitment_txid = justice_tx.input[input_idx].previous_output.txid; - match data.sign_to_local_justice_tx( - justice_tx.clone(), - input_idx, - value.to_sat(), - *commitment_number, - ) { - Ok(signed_justice_tx) => { - let dup = self - .watchtower_state - .lock() - .unwrap() - .get_mut(&data.channel_id()) - .unwrap() - .insert(commitment_txid, signed_justice_tx); - assert!(dup.is_none()); - channel_state.pop_front(); - }, - Err(_) => break, + if update.updates_watchtower_state() { + let justice_txs = data.get_pending_justice_txs( + FEERATE_FLOOR_SATS_PER_KW as u64, + self.destination_script.clone(), + ); + for jtx in justice_txs { + self.watchtower_state + .lock() + .unwrap() + .get_mut(&data.channel_id()) + .unwrap() + .entry(jtx.revoked_commitment_txid) + .or_insert_with(Vec::new) + .push(jtx.tx); } } }