From: Matt Corallo Date: Tue, 24 May 2022 23:57:56 +0000 (+0000) Subject: Expose counterparty-revoked-outputs in `get_claimable_balance` X-Git-Tag: v0.0.111~34^2~1 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=5a8ede09fb3c8bbcd8694d94c12dac9ea7485537;p=rust-lightning Expose counterparty-revoked-outputs in `get_claimable_balance` This uses the various new tracking added in the prior commits to expose a new `Balance` type - `CounterpartyRevokedOutputClaimable`. Some nontrivial work is required, however, as we now have to track HTLC outputs as spendable in a transaction that comes *after* an HTLC-Success/HTLC-Timeout transaction, which we previously didn't need to do. Thus, we have to check if an `onchain_events_awaiting_threshold_conf` event spends a commitment transaction's HTLC output while walking events. Further, because we now need to track HTLC outputs after the HTLC-Success/HTLC-Timeout confirms, and because we have to track the counterparty's `to_self` output as a contentious output which could be claimed by either party, we have to examine the `OnchainTxHandler`'s set of outputs to spend when determining if certain outputs are still spendable. Two new tests are added which test various different transaction formats, and hopefully provide good test coverage of the various revoked output paths. --- diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 863ce4759..08e5fedd8 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -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, /// 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,6 +586,18 @@ 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. @@ -1421,9 +1437,9 @@ impl ChannelMonitor { /// 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. @@ -1432,9 +1448,13 @@ impl ChannelMonitor { 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 } }); @@ -1446,9 +1466,10 @@ impl ChannelMonitor { } 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 { + 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; @@ -1456,12 +1477,16 @@ impl ChannelMonitor { 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())); }, @@ -1475,22 +1500,69 @@ impl ChannelMonitor { } } let htlc_resolved = us.htlcs_resolved_on_chain.iter() - .find(|v| v.commitment_tx_output_idx == htlc_commitment_tx_output_idx); + .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 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() { + } 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 @@ -1534,8 +1606,8 @@ impl ChannelMonitor { 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 { @@ -1554,9 +1626,50 @@ impl ChannelMonitor { // 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, @@ -1566,7 +1679,7 @@ impl ChannelMonitor { 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, @@ -1587,8 +1700,6 @@ impl ChannelMonitor { }); } } - // 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() { diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 8f62c43c4..0f2edff5e 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -691,6 +691,10 @@ impl OnchainTxHandler { } } + pub(crate) fn is_output_spend_pending(&self, outpoint: &BitcoinOutPoint) -> bool { + 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 .iter() diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 4c91d4f7b..85077683f 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1534,13 +1534,11 @@ macro_rules! expect_payment_failed { }; } -pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>( - node: &'a Node<'b, 'c, 'd>, expected_payment_hash: PaymentHash, expected_rejected_by_dest: bool, - conditions: PaymentFailedConditions<'e> +pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( + node: &'a Node<'b, 'c, 'd>, payment_failed_event: Event, expected_payment_hash: PaymentHash, + expected_rejected_by_dest: bool, conditions: PaymentFailedConditions<'e> ) { - let mut events = node.node.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - let expected_payment_id = match events.pop().unwrap() { + let expected_payment_id = match payment_failed_event { Event::PaymentPathFailed { payment_hash, rejected_by_dest, path, retry, payment_id, network_update, short_channel_id, #[cfg(test)] error_code, @@ -1603,6 +1601,15 @@ pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>( } } +pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>( + node: &'a Node<'b, 'c, 'd>, expected_payment_hash: PaymentHash, expected_rejected_by_dest: bool, + conditions: PaymentFailedConditions<'e> +) { + let mut events = node.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + expect_payment_failed_conditions_event(node, events.pop().unwrap(), expected_payment_hash, expected_rejected_by_dest, conditions); +} + pub fn send_along_route_with_secret<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, route: Route, expected_paths: &[&[&Node<'a, 'b, 'c>]], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: PaymentSecret) -> PaymentId { let payment_id = origin_node.node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap(); check_added_monitors!(origin_node, expected_paths.len()); diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 9be2059a7..67ea07f2a 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -11,6 +11,7 @@ use chain::channelmonitor::{ANTI_REORG_DELAY, Balance}; use chain::transaction::OutPoint; +use chain::chaininterface::LowerBoundedFeeEstimator; use ln::channel; use ln::channelmanager::BREAKDOWN_TIMEOUT; use ln::features::InitFeatures; @@ -228,6 +229,17 @@ fn sorted_vec(mut v: Vec) -> Vec { v } +/// Asserts that `a` and `b` are close, but maybe off by up to 5. +/// This is useful when checking fees and weights on transactions as things may vary by a few based +/// on signature size and signature size estimation being non-exact. +fn fuzzy_assert_eq>(a: V, b: V) { + let a_u64 = a.try_into().map_err(|_| ()).unwrap(); + let b_u64 = b.try_into().map_err(|_| ()).unwrap(); + eprintln!("Checking {} and {} for fuzzy equality", a_u64, b_u64); + assert!(a_u64 >= b_u64 - 5); + assert!(b_u64 >= a_u64 - 5); +} + fn do_test_claim_value_force_close(prev_commitment_tx: bool) { // Tests `get_claimable_balances` with an HTLC across a force-close. // We build a channel with an HTLC pending, then force close the channel and check that the @@ -734,3 +746,649 @@ fn test_balances_on_local_commitment_htlcs() { assert!(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances().is_empty()); test_spendable_output(&nodes[0], &as_txn[1]); } + +fn sorted_vec_with_additions(v_orig: &Vec, extra_ts: &[&T]) -> Vec { + let mut v = v_orig.clone(); + for t in extra_ts { + v.push((*t).clone()); + } + v.sort_unstable(); + v +} + +fn do_test_revoked_counterparty_commitment_balances(confirm_htlc_spend_first: bool) { + // Tests `get_claimable_balances` for revoked counterparty commitment transactions. + let mut chanmon_cfgs = create_chanmon_cfgs(2); + // We broadcast a second-to-latest commitment transaction, without providing the revocation + // secret to the counterparty. However, because we always immediately take the revocation + // secret from the keys_manager, we would panic at broadcast as we're trying to sign a + // transaction which, from the point of view of our keys_manager, is revoked. + chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true; + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id, funding_tx) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 100_000_000, InitFeatures::known(), InitFeatures::known()); + let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; + assert_eq!(funding_outpoint.to_channel_id(), chan_id); + + // We create five HTLCs for B to claim against A's revoked commitment transaction: + // + // (1) one for which A is the originator and B knows the preimage + // (2) one for which B is the originator where the HTLC has since timed-out + // (3) one for which B is the originator but where the HTLC has not yet timed-out + // (4) one dust HTLC which is lost in the channel closure + // (5) one that actually isn't in the revoked commitment transaction at all, but was added in + // later commitment transaction updates + // + // Though they could all be claimed in a single claim transaction, due to CLTV timeouts they + // are all currently claimed in separate transactions, which helps us test as we can claim + // HTLCs individually. + + let (claimed_payment_preimage, claimed_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000); + let timeout_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 4_000_000).1; + let dust_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 3_000).1; + + let htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety + + connect_blocks(&nodes[0], 10); + connect_blocks(&nodes[1], 10); + + let live_htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety + let live_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 5_000_000).1; + + // Get the latest commitment transaction from A and then update the fee to revoke it + let as_revoked_txn = get_local_commitment_txn!(nodes[0], chan_id); + let opt_anchors = get_opt_anchors!(nodes[0], chan_id); + + let chan_feerate = get_feerate!(nodes[0], chan_id) as u64; + + let missing_htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety + let missing_htlc_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 2_000_000).1; + + nodes[1].node.claim_funds(claimed_payment_preimage); + expect_payment_claimed!(nodes[1], claimed_payment_hash, 3_000_000); + check_added_monitors!(nodes[1], 1); + let _b_htlc_msgs = get_htlc_update_msgs!(&nodes[1], nodes[0].node.get_our_node_id()); + + connect_blocks(&nodes[0], htlc_cltv_timeout + 1 - 10); + check_closed_broadcast!(nodes[0], true); + check_added_monitors!(nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 6); + let mut failed_payments: HashSet<_> = + [timeout_payment_hash, dust_payment_hash, live_payment_hash, missing_htlc_payment_hash] + .iter().map(|a| *a).collect(); + events.retain(|ev| { + match ev { + Event::HTLCHandlingFailed { failed_next_destination: HTLCDestination::NextHopChannel { node_id, channel_id }, .. } => { + assert_eq!(*channel_id, chan_id); + assert_eq!(*node_id, Some(nodes[1].node.get_our_node_id())); + false + }, + Event::HTLCHandlingFailed { failed_next_destination: HTLCDestination::FailedPayment { payment_hash }, .. } => { + assert!(failed_payments.remove(payment_hash)); + false + }, + _ => true, + } + }); + assert!(failed_payments.is_empty()); + if let Event::PendingHTLCsForwardable { .. } = events[0] {} else { panic!(); } + match &events[1] { + Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {}, + _ => panic!(), + } + + connect_blocks(&nodes[1], htlc_cltv_timeout + 1 - 10); + check_closed_broadcast!(nodes[1], true); + check_added_monitors!(nodes[1], 1); + check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed); + + // Prior to channel closure, B considers the preimage HTLC as its own, and otherwise only + // lists the two on-chain timeout-able HTLCs as claimable balances. + assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { + claimable_amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000, + }, Balance::MaybeClaimableHTLCAwaitingTimeout { + claimable_amount_satoshis: 2_000, + claimable_height: missing_htlc_cltv_timeout, + }, Balance::MaybeClaimableHTLCAwaitingTimeout { + claimable_amount_satoshis: 4_000, + claimable_height: htlc_cltv_timeout, + }, Balance::MaybeClaimableHTLCAwaitingTimeout { + claimable_amount_satoshis: 5_000, + claimable_height: live_htlc_cltv_timeout, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[1], &as_revoked_txn[0]); + let mut claim_txn: Vec<_> = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().drain(..).filter(|tx| tx.input.iter().any(|inp| inp.previous_output.txid == as_revoked_txn[0].txid())).collect(); + // Currently the revoked commitment is claimed in four transactions as the HTLCs all expire + // quite soon. + assert_eq!(claim_txn.len(), 4); + claim_txn.sort_unstable_by_key(|tx| tx.output.iter().map(|output| output.value).sum::()); + + // The following constants were determined experimentally + const BS_TO_SELF_CLAIM_EXP_WEIGHT: usize = 483; + const OUTBOUND_HTLC_CLAIM_EXP_WEIGHT: usize = 571; + const INBOUND_HTLC_CLAIM_EXP_WEIGHT: usize = 578; + + // Check that the weight is close to the expected weight. Note that signature sizes vary + // somewhat so it may not always be exact. + fuzzy_assert_eq(claim_txn[0].weight(), OUTBOUND_HTLC_CLAIM_EXP_WEIGHT); + fuzzy_assert_eq(claim_txn[1].weight(), INBOUND_HTLC_CLAIM_EXP_WEIGHT); + fuzzy_assert_eq(claim_txn[2].weight(), INBOUND_HTLC_CLAIM_EXP_WEIGHT); + fuzzy_assert_eq(claim_txn[3].weight(), BS_TO_SELF_CLAIM_EXP_WEIGHT); + + // The expected balance for the next three checks, with the largest-HTLC and to_self output + // claim balances separated out. + let expected_balance = vec![Balance::ClaimableAwaitingConfirmations { + // to_remote output in A's revoked commitment + claimable_amount_satoshis: 100_000 - 5_000 - 4_000 - 3, + confirmation_height: nodes[1].best_block_info().1 + 5, + }, Balance::CounterpartyRevokedOutputClaimable { + claimable_amount_satoshis: 3_000, + }, Balance::CounterpartyRevokedOutputClaimable { + claimable_amount_satoshis: 4_000, + }]; + + let to_self_unclaimed_balance = Balance::CounterpartyRevokedOutputClaimable { + claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + }; + let to_self_claimed_avail_height; + let largest_htlc_unclaimed_balance = Balance::CounterpartyRevokedOutputClaimable { + claimable_amount_satoshis: 5_000, + }; + let largest_htlc_claimed_avail_height; + + // Once the channel has been closed by A, B now considers all of the commitment transactions' + // outputs as `CounterpartyRevokedOutputClaimable`. + assert_eq!(sorted_vec_with_additions(&expected_balance, &[&to_self_unclaimed_balance, &largest_htlc_unclaimed_balance]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + if confirm_htlc_spend_first { + mine_transaction(&nodes[1], &claim_txn[2]); + largest_htlc_claimed_avail_height = nodes[1].best_block_info().1 + 5; + to_self_claimed_avail_height = nodes[1].best_block_info().1 + 6; // will be claimed in the next block + } else { + // Connect the to_self output claim, taking all of A's non-HTLC funds + mine_transaction(&nodes[1], &claim_txn[3]); + to_self_claimed_avail_height = nodes[1].best_block_info().1 + 5; + largest_htlc_claimed_avail_height = nodes[1].best_block_info().1 + 6; // will be claimed in the next block + } + + let largest_htlc_claimed_balance = Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: 5_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000, + confirmation_height: largest_htlc_claimed_avail_height, + }; + let to_self_claimed_balance = Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 + - chan_feerate * claim_txn[3].weight() as u64 / 1000, + confirmation_height: to_self_claimed_avail_height, + }; + + if confirm_htlc_spend_first { + assert_eq!(sorted_vec_with_additions(&expected_balance, &[&to_self_unclaimed_balance, &largest_htlc_claimed_balance]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + } else { + assert_eq!(sorted_vec_with_additions(&expected_balance, &[&to_self_claimed_balance, &largest_htlc_unclaimed_balance]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + } + + if confirm_htlc_spend_first { + mine_transaction(&nodes[1], &claim_txn[3]); + } else { + mine_transaction(&nodes[1], &claim_txn[2]); + } + assert_eq!(sorted_vec_with_additions(&expected_balance, &[&to_self_claimed_balance, &largest_htlc_claimed_balance]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + // Finally, connect the last two remaining HTLC spends and check that they move to + // `ClaimableAwaitingConfirmations` + mine_transaction(&nodes[1], &claim_txn[0]); + mine_transaction(&nodes[1], &claim_txn[1]); + + assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations { + // to_remote output in A's revoked commitment + claimable_amount_satoshis: 100_000 - 5_000 - 4_000 - 3, + confirmation_height: nodes[1].best_block_info().1 + 1, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 + - chan_feerate * claim_txn[3].weight() as u64 / 1000, + confirmation_height: to_self_claimed_avail_height, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: 3_000 - chan_feerate * OUTBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000, + confirmation_height: nodes[1].best_block_info().1 + 4, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: 4_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000, + confirmation_height: nodes[1].best_block_info().1 + 5, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: 5_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000, + confirmation_height: largest_htlc_claimed_avail_height, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[1], 1); + test_spendable_output(&nodes[1], &as_revoked_txn[0]); + + let mut payment_failed_events = nodes[1].node.get_and_clear_pending_events(); + expect_payment_failed_conditions_event(&nodes[1], payment_failed_events.pop().unwrap(), + dust_payment_hash, true, PaymentFailedConditions::new()); + expect_payment_failed_conditions_event(&nodes[1], payment_failed_events.pop().unwrap(), + missing_htlc_payment_hash, true, PaymentFailedConditions::new()); + assert!(payment_failed_events.is_empty()); + + connect_blocks(&nodes[1], 1); + test_spendable_output(&nodes[1], &claim_txn[if confirm_htlc_spend_first { 2 } else { 3 }]); + connect_blocks(&nodes[1], 1); + test_spendable_output(&nodes[1], &claim_txn[if confirm_htlc_spend_first { 3 } else { 2 }]); + expect_payment_failed!(nodes[1], live_payment_hash, true); + connect_blocks(&nodes[1], 1); + test_spendable_output(&nodes[1], &claim_txn[0]); + connect_blocks(&nodes[1], 1); + test_spendable_output(&nodes[1], &claim_txn[1]); + expect_payment_failed!(nodes[1], timeout_payment_hash, true); + assert_eq!(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances(), Vec::new()); +} + +#[test] +fn test_revoked_counterparty_commitment_balances() { + do_test_revoked_counterparty_commitment_balances(true); + do_test_revoked_counterparty_commitment_balances(false); +} + +#[test] +fn test_revoked_counterparty_htlc_tx_balances() { + // Tests `get_claimable_balances` for revocation spends of HTLC transactions. + let mut chanmon_cfgs = create_chanmon_cfgs(2); + chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true; + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Create some initial channels + let (_, _, chan_id, funding_tx) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 11_000_000, InitFeatures::known(), InitFeatures::known()); + let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; + assert_eq!(funding_outpoint.to_channel_id(), chan_id); + + let payment_preimage = route_payment(&nodes[0], &[&nodes[1]], 3_000_000).0; + let failed_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 1_000_000).1; + let revoked_local_txn = get_local_commitment_txn!(nodes[1], chan_id); + assert_eq!(revoked_local_txn[0].input.len(), 1); + assert_eq!(revoked_local_txn[0].input[0].previous_output.txid, funding_tx.txid()); + + // The to-be-revoked commitment tx should have two HTLCs and an output for both sides + assert_eq!(revoked_local_txn[0].output.len(), 4); + + claim_payment(&nodes[0], &[&nodes[1]], payment_preimage); + + let chan_feerate = get_feerate!(nodes[0], chan_id) as u64; + let opt_anchors = get_opt_anchors!(nodes[0], chan_id); + + // B will generate an HTLC-Success from its revoked commitment tx + mine_transaction(&nodes[1], &revoked_local_txn[0]); + check_closed_broadcast!(nodes[1], true); + check_added_monitors!(nodes[1], 1); + check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed); + let revoked_htlc_success_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + + assert_eq!(revoked_htlc_success_txn.len(), 2); + assert_eq!(revoked_htlc_success_txn[0].input.len(), 1); + assert_eq!(revoked_htlc_success_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT); + check_spends!(revoked_htlc_success_txn[0], revoked_local_txn[0]); + check_spends!(revoked_htlc_success_txn[1], funding_tx); + + connect_blocks(&nodes[1], TEST_FINAL_CLTV); + let revoked_htlc_timeout_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + assert_eq!(revoked_htlc_timeout_txn.len(), 1); + check_spends!(revoked_htlc_timeout_txn[0], revoked_local_txn[0]); + assert_ne!(revoked_htlc_success_txn[0].input[0].previous_output, revoked_htlc_timeout_txn[0].input[0].previous_output); + assert_eq!(revoked_htlc_success_txn[0].lock_time.0, 0); + assert_ne!(revoked_htlc_timeout_txn[0].lock_time.0, 0); + + // A will generate justice tx from B's revoked commitment/HTLC tx + mine_transaction(&nodes[0], &revoked_local_txn[0]); + check_closed_broadcast!(nodes[0], true); + check_added_monitors!(nodes[0], 1); + check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed); + let to_remote_conf_height = nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1; + + let as_commitment_claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + assert_eq!(as_commitment_claim_txn.len(), 2); + check_spends!(as_commitment_claim_txn[0], revoked_local_txn[0]); + check_spends!(as_commitment_claim_txn[1], funding_tx); + + // The next two checks have the same balance set for A - even though we confirm a revoked HTLC + // transaction our balance tracking doesn't use the on-chain value so the + // `CounterpartyRevokedOutputClaimable` entry doesn't change. + let as_balances = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations { + // to_remote output in B's revoked commitment + claimable_amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + confirmation_height: to_remote_conf_height, + }, Balance::CounterpartyRevokedOutputClaimable { + // to_self output in B's revoked commitment + claimable_amount_satoshis: 10_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1 + claimable_amount_satoshis: 3_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + claimable_amount_satoshis: 1_000, + }]); + assert_eq!(as_balances, + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[0], &revoked_htlc_success_txn[0]); + let as_htlc_claim_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + assert_eq!(as_htlc_claim_tx.len(), 2); + check_spends!(as_htlc_claim_tx[0], revoked_htlc_success_txn[0]); + check_spends!(as_htlc_claim_tx[1], revoked_local_txn[0]); // A has to generate a new claim for the remaining revoked + // outputs (which no longer includes the spent HTLC output) + + assert_eq!(as_balances, + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + assert_eq!(as_htlc_claim_tx[0].output.len(), 1); + fuzzy_assert_eq(as_htlc_claim_tx[0].output[0].value, + 3_000 - chan_feerate * (revoked_htlc_success_txn[0].weight() + as_htlc_claim_tx[0].weight()) as u64 / 1000); + + mine_transaction(&nodes[0], &as_htlc_claim_tx[0]); + assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations { + // to_remote output in B's revoked commitment + claimable_amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + confirmation_height: to_remote_conf_height, + }, Balance::CounterpartyRevokedOutputClaimable { + // to_self output in B's revoked commitment + claimable_amount_satoshis: 10_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + claimable_amount_satoshis: 1_000, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: as_htlc_claim_tx[0].output[0].value, + confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1, + }]), + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[0], ANTI_REORG_DELAY - 3); + test_spendable_output(&nodes[0], &revoked_local_txn[0]); + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output to B + claimable_amount_satoshis: 10_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + claimable_amount_satoshis: 1_000, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: as_htlc_claim_tx[0].output[0].value, + confirmation_height: nodes[0].best_block_info().1 + 2, + }]), + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[0], 2); + test_spendable_output(&nodes[0], &as_htlc_claim_tx[0]); + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output in B's revoked commitment + claimable_amount_satoshis: 10_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + claimable_amount_satoshis: 1_000, + }]), + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[0], revoked_htlc_timeout_txn[0].lock_time.0 - nodes[0].best_block_info().1); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(&nodes[0], + [HTLCDestination::FailedPayment { payment_hash: failed_payment_hash }]); + // As time goes on A may split its revocation claim transaction into multiple. + let as_fewer_input_rbf = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + for tx in as_fewer_input_rbf.iter() { + check_spends!(tx, revoked_local_txn[0]); + } + + // Connect a number of additional blocks to ensure we don't forget the HTLC output needs + // claiming. + connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1); + let as_fewer_input_rbf = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + for tx in as_fewer_input_rbf.iter() { + check_spends!(tx, revoked_local_txn[0]); + } + + mine_transaction(&nodes[0], &revoked_htlc_timeout_txn[0]); + let as_second_htlc_claim_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + assert_eq!(as_second_htlc_claim_tx.len(), 2); + + check_spends!(as_second_htlc_claim_tx[0], revoked_htlc_timeout_txn[0]); + check_spends!(as_second_htlc_claim_tx[1], revoked_local_txn[0]); + + // Connect blocks to finalize the HTLC resolution with the HTLC-Timeout transaction. In a + // previous iteration of the revoked balance handling this would result in us "forgetting" that + // the revoked HTLC output still needed to be claimed. + connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1); + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output in B's revoked commitment + claimable_amount_satoshis: 10_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + claimable_amount_satoshis: 1_000, + }]), + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[0], &as_second_htlc_claim_tx[0]); + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output in B's revoked commitment + claimable_amount_satoshis: 10_000, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: as_second_htlc_claim_tx[0].output[0].value, + confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1, + }]), + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[0], &as_second_htlc_claim_tx[1]); + assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations { + // to_self output in B's revoked commitment + claimable_amount_satoshis: as_second_htlc_claim_tx[1].output[0].value, + confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1, + }, Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: as_second_htlc_claim_tx[0].output[0].value, + confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 2, + }]), + sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[0], ANTI_REORG_DELAY - 2); + test_spendable_output(&nodes[0], &as_second_htlc_claim_tx[0]); + connect_blocks(&nodes[0], 1); + test_spendable_output(&nodes[0], &as_second_htlc_claim_tx[1]); + + assert_eq!(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances(), Vec::new()); +} + +#[test] +fn test_revoked_counterparty_aggregated_claims() { + // Tests `get_claimable_balances` for revoked counterparty commitment transactions when + // claiming with an aggregated claim transaction. + let mut chanmon_cfgs = create_chanmon_cfgs(2); + // We broadcast a second-to-latest commitment transaction, without providing the revocation + // secret to the counterparty. However, because we always immediately take the revocation + // secret from the keys_manager, we would panic at broadcast as we're trying to sign a + // transaction which, from the point of view of our keys_manager, is revoked. + chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true; + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id, funding_tx) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 100_000_000, InitFeatures::known(), InitFeatures::known()); + let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; + assert_eq!(funding_outpoint.to_channel_id(), chan_id); + + // We create two HTLCs, one which we will give A the preimage to to generate an HTLC-Success + // transaction, and one which we will not, allowing B to claim the HTLC output in an aggregated + // revocation-claim transaction. + + let (claimed_payment_preimage, claimed_payment_hash, ..) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000); + let revoked_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 4_000_000).1; + + let htlc_cltv_timeout = nodes[1].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety + + // Cheat by giving A's ChannelMonitor the preimage to the to-be-claimed HTLC so that we have an + // HTLC-claim transaction on the to-be-revoked state. + get_monitor!(nodes[0], chan_id).provide_payment_preimage(&claimed_payment_hash, &claimed_payment_preimage, + &node_cfgs[0].tx_broadcaster, &LowerBoundedFeeEstimator::new(node_cfgs[0].fee_estimator), &nodes[0].logger); + + // Now get the latest commitment transaction from A and then update the fee to revoke it + let as_revoked_txn = get_local_commitment_txn!(nodes[0], chan_id); + + assert_eq!(as_revoked_txn.len(), 2); + check_spends!(as_revoked_txn[0], funding_tx); + check_spends!(as_revoked_txn[1], as_revoked_txn[0]); // The HTLC-Claim transaction + + let opt_anchors = get_opt_anchors!(nodes[0], chan_id); + let chan_feerate = get_feerate!(nodes[0], chan_id) as u64; + + { + let mut feerate = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap(); + *feerate += 1; + } + nodes[0].node.timer_tick_occurred(); + check_added_monitors!(nodes[0], 1); + + let fee_update = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fee(&nodes[0].node.get_our_node_id(), &fee_update.update_fee.unwrap()); + commitment_signed_dance!(nodes[1], nodes[0], fee_update.commitment_signed, false); + + nodes[0].node.claim_funds(claimed_payment_preimage); + expect_payment_claimed!(nodes[0], claimed_payment_hash, 3_000_000); + check_added_monitors!(nodes[0], 1); + let _a_htlc_msgs = get_htlc_update_msgs!(&nodes[0], nodes[1].node.get_our_node_id()); + + assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose { + claimable_amount_satoshis: 100_000 - 4_000 - 3_000, + }, Balance::MaybeClaimableHTLCAwaitingTimeout { + claimable_amount_satoshis: 4_000, + claimable_height: htlc_cltv_timeout, + }, Balance::MaybeClaimableHTLCAwaitingTimeout { + claimable_amount_satoshis: 3_000, + claimable_height: htlc_cltv_timeout, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[1], &as_revoked_txn[0]); + check_closed_broadcast!(nodes[1], true); + check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed); + check_added_monitors!(nodes[1], 1); + + let mut claim_txn: Vec<_> = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().drain(..).filter(|tx| tx.input.iter().any(|inp| inp.previous_output.txid == as_revoked_txn[0].txid())).collect(); + // Currently the revoked commitment outputs are all claimed in one aggregated transaction + assert_eq!(claim_txn.len(), 1); + assert_eq!(claim_txn[0].input.len(), 3); + check_spends!(claim_txn[0], as_revoked_txn[0]); + + let to_remote_maturity = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1; + + assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations { + // to_remote output in A's revoked commitment + claimable_amount_satoshis: 100_000 - 4_000 - 3_000, + confirmation_height: to_remote_maturity, + }, Balance::CounterpartyRevokedOutputClaimable { + // to_self output in A's revoked commitment + claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1 + claimable_amount_satoshis: 4_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + claimable_amount_satoshis: 3_000, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + // Confirm A's HTLC-Success tranasction which presumably raced B's claim, causing B to create a + // new claim. + mine_transaction(&nodes[1], &as_revoked_txn[1]); + expect_payment_sent!(nodes[1], claimed_payment_preimage); + let mut claim_txn_2: Vec<_> = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); + claim_txn_2.sort_unstable_by_key(|tx| if tx.input.iter().any(|inp| inp.previous_output.txid == as_revoked_txn[0].txid()) { 0 } else { 1 }); + // Once B sees the HTLC-Success transaction it splits its claim transaction into two, though in + // theory it could re-aggregate the claims as well. + assert_eq!(claim_txn_2.len(), 2); + assert_eq!(claim_txn_2[0].input.len(), 2); + check_spends!(claim_txn_2[0], as_revoked_txn[0]); + assert_eq!(claim_txn_2[1].input.len(), 1); + check_spends!(claim_txn_2[1], as_revoked_txn[1]); + + assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations { + // to_remote output in A's revoked commitment + claimable_amount_satoshis: 100_000 - 4_000 - 3_000, + confirmation_height: to_remote_maturity, + }, Balance::CounterpartyRevokedOutputClaimable { + // to_self output in A's revoked commitment + claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1 + claimable_amount_satoshis: 4_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + // The amount here is a bit of a misnomer, really its been reduced by the HTLC + // transaction fee, but the claimable amount is always a bit of an overshoot for HTLCs + // anyway, so its not a big change. + claimable_amount_satoshis: 3_000, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[1], 5); + test_spendable_output(&nodes[1], &as_revoked_txn[0]); + + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output in A's revoked commitment + claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1 + claimable_amount_satoshis: 4_000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2 + // The amount here is a bit of a misnomer, really its been reduced by the HTLC + // transaction fee, but the claimable amount is always a bit of an overshoot for HTLCs + // anyway, so its not a big change. + claimable_amount_satoshis: 3_000, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[1], &claim_txn_2[1]); + let htlc_2_claim_maturity = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1; + + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output in A's revoked commitment + claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1 + claimable_amount_satoshis: 4_000, + }, Balance::ClaimableAwaitingConfirmations { // HTLC 2 + claimable_amount_satoshis: claim_txn_2[1].output[0].value, + confirmation_height: htlc_2_claim_maturity, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + connect_blocks(&nodes[1], 5); + test_spendable_output(&nodes[1], &claim_txn_2[1]); + + assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable { + // to_self output in A's revoked commitment + claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate * + (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000, + }, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1 + claimable_amount_satoshis: 4_000, + }]), + sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances())); + + mine_transaction(&nodes[1], &claim_txn_2[0]); + let rest_claim_maturity = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1; + + assert_eq!(vec![Balance::ClaimableAwaitingConfirmations { + claimable_amount_satoshis: claim_txn_2[0].output[0].value, + confirmation_height: rest_claim_maturity, + }], + nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()); + + assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); // We shouldn't fail the payment until we spend the output + + connect_blocks(&nodes[1], 5); + expect_payment_failed!(nodes[1], revoked_payment_hash, true); + test_spendable_output(&nodes[1], &claim_txn_2[0]); + assert!(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances().is_empty()); +}