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;
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`
/// 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.
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).
/// 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.
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 }
});
}
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);
}
}
}
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 {
// 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,
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,
});
}
}
- // 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() {