Move per-HTLC logic out of get_claimable_balances into a helper
[rust-lightning] / lightning / src / chain / channelmonitor.rs
index 0d1c458bc2d9d2ae6c91497587d35ac26a27aff4..500f2b521dd404bdb5c53b06332c37fb5064a4c2 100644 (file)
@@ -22,6 +22,7 @@
 
 use bitcoin::blockdata::block::BlockHeader;
 use bitcoin::blockdata::transaction::{TxOut,Transaction};
+use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
 use bitcoin::blockdata::script::{Script, Builder};
 use bitcoin::blockdata::opcodes;
 
@@ -382,6 +383,9 @@ enum OnchainEvent {
                on_local_output_csv: Option<u16>,
                /// If the funding spend transaction was a known remote commitment transaction, we track
                /// the output index and amount of the counterparty's `to_self` output here.
+               ///
+               /// This allows us to generate a [`Balance::CounterpartyRevokedOutputClaimable`] for the
+               /// counterparty output.
                commitment_tx_to_counterparty_output: CommitmentTxCounterpartyOutputInfo,
        },
        /// A spend of a commitment transaction HTLC output, set in the cases where *no* `HTLCUpdate`
@@ -582,18 +586,35 @@ pub enum Balance {
                /// done so.
                claimable_height: u32,
        },
+       /// The channel has been closed, and our counterparty broadcasted a revoked commitment
+       /// transaction.
+       ///
+       /// Thus, we're able to claim all outputs in the commitment transaction, one of which has the
+       /// following amount.
+       CounterpartyRevokedOutputClaimable {
+               /// The amount, in satoshis, of the output which we can claim.
+               ///
+               /// Note that for outputs from HTLC balances this may be excluding some on-chain fees that
+               /// were already spent.
+               claimable_amount_satoshis: u64,
+       },
 }
 
 /// An HTLC which has been irrevocably resolved on-chain, and has reached ANTI_REORG_DELAY.
 #[derive(PartialEq)]
 struct IrrevocablyResolvedHTLC {
        commitment_tx_output_idx: u32,
+       /// The txid of the transaction which resolved the HTLC, this may be a commitment (if the HTLC
+       /// was not present in the confirmed commitment transaction), HTLC-Success, or HTLC-Timeout
+       /// transaction.
+       resolving_txid: Option<Txid>, // Added as optional, but always filled in, in 0.0.110
        /// Only set if the HTLC claim was ours using a payment preimage
        payment_preimage: Option<PaymentPreimage>,
 }
 
 impl_writeable_tlv_based!(IrrevocablyResolvedHTLC, {
        (0, commitment_tx_output_idx, required),
+       (1, resolving_txid, option),
        (2, payment_preimage, option),
 });
 
@@ -1406,7 +1427,150 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
        pub fn current_best_block(&self) -> BestBlock {
                self.inner.lock().unwrap().best_block.clone()
        }
+}
+
+impl<Signer: Sign> ChannelMonitorImpl<Signer> {
+       /// Helper for get_claimable_balances which does the work for an individual HTLC, generating up
+       /// to one `Balance` for the HTLC.
+       fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, holder_commitment: bool,
+               counterparty_revoked_commitment: bool, confirmed_txid: Option<Txid>)
+       -> Option<Balance> {
+               let htlc_commitment_tx_output_idx =
+                       if let Some(v) = htlc.transaction_output_index { v } else { return None; };
+
+               let mut htlc_spend_txid_opt = None;
+               let mut holder_timeout_spend_pending = None;
+               let mut htlc_spend_pending = None;
+               let mut holder_delayed_output_pending = None;
+               for event in self.onchain_events_awaiting_threshold_conf.iter() {
+                       match event.event {
+                               OnchainEvent::HTLCUpdate { commitment_tx_output_idx, htlc_value_satoshis, .. }
+                               if commitment_tx_output_idx == Some(htlc_commitment_tx_output_idx) => {
+                                       debug_assert!(htlc_spend_txid_opt.is_none());
+                                       htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid());
+                                       debug_assert!(holder_timeout_spend_pending.is_none());
+                                       debug_assert_eq!(htlc_value_satoshis.unwrap(), htlc.amount_msat / 1000);
+                                       holder_timeout_spend_pending = Some(event.confirmation_threshold());
+                               },
+                               OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. }
+                               if commitment_tx_output_idx == htlc_commitment_tx_output_idx => {
+                                       debug_assert!(htlc_spend_txid_opt.is_none());
+                                       htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid());
+                                       debug_assert!(htlc_spend_pending.is_none());
+                                       htlc_spend_pending = Some((event.confirmation_threshold(), preimage.is_some()));
+                               },
+                               OnchainEvent::MaturingOutput {
+                                       descriptor: SpendableOutputDescriptor::DelayedPaymentOutput(ref descriptor) }
+                               if descriptor.outpoint.index as u32 == htlc_commitment_tx_output_idx => {
+                                       debug_assert!(holder_delayed_output_pending.is_none());
+                                       holder_delayed_output_pending = Some(event.confirmation_threshold());
+                               },
+                               _ => {},
+                       }
+               }
+               let htlc_resolved = self.htlcs_resolved_on_chain.iter()
+                       .find(|v| if v.commitment_tx_output_idx == htlc_commitment_tx_output_idx {
+                               debug_assert!(htlc_spend_txid_opt.is_none());
+                               htlc_spend_txid_opt = v.resolving_txid;
+                               true
+                       } else { false });
+               debug_assert!(holder_timeout_spend_pending.is_some() as u8 + htlc_spend_pending.is_some() as u8 + htlc_resolved.is_some() as u8 <= 1);
+
+               let htlc_output_to_spend =
+                       if let Some(txid) = htlc_spend_txid_opt {
+                               debug_assert!(
+                                       self.onchain_tx_handler.channel_transaction_parameters.opt_anchors.is_none(),
+                                       "This code needs updating for anchors");
+                               BitcoinOutPoint::new(txid, 0)
+                       } else {
+                               BitcoinOutPoint::new(confirmed_txid.unwrap(), htlc_commitment_tx_output_idx)
+                       };
+               let htlc_output_spend_pending = self.onchain_tx_handler.is_output_spend_pending(&htlc_output_to_spend);
+
+               if let Some(conf_thresh) = holder_delayed_output_pending {
+                       debug_assert!(holder_commitment);
+                       return Some(Balance::ClaimableAwaitingConfirmations {
+                               claimable_amount_satoshis: htlc.amount_msat / 1000,
+                               confirmation_height: conf_thresh,
+                       });
+               } else if htlc_resolved.is_some() && !htlc_output_spend_pending {
+                       // Funding transaction spends should be fully confirmed by the time any
+                       // HTLC transactions are resolved, unless we're talking about a holder
+                       // commitment tx, whose resolution is delayed until the CSV timeout is
+                       // reached, even though HTLCs may be resolved after only
+                       // ANTI_REORG_DELAY confirmations.
+                       debug_assert!(holder_commitment || self.funding_spend_confirmed.is_some());
+               } else if counterparty_revoked_commitment {
+                       let htlc_output_claim_pending = self.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
+                               if let OnchainEvent::MaturingOutput {
+                                       descriptor: SpendableOutputDescriptor::StaticOutput { .. }
+                               } = &event.event {
+                                       if event.transaction.as_ref().map(|tx| tx.input.iter().any(|inp| {
+                                               if let Some(htlc_spend_txid) = htlc_spend_txid_opt {
+                                                       Some(tx.txid()) == htlc_spend_txid_opt ||
+                                                               inp.previous_output.txid == htlc_spend_txid
+                                               } else {
+                                                       Some(inp.previous_output.txid) == confirmed_txid &&
+                                                               inp.previous_output.vout == htlc_commitment_tx_output_idx
+                                               }
+                                       })).unwrap_or(false) {
+                                               Some(())
+                                       } else { None }
+                               } else { None }
+                       });
+                       if htlc_output_claim_pending.is_some() {
+                               // We already push `Balance`s onto the `res` list for every
+                               // `StaticOutput` in a `MaturingOutput` in the revoked
+                               // counterparty commitment transaction case generally, so don't
+                               // need to do so again here.
+                       } else {
+                               debug_assert!(holder_timeout_spend_pending.is_none(),
+                                       "HTLCUpdate OnchainEvents should never appear for preimage claims");
+                               debug_assert!(!htlc.offered || htlc_spend_pending.is_none() || !htlc_spend_pending.unwrap().1,
+                                       "We don't (currently) generate preimage claims against revoked outputs, where did you get one?!");
+                               return Some(Balance::CounterpartyRevokedOutputClaimable {
+                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
+                               });
+                       }
+               } else if htlc.offered == holder_commitment {
+                       // If the payment was outbound, check if there's an HTLCUpdate
+                       // indicating we have spent this HTLC with a timeout, claiming it back
+                       // and awaiting confirmations on it.
+                       if let Some(conf_thresh) = holder_timeout_spend_pending {
+                               return Some(Balance::ClaimableAwaitingConfirmations {
+                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
+                                       confirmation_height: conf_thresh,
+                               });
+                       } else {
+                               return Some(Balance::MaybeClaimableHTLCAwaitingTimeout {
+                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
+                                       claimable_height: htlc.cltv_expiry,
+                               });
+                       }
+               } else if self.payment_preimages.get(&htlc.payment_hash).is_some() {
+                       // Otherwise (the payment was inbound), only expose it as claimable if
+                       // we know the preimage.
+                       // Note that if there is a pending claim, but it did not use the
+                       // preimage, we lost funds to our counterparty! We will then continue
+                       // to show it as ContentiousClaimable until ANTI_REORG_DELAY.
+                       debug_assert!(holder_timeout_spend_pending.is_none());
+                       if let Some((conf_thresh, true)) = htlc_spend_pending {
+                               return Some(Balance::ClaimableAwaitingConfirmations {
+                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
+                                       confirmation_height: conf_thresh,
+                               });
+                       } else {
+                               return Some(Balance::ContentiousClaimable {
+                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
+                                       timeout_height: htlc.cltv_expiry,
+                               });
+                       }
+               }
+               None
+       }
+}
 
+impl<Signer: Sign> ChannelMonitor<Signer> {
        /// Gets the balances in this channel which are either claimable by us if we were to
        /// force-close the channel now or which are claimable on-chain (possibly awaiting
        /// confirmation).
@@ -1416,9 +1580,9 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
        /// balance, or until our counterparty has claimed the balance and accrued several
        /// confirmations on the claim transaction.
        ///
-       /// Note that the balances available when you or your counterparty have broadcasted revoked
-       /// state(s) may not be fully captured here.
-       // TODO, fix that ^
+       /// Note that for `ChannelMonitors` which track a channel which went on-chain with versions of
+       /// LDK prior to 0.0.108, balances may not be fully captured if our counterparty broadcasted
+       /// a revoked state.
        ///
        /// See [`Balance`] for additional details on the types of claimable balances which
        /// may be returned here and their meanings.
@@ -1427,9 +1591,13 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
                let us = self.inner.lock().unwrap();
 
                let mut confirmed_txid = us.funding_spend_confirmed;
+               let mut confirmed_counterparty_output = us.confirmed_commitment_tx_counterparty_output;
                let mut pending_commitment_tx_conf_thresh = None;
                let funding_spend_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
-                       if let OnchainEvent::FundingSpendConfirmation { .. } = event.event {
+                       if let OnchainEvent::FundingSpendConfirmation { commitment_tx_to_counterparty_output, .. } =
+                               event.event
+                       {
+                               confirmed_counterparty_output = commitment_tx_to_counterparty_output;
                                Some((event.txid, event.confirmation_threshold()))
                        } else { None }
                });
@@ -1441,71 +1609,12 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
                }
 
                macro_rules! walk_htlcs {
-                       ($holder_commitment: expr, $htlc_iter: expr) => {
+                       ($holder_commitment: expr, $counterparty_revoked_commitment: expr, $htlc_iter: expr) => {
                                for htlc in $htlc_iter {
-                                       if let Some(htlc_commitment_tx_output_idx) = htlc.transaction_output_index {
-                                               if let Some(conf_thresh) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
-                                                       if let OnchainEvent::MaturingOutput { descriptor: SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) } = &event.event {
-                                                               if descriptor.outpoint.index as u32 == htlc_commitment_tx_output_idx { Some(event.confirmation_threshold()) } else { None }
-                                                       } else { None }
-                                               }) {
-                                                       debug_assert!($holder_commitment);
-                                                       res.push(Balance::ClaimableAwaitingConfirmations {
-                                                               claimable_amount_satoshis: htlc.amount_msat / 1000,
-                                                               confirmation_height: conf_thresh,
-                                                       });
-                                               } else if us.htlcs_resolved_on_chain.iter().any(|v| v.commitment_tx_output_idx == htlc_commitment_tx_output_idx) {
-                                                       // Funding transaction spends should be fully confirmed by the time any
-                                                       // HTLC transactions are resolved, unless we're talking about a holder
-                                                       // commitment tx, whose resolution is delayed until the CSV timeout is
-                                                       // reached, even though HTLCs may be resolved after only
-                                                       // ANTI_REORG_DELAY confirmations.
-                                                       debug_assert!($holder_commitment || us.funding_spend_confirmed.is_some());
-                                               } else if htlc.offered == $holder_commitment {
-                                                       // If the payment was outbound, check if there's an HTLCUpdate
-                                                       // indicating we have spent this HTLC with a timeout, claiming it back
-                                                       // and awaiting confirmations on it.
-                                                       let htlc_update_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
-                                                               if let OnchainEvent::HTLCUpdate { commitment_tx_output_idx: Some(commitment_tx_output_idx), .. } = event.event {
-                                                                       if commitment_tx_output_idx == htlc_commitment_tx_output_idx {
-                                                                               Some(event.confirmation_threshold()) } else { None }
-                                                               } else { None }
-                                                       });
-                                                       if let Some(conf_thresh) = htlc_update_pending {
-                                                               res.push(Balance::ClaimableAwaitingConfirmations {
-                                                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
-                                                                       confirmation_height: conf_thresh,
-                                                               });
-                                                       } else {
-                                                               res.push(Balance::MaybeClaimableHTLCAwaitingTimeout {
-                                                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
-                                                                       claimable_height: htlc.cltv_expiry,
-                                                               });
-                                                       }
-                                               } else if us.payment_preimages.get(&htlc.payment_hash).is_some() {
-                                                       // Otherwise (the payment was inbound), only expose it as claimable if
-                                                       // we know the preimage.
-                                                       // Note that if there is a pending claim, but it did not use the
-                                                       // preimage, we lost funds to our counterparty! We will then continue
-                                                       // to show it as ContentiousClaimable until ANTI_REORG_DELAY.
-                                                       let htlc_spend_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
-                                                               if let OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. } = event.event {
-                                                                       if commitment_tx_output_idx == htlc_commitment_tx_output_idx {
-                                                                               Some((event.confirmation_threshold(), preimage.is_some()))
-                                                                       } else { None }
-                                                               } else { None }
-                                                       });
-                                                       if let Some((conf_thresh, true)) = htlc_spend_pending {
-                                                               res.push(Balance::ClaimableAwaitingConfirmations {
-                                                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
-                                                                       confirmation_height: conf_thresh,
-                                                               });
-                                                       } else {
-                                                               res.push(Balance::ContentiousClaimable {
-                                                                       claimable_amount_satoshis: htlc.amount_msat / 1000,
-                                                                       timeout_height: htlc.cltv_expiry,
-                                                               });
-                                                       }
+                                       if htlc.transaction_output_index.is_some() {
+
+                                               if let Some(bal) = us.get_htlc_balance(htlc, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid) {
+                                                       res.push(bal);
                                                }
                                        }
                                }
@@ -1514,8 +1623,8 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
 
                if let Some(txid) = confirmed_txid {
                        let mut found_commitment_tx = false;
-                       if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
-                               walk_htlcs!(false, us.counterparty_claimable_outpoints.get(&txid).unwrap().iter().map(|(a, _)| a));
+                       if let Some(counterparty_tx_htlcs) = us.counterparty_claimable_outpoints.get(&txid) {
+                               // First look for the to_remote output back to us.
                                if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
                                        if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
                                                if let OnchainEvent::MaturingOutput {
@@ -1534,9 +1643,50 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
                                                // confirmation with the same height or have never met our dust amount.
                                        }
                                }
+                               if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
+                                       walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, _)| a));
+                               } else {
+                                       walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, _)| a));
+                                       // The counterparty broadcasted a revoked state!
+                                       // Look for any StaticOutputs first, generating claimable balances for those.
+                                       // If any match the confirmed counterparty revoked to_self output, skip
+                                       // generating a CounterpartyRevokedOutputClaimable.
+                                       let mut spent_counterparty_output = false;
+                                       for event in us.onchain_events_awaiting_threshold_conf.iter() {
+                                               if let OnchainEvent::MaturingOutput {
+                                                       descriptor: SpendableOutputDescriptor::StaticOutput { output, .. }
+                                               } = &event.event {
+                                                       res.push(Balance::ClaimableAwaitingConfirmations {
+                                                               claimable_amount_satoshis: output.value,
+                                                               confirmation_height: event.confirmation_threshold(),
+                                                       });
+                                                       if let Some(confirmed_to_self_idx) = confirmed_counterparty_output.map(|(idx, _)| idx) {
+                                                               if event.transaction.as_ref().map(|tx|
+                                                                       tx.input.iter().any(|inp| inp.previous_output.vout == confirmed_to_self_idx)
+                                                               ).unwrap_or(false) {
+                                                                       spent_counterparty_output = true;
+                                                               }
+                                                       }
+                                               }
+                                       }
+
+                                       if spent_counterparty_output {
+                                       } else if let Some((confirmed_to_self_idx, amt)) = confirmed_counterparty_output {
+                                               let output_spendable = us.onchain_tx_handler
+                                                       .is_output_spend_pending(&BitcoinOutPoint::new(txid, confirmed_to_self_idx));
+                                               if output_spendable {
+                                                       res.push(Balance::CounterpartyRevokedOutputClaimable {
+                                                               claimable_amount_satoshis: amt,
+                                                       });
+                                               }
+                                       } else {
+                                               // Counterparty output is missing, either it was broadcasted on a
+                                               // previous version of LDK or the counterparty hadn't met dust.
+                                       }
+                               }
                                found_commitment_tx = true;
                        } else if txid == us.current_holder_commitment_tx.txid {
-                               walk_htlcs!(true, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
+                               walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
                                if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
                                        res.push(Balance::ClaimableAwaitingConfirmations {
                                                claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
@@ -1546,7 +1696,7 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
                                found_commitment_tx = true;
                        } else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
                                if txid == prev_commitment.txid {
-                                       walk_htlcs!(true, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
+                                       walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
                                        if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
                                                res.push(Balance::ClaimableAwaitingConfirmations {
                                                        claimable_amount_satoshis: prev_commitment.to_self_value_sat,
@@ -1567,8 +1717,6 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
                                        });
                                }
                        }
-                       // TODO: Add logic to provide claimable balances for counterparty broadcasting revoked
-                       // outputs.
                } else {
                        let mut claimable_inbound_htlc_value_sat = 0;
                        for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() {
@@ -2727,7 +2875,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
                                                htlc_value_satoshis,
                                        }));
                                        if let Some(idx) = commitment_tx_output_idx {
-                                               self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC { commitment_tx_output_idx: idx, payment_preimage: None });
+                                               self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC {
+                                                       commitment_tx_output_idx: idx, resolving_txid: Some(entry.txid),
+                                                       payment_preimage: None,
+                                               });
                                        }
                                },
                                OnchainEvent::MaturingOutput { descriptor } => {
@@ -2737,7 +2888,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
                                        });
                                },
                                OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. } => {
-                                       self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC { commitment_tx_output_idx, payment_preimage: preimage });
+                                       self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC {
+                                               commitment_tx_output_idx, resolving_txid: Some(entry.txid),
+                                               payment_preimage: preimage,
+                                       });
                                },
                                OnchainEvent::FundingSpendConfirmation { commitment_tx_to_counterparty_output, .. } => {
                                        self.funding_spend_confirmed = Some(entry.txid);