From 1818c4a115642613faf06ba6cf1ee41b0fba318a Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 20 Jan 2022 04:28:38 +0000 Subject: [PATCH] Include inbound-claimed-HTLCs in reported channel balances Given the balance is reported as "total balance if we went to chain ignoring fees", it seems reasonable to include claimed HTLCs - if we went to chain we'd get those funds, less on-chain fees. Further, if we do not include them, its possible to have pending outbound holding-cell HTLCs underflow the balance calculation, causing a panic in debug mode, and bogus values in release. This resolves a subtraction underflow bug found by the `chanmon_consistency` fuzz target. --- lightning/src/ln/channel.rs | 11 +++++++-- lightning/src/ln/functional_tests.rs | 37 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 4b1f4a7d..3d968206 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2155,8 +2155,15 @@ impl Channel { /// This is the amount that would go to us if we close the channel, ignoring any on-chain fees. /// See also [`Channel::get_inbound_outbound_available_balance_msat`] pub fn get_balance_msat(&self) -> u64 { - self.value_to_self_msat - - self.get_outbound_pending_htlc_stats(None).pending_htlcs_value_msat + // Include our local balance, plus any inbound HTLCs we know the preimage for, minus any + // HTLCs sent or which will be sent after commitment signed's are exchanged. + let mut balance_msat = self.value_to_self_msat; + for ref htlc in self.pending_inbound_htlcs.iter() { + if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state { + balance_msat += htlc.amount_msat; + } + } + balance_msat - self.get_outbound_pending_htlc_stats(None).pending_htlcs_value_msat } pub fn get_holder_counterparty_selected_channel_reserve_satoshis(&self) -> (u64, Option) { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index dd9d3a3c..ce6a84c9 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -8101,6 +8101,43 @@ fn test_bump_txn_sanitize_tracking_maps() { } } +#[test] +fn test_pending_claimed_htlc_no_balance_underflow() { + // Tests that if we have a pending outbound HTLC as well as a claimed-but-not-fully-removed + // HTLC we will not underflow when we call `Channel::get_balance_msat()`. + let chanmon_cfgs = create_chanmon_cfgs(2); + 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_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0, InitFeatures::known(), InitFeatures::known()); + + let payment_preimage = route_payment(&nodes[0], &[&nodes[1]], 1_010_000).0; + nodes[1].node.claim_funds(payment_preimage); + check_added_monitors!(nodes[1], 1); + let fulfill_ev = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &fulfill_ev.update_fulfill_htlcs[0]); + expect_payment_sent_without_paths!(nodes[0], payment_preimage); + nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &fulfill_ev.commitment_signed); + check_added_monitors!(nodes[0], 1); + let (_raa, _cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id()); + + // At this point nodes[1] has received 1,010k msat (10k msat more than their reserve) and can + // send an HTLC back (though it will go in the holding cell). Send an HTLC back and check we + // can get our balance. + + // Get a route from nodes[1] to nodes[0] by getting a route going the other way and then flip + // the public key of the only hop. This works around ChannelDetails not showing the + // almost-claimed HTLC as available balance. + let (mut route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 10_000); + route.payment_params = None; // This is all wrong, but unnecessary + route.paths[0][0].pubkey = nodes[0].node.get_our_node_id(); + let (_, payment_hash_2, payment_secret_2) = get_payment_preimage_hash!(nodes[0]); + nodes[1].node.send_payment(&route, payment_hash_2, &Some(payment_secret_2)).unwrap(); + + assert_eq!(nodes[1].node.list_channels()[0].balance_msat, 1_000_000); +} + #[test] fn test_channel_conf_timeout() { // Tests that, for inbound channels, we give up on them if the funding transaction does not -- 2.30.2