Skip to content

Commit 05af3c7

Browse files
TheBlueMattdunxen
authored andcommitted
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 50d21b7 commit 05af3c7

File tree

2 files changed

+49
-13
lines changed

2 files changed

+49
-13
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,10 @@ pub enum Balance {
664664
claimable_height: u32,
665665
/// The payment hash whose preimage our counterparty needs to claim this HTLC.
666666
payment_hash: PaymentHash,
667+
/// Whether this HTLC represents a payment which was sent outbound from us. Otherwise it
668+
/// represents an HTLC which was forwarded (and should, thus, have a corresponding inbound
669+
/// edge on another channel).
670+
outbound_payment: bool,
667671
},
668672
/// HTLCs which we received from our counterparty which are claimable with a preimage which we
669673
/// do not currently have. This will only be claimable if we receive the preimage from the node
@@ -706,9 +710,9 @@ impl Balance {
706710
Balance::ContentiousClaimable { amount_satoshis, .. }|
707711
Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
708712
=> *amount_satoshis,
709-
Balance::MaybeTimeoutClaimableHTLC { .. }|
710-
Balance::MaybePreimageClaimableHTLC { .. }
711-
=> 0,
713+
Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. }
714+
=> if *outbound_payment { 0 } else { *amount_satoshis },
715+
Balance::MaybePreimageClaimableHTLC { .. } => 0,
712716
}
713717
}
714718
}
@@ -1960,9 +1964,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19601964
impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
19611965
/// Helper for get_claimable_balances which does the work for an individual HTLC, generating up
19621966
/// to one `Balance` for the HTLC.
1963-
fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, holder_commitment: bool,
1964-
counterparty_revoked_commitment: bool, confirmed_txid: Option<Txid>)
1965-
-> Option<Balance> {
1967+
fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, source: Option<&HTLCSource>,
1968+
holder_commitment: bool, counterparty_revoked_commitment: bool,
1969+
confirmed_txid: Option<Txid>
1970+
) -> Option<Balance> {
19661971
let htlc_commitment_tx_output_idx =
19671972
if let Some(v) = htlc.transaction_output_index { v } else { return None; };
19681973

@@ -2099,10 +2104,19 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
20992104
confirmation_height: conf_thresh,
21002105
});
21012106
} else {
2107+
let outbound_payment = match source {
2108+
None => {
2109+
debug_assert!(false, "Outbound HTLCs should have a source");
2110+
true
2111+
},
2112+
Some(&HTLCSource::PreviousHopData(_)) => false,
2113+
Some(&HTLCSource::OutboundRoute { .. }) => true,
2114+
};
21022115
return Some(Balance::MaybeTimeoutClaimableHTLC {
21032116
amount_satoshis: htlc.amount_msat / 1000,
21042117
claimable_height: htlc.cltv_expiry,
21052118
payment_hash: htlc.payment_hash,
2119+
outbound_payment,
21062120
});
21072121
}
21082122
} else if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
@@ -2175,10 +2189,12 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
21752189

21762190
macro_rules! walk_htlcs {
21772191
($holder_commitment: expr, $counterparty_revoked_commitment: expr, $htlc_iter: expr) => {
2178-
for htlc in $htlc_iter {
2192+
for (htlc, source) in $htlc_iter {
21792193
if htlc.transaction_output_index.is_some() {
21802194

2181-
if let Some(bal) = us.get_htlc_balance(htlc, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid) {
2195+
if let Some(bal) = us.get_htlc_balance(
2196+
htlc, source, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid
2197+
) {
21822198
res.push(bal);
21832199
}
21842200
}
@@ -2209,9 +2225,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22092225
}
22102226
}
22112227
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
2212-
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, _)| a));
2228+
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
22132229
} else {
2214-
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, _)| a));
2230+
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
22152231
// The counterparty broadcasted a revoked state!
22162232
// Look for any StaticOutputs first, generating claimable balances for those.
22172233
// If any match the confirmed counterparty revoked to_self output, skip
@@ -2251,7 +2267,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22512267
}
22522268
found_commitment_tx = true;
22532269
} else if txid == us.current_holder_commitment_tx.txid {
2254-
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
2270+
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())));
22552271
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
22562272
res.push(Balance::ClaimableAwaitingConfirmations {
22572273
amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
@@ -2261,7 +2277,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22612277
found_commitment_tx = true;
22622278
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
22632279
if txid == prev_commitment.txid {
2264-
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
2280+
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())));
22652281
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
22662282
res.push(Balance::ClaimableAwaitingConfirmations {
22672283
amount_satoshis: prev_commitment.to_self_value_sat,
@@ -2284,13 +2300,22 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22842300
}
22852301
} else {
22862302
let mut claimable_inbound_htlc_value_sat = 0;
2287-
for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() {
2303+
for (htlc, _, source) in us.current_holder_commitment_tx.htlc_outputs.iter() {
22882304
if htlc.transaction_output_index.is_none() { continue; }
22892305
if htlc.offered {
2306+
let outbound_payment = match source {
2307+
None => {
2308+
debug_assert!(false, "Outbound HTLCs should have a source");
2309+
true
2310+
},
2311+
Some(HTLCSource::PreviousHopData(_)) => false,
2312+
Some(HTLCSource::OutboundRoute { .. }) => true,
2313+
};
22902314
res.push(Balance::MaybeTimeoutClaimableHTLC {
22912315
amount_satoshis: htlc.amount_msat / 1000,
22922316
claimable_height: htlc.cltv_expiry,
22932317
payment_hash: htlc.payment_hash,
2318+
outbound_payment,
22942319
});
22952320
} else if us.payment_preimages.get(&htlc.payment_hash).is_some() {
22962321
claimable_inbound_htlc_value_sat += htlc.amount_msat / 1000;

lightning/src/ln/monitor_tests.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,13 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
402402
amount_satoshis: 3_000,
403403
claimable_height: htlc_cltv_timeout,
404404
payment_hash,
405+
outbound_payment: true,
405406
};
406407
let sent_htlc_timeout_balance = Balance::MaybeTimeoutClaimableHTLC {
407408
amount_satoshis: 4_000,
408409
claimable_height: htlc_cltv_timeout,
409410
payment_hash: timeout_payment_hash,
411+
outbound_payment: true,
410412
};
411413
let received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
412414
amount_satoshis: 3_000,
@@ -803,11 +805,13 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) {
803805
amount_satoshis: 10_000,
804806
claimable_height: htlc_cltv_timeout,
805807
payment_hash,
808+
outbound_payment: true,
806809
};
807810
let htlc_balance_unknown_preimage = Balance::MaybeTimeoutClaimableHTLC {
808811
amount_satoshis: 20_000,
809812
claimable_height: htlc_cltv_timeout,
810813
payment_hash: payment_hash_2,
814+
outbound_payment: true,
811815
};
812816

813817
let commitment_tx_fee = chan_feerate *
@@ -950,6 +954,7 @@ fn test_no_preimage_inbound_htlc_balances() {
950954
amount_satoshis: 10_000,
951955
claimable_height: htlc_cltv_timeout,
952956
payment_hash: to_b_failed_payment_hash,
957+
outbound_payment: true,
953958
};
954959
let a_received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
955960
amount_satoshis: 20_000,
@@ -965,6 +970,7 @@ fn test_no_preimage_inbound_htlc_balances() {
965970
amount_satoshis: 20_000,
966971
claimable_height: htlc_cltv_timeout,
967972
payment_hash: to_a_failed_payment_hash,
973+
outbound_payment: true,
968974
};
969975

970976
// Both A and B will have an HTLC that's claimable on timeout and one that's claimable if they
@@ -1261,14 +1267,17 @@ fn do_test_revoked_counterparty_commitment_balances(anchors: bool, confirm_htlc_
12611267
amount_satoshis: 2_000,
12621268
claimable_height: missing_htlc_cltv_timeout,
12631269
payment_hash: missing_htlc_payment_hash,
1270+
outbound_payment: true,
12641271
}, Balance::MaybeTimeoutClaimableHTLC {
12651272
amount_satoshis: 4_000,
12661273
claimable_height: htlc_cltv_timeout,
12671274
payment_hash: timeout_payment_hash,
1275+
outbound_payment: true,
12681276
}, Balance::MaybeTimeoutClaimableHTLC {
12691277
amount_satoshis: 5_000,
12701278
claimable_height: live_htlc_cltv_timeout,
12711279
payment_hash: live_payment_hash,
1280+
outbound_payment: true,
12721281
}]),
12731282
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
12741283

@@ -1811,10 +1820,12 @@ fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) {
18111820
amount_satoshis: 4_000,
18121821
claimable_height: htlc_cltv_timeout,
18131822
payment_hash: revoked_payment_hash,
1823+
outbound_payment: true,
18141824
}, Balance::MaybeTimeoutClaimableHTLC {
18151825
amount_satoshis: 3_000,
18161826
claimable_height: htlc_cltv_timeout,
18171827
payment_hash: claimed_payment_hash,
1828+
outbound_payment: true,
18181829
}]),
18191830
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
18201831

0 commit comments

Comments
 (0)