diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 142368968..fc4caef8a 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -24,7 +24,7 @@ //! servicing [`ChannelMonitor`] updates from the client. use bitcoin::blockdata::block::BlockHeader; -use bitcoin::hash_types::Txid; +use bitcoin::hash_types::{Txid, BlockHash}; use crate::chain; use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput}; @@ -561,7 +561,7 @@ where }); } - fn get_relevant_txids(&self) -> Vec { + fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { let mut txids = Vec::new(); let monitor_states = self.monitors.read().unwrap(); for monitor_state in monitor_states.values() { diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 0351a4ac5..0bfd94445 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -332,14 +332,15 @@ impl Readable for CounterpartyCommitmentParameters { } } -/// An entry for an [`OnchainEvent`], stating the block height when the event was observed and the -/// transaction causing it. +/// An entry for an [`OnchainEvent`], stating the block height and hash when the event was +/// observed, as well as the transaction causing it. /// /// Used to determine when the on-chain event can be considered safe from a chain reorganization. #[derive(PartialEq, Eq)] struct OnchainEventEntry { txid: Txid, height: u32, + block_hash: Option, // Added as optional, will be filled in for any entry generated on 0.0.113 or after event: OnchainEvent, transaction: Option, // Added as optional, but always filled in, in LDK 0.0.110 } @@ -440,6 +441,7 @@ impl Writeable for OnchainEventEntry { (0, self.txid, required), (1, self.transaction, option), (2, self.height, required), + (3, self.block_hash, option), (4, self.event, required), }); Ok(()) @@ -450,16 +452,18 @@ impl MaybeReadable for OnchainEventEntry { fn read(reader: &mut R) -> Result, DecodeError> { let mut txid = Txid::all_zeros(); let mut transaction = None; + let mut block_hash = None; let mut height = 0; let mut event = None; read_tlv_fields!(reader, { (0, txid, required), (1, transaction, option), (2, height, required), + (3, block_hash, option), (4, event, ignorable), }); if let Some(ev) = event { - Ok(Some(Self { txid, transaction, height, event: ev })) + Ok(Some(Self { txid, transaction, height, block_hash, event: ev })) } else { Ok(None) } @@ -1484,11 +1488,11 @@ impl ChannelMonitor { } /// Returns the set of txids that should be monitored for re-organization out of the chain. - pub fn get_relevant_txids(&self) -> Vec { + pub fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { let inner = self.inner.lock().unwrap(); - let mut txids: Vec = inner.onchain_events_awaiting_threshold_conf + let mut txids: Vec<(Txid, Option)> = inner.onchain_events_awaiting_threshold_conf .iter() - .map(|entry| entry.txid) + .map(|entry| (entry.txid, entry.block_hash)) .chain(inner.onchain_tx_handler.get_relevant_txids().into_iter()) .collect(); txids.sort_unstable(); @@ -1941,7 +1945,7 @@ impl ChannelMonitor { /// been revoked yet, the previous one, we we will never "forget" to resolve an HTLC. macro_rules! fail_unbroadcast_htlcs { ($self: expr, $commitment_tx_type: expr, $commitment_txid_confirmed: expr, $commitment_tx_confirmed: expr, - $commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { { + $commitment_tx_conf_height: expr, $commitment_tx_conf_hash: expr, $confirmed_htlcs_list: expr, $logger: expr) => { { debug_assert_eq!($commitment_tx_confirmed.txid(), $commitment_txid_confirmed); macro_rules! check_htlc_fails { @@ -1985,6 +1989,7 @@ macro_rules! fail_unbroadcast_htlcs { txid: $commitment_txid_confirmed, transaction: Some($commitment_tx_confirmed.clone()), height: $commitment_tx_conf_height, + block_hash: Some(*$commitment_tx_conf_hash), event: OnchainEvent::HTLCUpdate { source: (**source).clone(), payment_hash: htlc.payment_hash.clone(), @@ -2172,7 +2177,7 @@ impl ChannelMonitorImpl { macro_rules! claim_htlcs { ($commitment_number: expr, $txid: expr) => { let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None); - self.onchain_tx_handler.update_claims_view(&Vec::new(), htlc_claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_requests(htlc_claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); } } if let Some(txid) = self.current_counterparty_commitment_txid { @@ -2198,10 +2203,10 @@ impl ChannelMonitorImpl { // block. Even if not, its a reasonable metric for the bump criteria on the HTLC // transactions. let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height()); - self.onchain_tx_handler.update_claims_view(&Vec::new(), claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); if let Some(ref tx) = self.prev_holder_signed_commitment_tx { let (claim_reqs, _) = self.get_broadcasted_holder_claims(&tx, self.best_block.height()); - self.onchain_tx_handler.update_claims_view(&Vec::new(), claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); } } } @@ -2288,8 +2293,8 @@ impl ChannelMonitorImpl { PackageSolvingData::HolderFundingOutput(funding_output), best_block_height, false, best_block_height, ); - self.onchain_tx_handler.update_claims_view( - &[], vec![commitment_package], best_block_height, best_block_height, + self.onchain_tx_handler.update_claims_view_from_requests( + vec![commitment_package], best_block_height, best_block_height, broadcaster, &bounded_fee_estimator, logger, ); } @@ -2403,7 +2408,7 @@ impl ChannelMonitorImpl { /// Returns packages to claim the revoked output(s), as well as additional outputs to watch and /// general information about the output that is to the counterparty in the commitment /// transaction. - fn check_spend_counterparty_transaction(&mut self, tx: &Transaction, height: u32, logger: &L) + fn check_spend_counterparty_transaction(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) -> (Vec, TransactionOutputs, CommitmentTxCounterpartyOutputInfo) where L::Target: Logger { // Most secp and related errors trying to create keys means we have no hope of constructing @@ -2474,13 +2479,13 @@ impl ChannelMonitorImpl { if let Some(per_commitment_data) = per_commitment_option { fail_unbroadcast_htlcs!(self, "revoked_counterparty", commitment_txid, tx, height, - per_commitment_data.iter().map(|(htlc, htlc_source)| + block_hash, per_commitment_data.iter().map(|(htlc, htlc_source)| (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref())) ), logger); } else { debug_assert!(false, "We should have per-commitment option for any recognized old commitment txn"); fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, tx, height, - [].iter().map(|reference| *reference), logger); + block_hash, [].iter().map(|reference| *reference), logger); } } } else if let Some(per_commitment_data) = per_commitment_option { @@ -2497,7 +2502,7 @@ impl ChannelMonitorImpl { self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number); log_info!(logger, "Got broadcast of non-revoked counterparty commitment transaction {}", commitment_txid); - fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, tx, height, + fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, tx, height, block_hash, per_commitment_data.iter().map(|(htlc, htlc_source)| (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref())) ), logger); @@ -2633,7 +2638,7 @@ impl ChannelMonitorImpl { (claimable_outpoints, Some((htlc_txid, outputs))) } - // Returns (1) `PackageTemplate`s that can be given to the OnChainTxHandler, so that the handler can + // Returns (1) `PackageTemplate`s that can be given to the OnchainTxHandler, so that the handler can // broadcast transactions claiming holder HTLC commitment outputs and (2) a holder revokable // script so we can detect whether a holder transaction has been seen on-chain. fn get_broadcasted_holder_claims(&self, holder_tx: &HolderSignedTx, conf_height: u32) -> (Vec, Option<(Script, PublicKey, PublicKey)>) { @@ -2678,7 +2683,7 @@ impl ChannelMonitorImpl { /// revoked using data in holder_claimable_outpoints. /// Should not be used if check_spend_revoked_transaction succeeds. /// Returns None unless the transaction is definitely one of our commitment transactions. - fn check_spend_holder_transaction(&mut self, tx: &Transaction, height: u32, logger: &L) -> Option<(Vec, TransactionOutputs)> where L::Target: Logger { + fn check_spend_holder_transaction(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) -> Option<(Vec, TransactionOutputs)> where L::Target: Logger { let commitment_txid = tx.txid(); let mut claim_requests = Vec::new(); let mut watch_outputs = Vec::new(); @@ -2701,7 +2706,7 @@ impl ChannelMonitorImpl { let mut to_watch = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, tx); append_onchain_update!(res, to_watch); fail_unbroadcast_htlcs!(self, "latest holder", commitment_txid, tx, height, - self.current_holder_commitment_tx.htlc_outputs.iter() + block_hash, self.current_holder_commitment_tx.htlc_outputs.iter() .map(|(htlc, _, htlc_source)| (htlc, htlc_source.as_ref())), logger); } else if let &Some(ref holder_tx) = &self.prev_holder_signed_commitment_tx { if holder_tx.txid == commitment_txid { @@ -2710,7 +2715,7 @@ impl ChannelMonitorImpl { let res = self.get_broadcasted_holder_claims(holder_tx, height); let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_tx, tx); append_onchain_update!(res, to_watch); - fail_unbroadcast_htlcs!(self, "previous holder", commitment_txid, tx, height, + fail_unbroadcast_htlcs!(self, "previous holder", commitment_txid, tx, height, block_hash, holder_tx.htlc_outputs.iter().map(|(htlc, _, htlc_source)| (htlc, htlc_source.as_ref())), logger); } @@ -2818,7 +2823,7 @@ impl ChannelMonitorImpl { if height > self.best_block.height() { self.best_block = BestBlock::new(block_hash, height); - self.block_confirmed(height, vec![], vec![], vec![], &broadcaster, &fee_estimator, &logger) + self.block_confirmed(height, block_hash, vec![], vec![], vec![], &broadcaster, &fee_estimator, &logger) } else if block_hash != self.best_block.block_hash() { self.best_block = BestBlock::new(block_hash, height); self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height <= height); @@ -2870,14 +2875,14 @@ impl ChannelMonitorImpl { let mut commitment_tx_to_counterparty_output = None; if (tx.input[0].sequence.0 >> 8*3) as u8 == 0x80 && (tx.lock_time.0 >> 8*3) as u8 == 0x20 { let (mut new_outpoints, new_outputs, counterparty_output_idx_sats) = - self.check_spend_counterparty_transaction(&tx, height, &logger); + self.check_spend_counterparty_transaction(&tx, height, &block_hash, &logger); commitment_tx_to_counterparty_output = counterparty_output_idx_sats; if !new_outputs.1.is_empty() { watch_outputs.push(new_outputs); } claimable_outpoints.append(&mut new_outpoints); if new_outpoints.is_empty() { - if let Some((mut new_outpoints, new_outputs)) = self.check_spend_holder_transaction(&tx, height, &logger) { + if let Some((mut new_outpoints, new_outputs)) = self.check_spend_holder_transaction(&tx, height, &block_hash, &logger) { debug_assert!(commitment_tx_to_counterparty_output.is_none(), "A commitment transaction matched as both a counterparty and local commitment tx?"); if !new_outputs.1.is_empty() { @@ -2893,6 +2898,7 @@ impl ChannelMonitorImpl { txid, transaction: Some((*tx).clone()), height, + block_hash: Some(block_hash), event: OnchainEvent::FundingSpendConfirmation { on_local_output_csv: balance_spendable_csv, commitment_tx_to_counterparty_output, @@ -2911,28 +2917,30 @@ impl ChannelMonitorImpl { // While all commitment/HTLC-Success/HTLC-Timeout transactions have one input, HTLCs // can also be resolved in a few other ways which can have more than one output. Thus, // we call is_resolving_htlc_output here outside of the tx.input.len() == 1 check. - self.is_resolving_htlc_output(&tx, height, &logger); + self.is_resolving_htlc_output(&tx, height, &block_hash, &logger); - self.is_paying_spendable_output(&tx, height, &logger); + self.is_paying_spendable_output(&tx, height, &block_hash, &logger); } if height > self.best_block.height() { self.best_block = BestBlock::new(block_hash, height); } - self.block_confirmed(height, txn_matched, watch_outputs, claimable_outpoints, &broadcaster, &fee_estimator, &logger) + self.block_confirmed(height, block_hash, txn_matched, watch_outputs, claimable_outpoints, &broadcaster, &fee_estimator, &logger) } /// Update state for new block(s)/transaction(s) confirmed. Note that the caller must update /// `self.best_block` before calling if a new best blockchain tip is available. More /// concretely, `self.best_block` must never be at a lower height than `conf_height`, avoiding - /// complexity especially in `OnchainTx::update_claims_view`. + /// complexity especially in + /// `OnchainTx::update_claims_view_from_requests`/`OnchainTx::update_claims_view_from_matched_txn`. /// /// `conf_height` should be set to the height at which any new transaction(s)/block(s) were /// confirmed at, even if it is not the current best height. fn block_confirmed( &mut self, conf_height: u32, + conf_hash: BlockHash, txn_matched: Vec<&Transaction>, mut watch_outputs: Vec, mut claimable_outpoints: Vec, @@ -3048,7 +3056,8 @@ impl ChannelMonitorImpl { } } - self.onchain_tx_handler.update_claims_view(&txn_matched, claimable_outpoints, conf_height, self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_requests(claimable_outpoints, conf_height, self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_matched_txn(&txn_matched, conf_height, conf_hash, self.best_block.height(), broadcaster, fee_estimator, logger); // Determine new outputs to watch by comparing against previously known outputs to watch, // updating the latter in the process. @@ -3237,7 +3246,7 @@ impl ChannelMonitorImpl { /// Check if any transaction broadcasted is resolving HTLC output by a success or timeout on a holder /// or counterparty commitment tx, if so send back the source, preimage if found and payment_hash of resolved HTLC - fn is_resolving_htlc_output(&mut self, tx: &Transaction, height: u32, logger: &L) where L::Target: Logger { + fn is_resolving_htlc_output(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) where L::Target: Logger { 'outer_loop: for input in &tx.input { let mut payment_data = None; let htlc_claim = HTLCClaim::from_witness(&input.witness); @@ -3322,7 +3331,7 @@ impl ChannelMonitorImpl { log_claim!($tx_info, $holder_tx, htlc_output, false); let outbound_htlc = $holder_tx == htlc_output.offered; self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { - txid: tx.txid(), height, transaction: Some(tx.clone()), + txid: tx.txid(), height, block_hash: Some(*block_hash), transaction: Some(tx.clone()), event: OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx: input.previous_output.vout, preimage: if accepted_preimage_claim || offered_preimage_claim { @@ -3366,6 +3375,7 @@ impl ChannelMonitorImpl { self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { txid: tx.txid(), height, + block_hash: Some(*block_hash), transaction: Some(tx.clone()), event: OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx: input.previous_output.vout, @@ -3389,6 +3399,7 @@ impl ChannelMonitorImpl { txid: tx.txid(), transaction: Some(tx.clone()), height, + block_hash: Some(*block_hash), event: OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx: input.previous_output.vout, preimage: Some(payment_preimage), @@ -3416,6 +3427,7 @@ impl ChannelMonitorImpl { txid: tx.txid(), transaction: Some(tx.clone()), height, + block_hash: Some(*block_hash), event: OnchainEvent::HTLCUpdate { source, payment_hash, htlc_value_satoshis: Some(amount_msat / 1000), @@ -3430,7 +3442,7 @@ impl ChannelMonitorImpl { } /// Check if any transaction broadcasted is paying fund back to some address we can assume to own - fn is_paying_spendable_output(&mut self, tx: &Transaction, height: u32, logger: &L) where L::Target: Logger { + fn is_paying_spendable_output(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) where L::Target: Logger { let mut spendable_output = None; for (i, outp) in tx.output.iter().enumerate() { // There is max one spendable output for any channel tx, including ones generated by us if i > ::core::u16::MAX as usize { @@ -3490,6 +3502,7 @@ impl ChannelMonitorImpl { txid: tx.txid(), transaction: Some(tx.clone()), height, + block_hash: Some(*block_hash), event: OnchainEvent::MaturingOutput { descriptor: spendable_output.clone() }, }; log_info!(logger, "Received spendable output {}, spendable at height {}", log_spendable!(spendable_output), entry.confirmation_threshold()); @@ -3531,7 +3544,7 @@ where self.0.best_block_updated(header, height, &*self.1, &*self.2, &*self.3); } - fn get_relevant_txids(&self) -> Vec { + fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { self.0.get_relevant_txids() } } diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 8ac6c3936..33d4826fe 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -171,7 +171,8 @@ pub trait Confirm { /// if they become available at the same time. fn best_block_updated(&self, header: &BlockHeader, height: u32); - /// Returns transactions that should be monitored for reorganization out of the chain. + /// Returns transactions that should be monitored for reorganization out of the chain along + /// with the hash of the block as part of which had been previously confirmed. /// /// Will include any transactions passed to [`transactions_confirmed`] that have insufficient /// confirmations to be safe from a chain reorganization. Will not include any transactions @@ -180,11 +181,13 @@ pub trait Confirm { /// May be called to determine the subset of transactions that must still be monitored for /// reorganization. Will be idempotent between calls but may change as a result of calls to the /// other interface methods. Thus, this is useful to determine which transactions may need to be - /// given to [`transaction_unconfirmed`]. + /// given to [`transaction_unconfirmed`]. If any of the returned transactions are confirmed in + /// a block other than the one with the given hash, they need to be unconfirmed and reconfirmed + /// via [`transaction_unconfirmed`] and [`transactions_confirmed`], respectively. /// /// [`transactions_confirmed`]: Self::transactions_confirmed /// [`transaction_unconfirmed`]: Self::transaction_unconfirmed - fn get_relevant_txids(&self) -> Vec; + fn get_relevant_txids(&self) -> Vec<(Txid, Option)>; } /// An enum representing the status of a channel monitor update persistence. diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 9cfa9d288..2ce2ed41b 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -16,7 +16,7 @@ use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint; use bitcoin::blockdata::script::Script; -use bitcoin::hash_types::Txid; +use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1; @@ -58,6 +58,7 @@ const MAX_ALLOC_SIZE: usize = 64*1024; struct OnchainEventEntry { txid: Txid, height: u32, + block_hash: Option, // Added as optional, will be filled in for any entry generated on 0.0.113 or after event: OnchainEvent, } @@ -92,6 +93,7 @@ impl Writeable for OnchainEventEntry { fn write(&self, writer: &mut W) -> Result<(), io::Error> { write_tlv_fields!(writer, { (0, self.txid, required), + (1, self.block_hash, option), (2, self.height, required), (4, self.event, required), }); @@ -103,14 +105,16 @@ impl MaybeReadable for OnchainEventEntry { fn read(reader: &mut R) -> Result, DecodeError> { let mut txid = Txid::all_zeros(); let mut height = 0; + let mut block_hash = None; let mut event = None; read_tlv_fields!(reader, { (0, txid, required), + (1, block_hash, option), (2, height, required), (4, event, ignorable), }); if let Some(ev) = event { - Ok(Some(Self { txid, height, event: ev })) + Ok(Some(Self { txid, height, block_hash, event: ev })) } else { Ok(None) } @@ -543,17 +547,22 @@ impl OnchainTxHandler { /// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link /// for this channel, provide new relevant on-chain transactions and/or new claim requests. - /// Formerly this was named `block_connected`, but it is now also used for claiming an HTLC output - /// if we receive a preimage after force-close. - /// `conf_height` represents the height at which the transactions in `txn_matched` were - /// confirmed. This does not need to equal the current blockchain tip height, which should be - /// provided via `cur_height`, however it must never be higher than `cur_height`. - pub(crate) fn update_claims_view(&mut self, txn_matched: &[&Transaction], requests: Vec, conf_height: u32, cur_height: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &L) - where B::Target: BroadcasterInterface, - F::Target: FeeEstimator, - L::Target: Logger, + /// Together with `update_claims_view_from_matched_txn` this used to be named + /// `block_connected`, but it is now also used for claiming an HTLC output if we receive a + /// preimage after force-close. + /// + /// `conf_height` represents the height at which the request was generated. This + /// does not need to equal the current blockchain tip height, which should be provided via + /// `cur_height`, however it must never be higher than `cur_height`. + pub(crate) fn update_claims_view_from_requests( + &mut self, requests: Vec, conf_height: u32, cur_height: u32, + broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &L + ) where + B::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, { - log_debug!(logger, "Updating claims view at height {} with {} matched transactions in block {} and {} claim requests", cur_height, txn_matched.len(), conf_height, requests.len()); + log_debug!(logger, "Updating claims view at height {} with {} claim requests", cur_height, requests.len()); let mut preprocessed_requests = Vec::with_capacity(requests.len()); let mut aggregated_request = None; @@ -633,7 +642,25 @@ impl OnchainTxHandler { self.pending_claim_requests.insert(txid, req); } } + } + /// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link + /// for this channel, provide new relevant on-chain transactions and/or new claim requests. + /// Together with `update_claims_view_from_requests` this used to be named `block_connected`, + /// but it is now also used for claiming an HTLC output if we receive a preimage after force-close. + /// + /// `conf_height` represents the height at which the transactions in `txn_matched` were + /// confirmed. This does not need to equal the current blockchain tip height, which should be + /// provided via `cur_height`, however it must never be higher than `cur_height`. + pub(crate) fn update_claims_view_from_matched_txn( + &mut self, txn_matched: &[&Transaction], conf_height: u32, conf_hash: BlockHash, + cur_height: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &L + ) where + B::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, + { + log_debug!(logger, "Updating claims view at height {} with {} matched transactions in block {}", cur_height, txn_matched.len(), conf_height); let mut bump_candidates = HashMap::new(); for tx in txn_matched { // Scan all input to verify is one of the outpoint spent is of interest for us @@ -661,6 +688,7 @@ impl OnchainTxHandler { let entry = OnchainEventEntry { txid: tx.txid(), height: conf_height, + block_hash: Some(conf_hash), event: OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() } }; if !self.onchain_events_awaiting_threshold_conf.contains(&entry) { @@ -701,6 +729,7 @@ impl OnchainTxHandler { let entry = OnchainEventEntry { txid: tx.txid(), height: conf_height, + block_hash: Some(conf_hash), event: OnchainEvent::ContentiousOutpoint { package }, }; if !self.onchain_events_awaiting_threshold_conf.contains(&entry) { @@ -860,12 +889,12 @@ impl OnchainTxHandler { self.claimable_outpoints.get(outpoint).is_some() } - pub(crate) fn get_relevant_txids(&self) -> Vec { - let mut txids: Vec = self.onchain_events_awaiting_threshold_conf + pub(crate) fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { + let mut txids: Vec<(Txid, Option)> = self.onchain_events_awaiting_threshold_conf .iter() - .map(|entry| entry.txid) + .map(|entry| (entry.txid, entry.block_hash)) .collect(); - txids.sort_unstable(); + txids.sort_unstable_by_key(|(txid, _)| *txid); txids.dedup(); txids } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 8c8202e3e..9b1bf2e45 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -4520,6 +4520,11 @@ impl Channel { self.channel_transaction_parameters.funding_outpoint } + /// Returns the block hash in which our funding transaction was confirmed. + pub fn get_funding_tx_confirmed_in(&self) -> Option { + self.funding_tx_confirmed_in + } + fn get_holder_selected_contest_delay(&self) -> u16 { self.channel_transaction_parameters.holder_selected_contest_delay } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7f9531331..91a853132 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5906,12 +5906,12 @@ where }); } - fn get_relevant_txids(&self) -> Vec { + fn get_relevant_txids(&self) -> Vec<(Txid, Option)> { let channel_state = self.channel_state.lock().unwrap(); let mut res = Vec::with_capacity(channel_state.by_id.len()); for chan in channel_state.by_id.values() { - if let Some(funding_txo) = chan.get_funding_txo() { - res.push(funding_txo.txid); + if let (Some(funding_txo), block_hash) = (chan.get_funding_txo(), chan.get_funding_tx_confirmed_in()) { + res.push((funding_txo.txid, block_hash)); } } res diff --git a/lightning/src/ln/reorg_tests.rs b/lightning/src/ln/reorg_tests.rs index 3481e8d6c..a445f420a 100644 --- a/lightning/src/ln/reorg_tests.rs +++ b/lightning/src/ln/reorg_tests.rs @@ -271,6 +271,7 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); *nodes[0].connect_style.borrow_mut() = connect_style; + let chan_conf_height = core::cmp::max(nodes[0].best_block_info().1 + 1, nodes[1].best_block_info().1 + 1); let chan = create_announced_chan_between_nodes(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()); let channel_state = nodes[0].node.channel_state.lock().unwrap(); @@ -281,8 +282,13 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_ if !reorg_after_reload { if use_funding_unconfirmed { let relevant_txids = nodes[0].node.get_relevant_txids(); - assert_eq!(&relevant_txids[..], &[chan.3.txid()]); - nodes[0].node.transaction_unconfirmed(&relevant_txids[0]); + assert_eq!(relevant_txids.len(), 1); + let block_hash_opt = relevant_txids[0].1; + let expected_hash = nodes[0].get_block_header(chan_conf_height).block_hash(); + assert_eq!(block_hash_opt, Some(expected_hash)); + let txid = relevant_txids[0].0; + assert_eq!(txid, chan.3.txid()); + nodes[0].node.transaction_unconfirmed(&txid); } else if connect_style == ConnectStyle::FullBlockViaListen { disconnect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH - 1); assert_eq!(nodes[0].node.list_usable_channels().len(), 1); @@ -290,6 +296,10 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_ } else { disconnect_all_blocks(&nodes[0]); } + + let relevant_txids = nodes[0].node.get_relevant_txids(); + assert_eq!(relevant_txids.len(), 0); + handle_announce_close_broadcast_events(&nodes, 0, 1, true, "Channel closed because of an exception: Funding transaction was un-confirmed. Locked at 6 confs, now have 0 confs."); check_added_monitors!(nodes[1], 1); { @@ -350,8 +360,13 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_ if reorg_after_reload { if use_funding_unconfirmed { let relevant_txids = nodes[0].node.get_relevant_txids(); - assert_eq!(&relevant_txids[..], &[chan.3.txid()]); - nodes[0].node.transaction_unconfirmed(&relevant_txids[0]); + assert_eq!(relevant_txids.len(), 1); + let block_hash_opt = relevant_txids[0].1; + let expected_hash = nodes[0].get_block_header(chan_conf_height).block_hash(); + assert_eq!(block_hash_opt, Some(expected_hash)); + let txid = relevant_txids[0].0; + assert_eq!(txid, chan.3.txid()); + nodes[0].node.transaction_unconfirmed(&txid); } else if connect_style == ConnectStyle::FullBlockViaListen { disconnect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH - 1); assert_eq!(nodes[0].node.list_channels().len(), 1); @@ -359,6 +374,10 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_ } else { disconnect_all_blocks(&nodes[0]); } + + let relevant_txids = nodes[0].node.get_relevant_txids(); + assert_eq!(relevant_txids.len(), 0); + handle_announce_close_broadcast_events(&nodes, 0, 1, true, "Channel closed because of an exception: Funding transaction was un-confirmed. Locked at 6 confs, now have 0 confs."); check_added_monitors!(nodes[1], 1); {