X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fchain%2Fchannelmonitor.rs;h=52cfe9972f7289388caa38d8b7244c7ec6b905bc;hb=8c6cb9953a3b00ce3da25fbdfd8ada0ec48fc63f;hp=08e5fedd8d3f6f177ed3239e8418a96780413ce2;hpb=5a8ede09fb3c8bbcd8694d94c12dac9ea7485537;p=rust-lightning diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 08e5fedd..52cfe997 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -59,8 +59,13 @@ use core::convert::TryInto; use core::ops::Deref; use sync::Mutex; -/// An update generated by the underlying Channel itself which contains some new information the -/// ChannelMonitor should be made aware of. +/// An update generated by the underlying channel itself which contains some new information the +/// [`ChannelMonitor`] should be made aware of. +/// +/// Because this represents only a small number of updates to the underlying state, it is generally +/// much smaller than a full [`ChannelMonitor`]. However, for large single commitment transaction +/// updates (e.g. ones during which there are hundreds of HTLCs pending on the commitment +/// transaction), a single update may reach upwards of 1 MiB in serialized size. #[cfg_attr(any(test, fuzzing, feature = "_test_utils"), derive(PartialEq))] #[derive(Clone)] #[must_use] @@ -578,14 +583,25 @@ pub enum Balance { /// HTLCs which we sent to our counterparty which are claimable after a timeout (less on-chain /// fees) if the counterparty does not know the preimage for the HTLCs. These are somewhat /// likely to be claimed by our counterparty before we do. - MaybeClaimableHTLCAwaitingTimeout { - /// The amount available to claim, in satoshis, excluding the on-chain fees which will be - /// required to do so. + MaybeTimeoutClaimableHTLC { + /// The amount potentially available to claim, in satoshis, excluding the on-chain fees + /// which will be required to do so. claimable_amount_satoshis: u64, /// The height at which we will be able to claim the balance if our counterparty has not /// done so. claimable_height: u32, }, + /// HTLCs which we received from our counterparty which are claimable with a preimage which we + /// do not currently have. This will only be claimable if we receive the preimage from the node + /// to which we forwarded this HTLC before the timeout. + MaybePreimageClaimableHTLC { + /// The amount potentially available to claim, in satoshis, excluding the on-chain fees + /// which will be required to do so. + claimable_amount_satoshis: u64, + /// The height at which our counterparty will be able to claim the balance if we have not + /// yet received the preimage and claimed it ourselves. + expiry_height: u32, + }, /// The channel has been closed, and our counterparty broadcasted a revoked commitment /// transaction. /// @@ -1427,7 +1443,155 @@ impl ChannelMonitor { pub fn current_best_block(&self) -> BestBlock { self.inner.lock().unwrap().best_block.clone() } +} + +impl ChannelMonitorImpl { + /// 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) + -> Option { + 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::MaybeTimeoutClaimableHTLC { + 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, + }); + } + } else if htlc_resolved.is_none() { + return Some(Balance::MaybePreimageClaimableHTLC { + claimable_amount_satoshis: htlc.amount_msat / 1000, + expiry_height: htlc.cltv_expiry, + }); + } + None + } +} +impl ChannelMonitor { /// 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). @@ -1468,136 +1632,10 @@ impl ChannelMonitor { macro_rules! walk_htlcs { ($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 { - let mut htlc_spend_txid_opt = None; - let mut htlc_update_pending = None; - let mut htlc_spend_pending = None; - let mut delayed_output_pending = None; - for event in us.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!(htlc_update_pending.is_none()); - debug_assert_eq!(htlc_value_satoshis.unwrap(), htlc.amount_msat / 1000); - htlc_update_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!(delayed_output_pending.is_none()); - delayed_output_pending = Some(event.confirmation_threshold()); - }, - _ => {}, - } - } - let htlc_resolved = us.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!(htlc_update_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!( - us.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_needs_spending = us.onchain_tx_handler.is_output_spend_pending(&htlc_output_to_spend); + if htlc.transaction_output_index.is_some() { - if let Some(conf_thresh) = delayed_output_pending { - debug_assert!($holder_commitment); - res.push(Balance::ClaimableAwaitingConfirmations { - claimable_amount_satoshis: htlc.amount_msat / 1000, - confirmation_height: conf_thresh, - }); - } else if htlc_resolved.is_some() && !htlc_output_needs_spending { - // 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 $counterparty_revoked_commitment { - let htlc_output_claim_pending = us.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!(htlc_update_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?!"); - res.push(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) = 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. - debug_assert!(htlc_update_pending.is_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 let Some(bal) = us.get_htlc_balance(htlc, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid) { + res.push(bal); } } } @@ -1705,12 +1743,19 @@ impl ChannelMonitor { for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() { if htlc.transaction_output_index.is_none() { continue; } if htlc.offered { - res.push(Balance::MaybeClaimableHTLCAwaitingTimeout { + res.push(Balance::MaybeTimeoutClaimableHTLC { claimable_amount_satoshis: htlc.amount_msat / 1000, claimable_height: htlc.cltv_expiry, }); } else if us.payment_preimages.get(&htlc.payment_hash).is_some() { claimable_inbound_htlc_value_sat += htlc.amount_msat / 1000; + } else { + // As long as the HTLC is still in our latest commitment state, treat + // it as potentially claimable, even if it has long-since expired. + res.push(Balance::MaybePreimageClaimableHTLC { + claimable_amount_satoshis: htlc.amount_msat / 1000, + expiry_height: htlc.cltv_expiry, + }); } } res.push(Balance::ClaimableOnChannelClose {