Skip to content

Commit 6a34baa

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 bc1c026 commit 6a34baa

File tree

2 files changed

+58
-16
lines changed

2 files changed

+58
-16
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 47 additions & 16 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
@@ -693,9 +697,15 @@ pub enum Balance {
693697
}
694698

695699
impl Balance {
696-
/// The amount claimable, in satoshis. This excludes balances that we are unsure if we are able
697-
/// to claim, this is because we are waiting for a preimage or for a timeout to expire. For more
698-
/// information on these balances see [`Balance::MaybeTimeoutClaimableHTLC`] and
700+
/// The amount claimable, in satoshis.
701+
///
702+
/// For outbound payments, this excludes the balance from the possible HTLC timeout.
703+
///
704+
/// For forwarded payments, this includes the balance from the possible HTLC timeout as
705+
/// (to be conservative) that balance does not include routing fees we'd earn if we'd claim
706+
/// the balance from a preimage in a successful forward.
707+
///
708+
/// For more information on these balances see [`Balance::MaybeTimeoutClaimableHTLC`] and
699709
/// [`Balance::MaybePreimageClaimableHTLC`].
700710
///
701711
/// On-chain fees required to claim the balance are not included in this amount.
@@ -706,9 +716,9 @@ impl Balance {
706716
Balance::ContentiousClaimable { amount_satoshis, .. }|
707717
Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
708718
=> *amount_satoshis,
709-
Balance::MaybeTimeoutClaimableHTLC { .. }|
710-
Balance::MaybePreimageClaimableHTLC { .. }
711-
=> 0,
719+
Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. }
720+
=> if *outbound_payment { 0 } else { *amount_satoshis },
721+
Balance::MaybePreimageClaimableHTLC { .. } => 0,
712722
}
713723
}
714724
}
@@ -1960,9 +1970,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19601970
impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
19611971
/// Helper for get_claimable_balances which does the work for an individual HTLC, generating up
19621972
/// 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> {
1973+
fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, source: Option<&HTLCSource>,
1974+
holder_commitment: bool, counterparty_revoked_commitment: bool,
1975+
confirmed_txid: Option<Txid>
1976+
) -> Option<Balance> {
19661977
let htlc_commitment_tx_output_idx =
19671978
if let Some(v) = htlc.transaction_output_index { v } else { return None; };
19681979

@@ -2099,10 +2110,19 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
20992110
confirmation_height: conf_thresh,
21002111
});
21012112
} else {
2113+
let outbound_payment = match source {
2114+
None => {
2115+
debug_assert!(false, "Outbound HTLCs should have a source");
2116+
true
2117+
},
2118+
Some(&HTLCSource::PreviousHopData(_)) => false,
2119+
Some(&HTLCSource::OutboundRoute { .. }) => true,
2120+
};
21022121
return Some(Balance::MaybeTimeoutClaimableHTLC {
21032122
amount_satoshis: htlc.amount_msat / 1000,
21042123
claimable_height: htlc.cltv_expiry,
21052124
payment_hash: htlc.payment_hash,
2125+
outbound_payment,
21062126
});
21072127
}
21082128
} else if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
@@ -2175,10 +2195,12 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
21752195

21762196
macro_rules! walk_htlcs {
21772197
($holder_commitment: expr, $counterparty_revoked_commitment: expr, $htlc_iter: expr) => {
2178-
for htlc in $htlc_iter {
2198+
for (htlc, source) in $htlc_iter {
21792199
if htlc.transaction_output_index.is_some() {
21802200

2181-
if let Some(bal) = us.get_htlc_balance(htlc, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid) {
2201+
if let Some(bal) = us.get_htlc_balance(
2202+
htlc, source, $holder_commitment, $counterparty_revoked_commitment, confirmed_txid
2203+
) {
21822204
res.push(bal);
21832205
}
21842206
}
@@ -2209,9 +2231,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22092231
}
22102232
}
22112233
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));
2234+
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
22132235
} else {
2214-
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, _)| a));
2236+
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, b)| (a, b.as_ref().map(|b| &**b))));
22152237
// The counterparty broadcasted a revoked state!
22162238
// Look for any StaticOutputs first, generating claimable balances for those.
22172239
// If any match the confirmed counterparty revoked to_self output, skip
@@ -2251,7 +2273,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22512273
}
22522274
found_commitment_tx = true;
22532275
} 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));
2276+
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())));
22552277
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
22562278
res.push(Balance::ClaimableAwaitingConfirmations {
22572279
amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
@@ -2261,7 +2283,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22612283
found_commitment_tx = true;
22622284
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
22632285
if txid == prev_commitment.txid {
2264-
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
2286+
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())));
22652287
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
22662288
res.push(Balance::ClaimableAwaitingConfirmations {
22672289
amount_satoshis: prev_commitment.to_self_value_sat,
@@ -2284,13 +2306,22 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22842306
}
22852307
} else {
22862308
let mut claimable_inbound_htlc_value_sat = 0;
2287-
for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() {
2309+
for (htlc, _, source) in us.current_holder_commitment_tx.htlc_outputs.iter() {
22882310
if htlc.transaction_output_index.is_none() { continue; }
22892311
if htlc.offered {
2312+
let outbound_payment = match source {
2313+
None => {
2314+
debug_assert!(false, "Outbound HTLCs should have a source");
2315+
true
2316+
},
2317+
Some(HTLCSource::PreviousHopData(_)) => false,
2318+
Some(HTLCSource::OutboundRoute { .. }) => true,
2319+
};
22902320
res.push(Balance::MaybeTimeoutClaimableHTLC {
22912321
amount_satoshis: htlc.amount_msat / 1000,
22922322
claimable_height: htlc.cltv_expiry,
22932323
payment_hash: htlc.payment_hash,
2324+
outbound_payment,
22942325
});
22952326
} else if us.payment_preimages.get(&htlc.payment_hash).is_some() {
22962327
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)