Skip to content

Commit c78aa46

Browse files
committed
Include an outbound_payment flag in MaybeTimeoutClaimableHTLC
When the user is fetching their current balances after forwarding a payment (before it clears), they'll see a `MaybePreimageClaimableHTLC` and a `MaybeTimeoutClaimableHTLC` but if they sum up their balance using `Balance::claimable_amount_satoshis` neither will be included. Obviously, exactly one of the two balances should be included - one of the two resolutions should happen in our favor. This causes our visible balance to fluctuate up and down by the full value of any HTLCs we're in the middle of forwarding, which is incredibly confusing to see. If we want to stop the fluctuations, we need to pick one of the two balances to include. The obvious candidate is `MaybeTimeoutClaimableHTLC` as it is the lower of the two, and represents our balance without the fee we'd receive from the forward. Sadly, if we always include it, we'll end up also including any HTLCs which we've sent but which haven't yet been claimed by their recipient, which is the wrong behavior. Luckily, we have access to the `Option<HTLCSource>` while walking HTLCs, which allows us to add an `outbound_payment` flag to `MaybeTimeoutClaimableHTLC`. This allows us to only include forwarded payments in `claimable_amount_satoshis`. Sadly, even with this in place our balance still fluctuates by the changes in the commitment transaction fees we have to pay during forwarding, but addressing that is left for later.
1 parent 082a19b commit c78aa46

File tree

2 files changed

+49
-13
lines changed

2 files changed

+49
-13
lines changed

lightning/src/chain/channelmonitor.rs

+38-13
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,10 @@ pub enum Balance {
620620
claimable_height: u32,
621621
/// The payment hash whose preimage our counterparty needs to claim this HTLC.
622622
payment_hash: PaymentHash,
623+
/// Whether this HTLC represents a payment which was sent outbound from us. Otherwise it
624+
/// represents an HTLC which was forwarded (and should, thus, have a corresponding inbound
625+
/// edge on another channel).
626+
outbound_payment: bool,
623627
},
624628
/// HTLCs which we received from our counterparty which are claimable with a preimage which we
625629
/// do not currently have. This will only be claimable if we receive the preimage from the node
@@ -662,9 +666,9 @@ impl Balance {
662666
Balance::ContentiousClaimable { amount_satoshis, .. }|
663667
Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
664668
=> *amount_satoshis,
665-
Balance::MaybeTimeoutClaimableHTLC { .. }|
666-
Balance::MaybePreimageClaimableHTLC { .. }
667-
=> 0,
669+
Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. }
670+
=> if *outbound_payment { 0 } else { *amount_satoshis },
671+
Balance::MaybePreimageClaimableHTLC { .. } => 0,
668672
}
669673
}
670674
}
@@ -1674,9 +1678,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
16741678
impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
16751679
/// Helper for get_claimable_balances which does the work for an individual HTLC, generating up
16761680
/// to one `Balance` for the HTLC.
1677-
fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, holder_commitment: bool,
1678-
counterparty_revoked_commitment: bool, confirmed_txid: Option<Txid>)
1679-
-> Option<Balance> {
1681+
fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, source: Option<&HTLCSource>,
1682+
holder_commitment: bool, counterparty_revoked_commitment: bool,
1683+
confirmed_txid: Option<Txid>
1684+
) -> Option<Balance> {
16801685
let htlc_commitment_tx_output_idx =
16811686
if let Some(v) = htlc.transaction_output_index { v } else { return None; };
16821687

@@ -1801,10 +1806,19 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
18011806
confirmation_height: conf_thresh,
18021807
});
18031808
} else {
1809+
let outbound_payment = match source {
1810+
None => {
1811+
debug_assert!(false, "Outbound HTLCs should have a source");
1812+
true
1813+
},
1814+
Some(&HTLCSource::PreviousHopData(_)) => false,
1815+
Some(&HTLCSource::OutboundRoute { .. }) => true,
1816+
};
18041817
return Some(Balance::MaybeTimeoutClaimableHTLC {
18051818
amount_satoshis: htlc.amount_msat / 1000,
18061819
claimable_height: htlc.cltv_expiry,
18071820
payment_hash: htlc.payment_hash,
1821+
outbound_payment,
18081822
});
18091823
}
18101824
} else if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
@@ -1878,10 +1892,12 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
18781892

18791893
macro_rules! walk_htlcs {
18801894
($holder_commitment: expr, $counterparty_revoked_commitment: expr, $htlc_iter: expr) => {
1881-
for htlc in $htlc_iter {
1895+
for (htlc, source) in $htlc_iter {
18821896
if htlc.transaction_output_index.is_some() {
18831897

1884-
if let Some(bal) = us.get_htlc_balance(htlc, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid) {
1898+
if let Some(bal) = us.get_htlc_balance(
1899+
htlc, source, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid
1900+
) {
18851901
res.push(bal);
18861902
}
18871903
}
@@ -1912,9 +1928,9 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
19121928
}
19131929
}
19141930
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1915-
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, _)| a));
1931+
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
19161932
} else {
1917-
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, _)| a));
1933+
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
19181934
// The counterparty broadcasted a revoked state!
19191935
// Look for any StaticOutputs first, generating claimable balances for those.
19201936
// If any match the confirmed counterparty revoked to_self output, skip
@@ -1954,7 +1970,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
19541970
}
19551971
found_commitment_tx = true;
19561972
} else if txid == us.current_holder_commitment_tx.txid {
1957-
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
1973+
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())));
19581974
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
19591975
res.push(Balance::ClaimableAwaitingConfirmations {
19601976
amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
@@ -1964,7 +1980,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
19641980
found_commitment_tx = true;
19651981
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
19661982
if txid == prev_commitment.txid {
1967-
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
1983+
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())));
19681984
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
19691985
res.push(Balance::ClaimableAwaitingConfirmations {
19701986
amount_satoshis: prev_commitment.to_self_value_sat,
@@ -1987,13 +2003,22 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
19872003
}
19882004
} else {
19892005
let mut claimable_inbound_htlc_value_sat = 0;
1990-
for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() {
2006+
for (htlc, _, source) in us.current_holder_commitment_tx.htlc_outputs.iter() {
19912007
if htlc.transaction_output_index.is_none() { continue; }
19922008
if htlc.offered {
2009+
let outbound_payment = match source {
2010+
None => {
2011+
debug_assert!(false, "Outbound HTLCs should have a source");
2012+
true
2013+
},
2014+
Some(HTLCSource::PreviousHopData(_)) => false,
2015+
Some(HTLCSource::OutboundRoute { .. }) => true,
2016+
};
19932017
res.push(Balance::MaybeTimeoutClaimableHTLC {
19942018
amount_satoshis: htlc.amount_msat / 1000,
19952019
claimable_height: htlc.cltv_expiry,
19962020
payment_hash: htlc.payment_hash,
2021+
outbound_payment,
19972022
});
19982023
} else if us.payment_preimages.get(&htlc.payment_hash).is_some() {
19992024
claimable_inbound_htlc_value_sat += htlc.amount_msat / 1000;

lightning/src/ln/monitor_tests.rs

+11
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,13 @@ fn do_test_claim_value_force_close(prev_commitment_tx: bool) {
286286
amount_satoshis: 3_000,
287287
claimable_height: htlc_cltv_timeout,
288288
payment_hash,
289+
outbound_payment: true,
289290
};
290291
let sent_htlc_timeout_balance = Balance::MaybeTimeoutClaimableHTLC {
291292
amount_satoshis: 4_000,
292293
claimable_height: htlc_cltv_timeout,
293294
payment_hash: timeout_payment_hash,
295+
outbound_payment: true,
294296
};
295297
let received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
296298
amount_satoshis: 3_000,
@@ -627,11 +629,13 @@ fn test_balances_on_local_commitment_htlcs() {
627629
amount_satoshis: 10_000,
628630
claimable_height: htlc_cltv_timeout,
629631
payment_hash,
632+
outbound_payment: true,
630633
};
631634
let htlc_balance_unknown_preimage = Balance::MaybeTimeoutClaimableHTLC {
632635
amount_satoshis: 20_000,
633636
claimable_height: htlc_cltv_timeout,
634637
payment_hash: payment_hash_2,
638+
outbound_payment: true,
635639
};
636640

637641
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
@@ -757,6 +761,7 @@ fn test_no_preimage_inbound_htlc_balances() {
757761
amount_satoshis: 10_000,
758762
claimable_height: htlc_cltv_timeout,
759763
payment_hash: to_b_failed_payment_hash,
764+
outbound_payment: true,
760765
};
761766
let a_received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
762767
amount_satoshis: 20_000,
@@ -772,6 +777,7 @@ fn test_no_preimage_inbound_htlc_balances() {
772777
amount_satoshis: 20_000,
773778
claimable_height: htlc_cltv_timeout,
774779
payment_hash: to_a_failed_payment_hash,
780+
outbound_payment: true,
775781
};
776782

777783
// Both A and B will have an HTLC that's claimable on timeout and one that's claimable if they
@@ -1063,14 +1069,17 @@ fn do_test_revoked_counterparty_commitment_balances(confirm_htlc_spend_first: bo
10631069
amount_satoshis: 2_000,
10641070
claimable_height: missing_htlc_cltv_timeout,
10651071
payment_hash: missing_htlc_payment_hash,
1072+
outbound_payment: true,
10661073
}, Balance::MaybeTimeoutClaimableHTLC {
10671074
amount_satoshis: 4_000,
10681075
claimable_height: htlc_cltv_timeout,
10691076
payment_hash: timeout_payment_hash,
1077+
outbound_payment: true,
10701078
}, Balance::MaybeTimeoutClaimableHTLC {
10711079
amount_satoshis: 5_000,
10721080
claimable_height: live_htlc_cltv_timeout,
10731081
payment_hash: live_payment_hash,
1082+
outbound_payment: true,
10741083
}]),
10751084
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
10761085

@@ -1500,10 +1509,12 @@ fn test_revoked_counterparty_aggregated_claims() {
15001509
amount_satoshis: 4_000,
15011510
claimable_height: htlc_cltv_timeout,
15021511
payment_hash: revoked_payment_hash,
1512+
outbound_payment: true,
15031513
}, Balance::MaybeTimeoutClaimableHTLC {
15041514
amount_satoshis: 3_000,
15051515
claimable_height: htlc_cltv_timeout,
15061516
payment_hash: claimed_payment_hash,
1517+
outbound_payment: true,
15071518
}]),
15081519
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
15091520

0 commit comments

Comments
 (0)