Skip to content

Commit 67e788e

Browse files
committed
Include pending HTLCs in ChannelDetails
This exposes details around pending HTLCs in ChannelDetails. The state of the HTLC in the state machine is also included, so it can be determined which protocol message the HTLC is waiting for to advance.
1 parent e64342a commit 67e788e

File tree

4 files changed

+337
-0
lines changed

4 files changed

+337
-0
lines changed

fuzz/src/router.rs

+2
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
242242
config: None,
243243
feerate_sat_per_1000_weight: None,
244244
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
245+
pending_inbound_htlcs: Vec::new(),
246+
pending_outbound_htlcs: Vec::new(),
245247
});
246248
}
247249
Some(&$first_hops_vec[..])

lightning/src/ln/channel.rs

+314
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,72 @@ enum InboundHTLCState {
158158
LocalRemoved(InboundHTLCRemovalReason),
159159
}
160160

161+
/// Exposes the state of pending inbound HTLCs.
162+
///
163+
/// At a high level, an HTLC being forwarded from one Lightning node to another Lightning node goes
164+
/// through the following states in the state machine:
165+
/// - Announced for addition by the originating node through the update_add_htlc message.
166+
/// - Added to the commitment transaction of the receiving node and originating node in turn
167+
/// through the exchange of commitment_signed and revoke_and_ack messages.
168+
/// - Announced for resolution (fulfillment or failure) by the receiving node through either one of
169+
/// the update_fulfill_htlc, update_fail_htlc, and update_fail_malformed_htlc messages.
170+
/// - Removed from the commitment transaction of the originating node and receiving node in turn
171+
/// through the exchange of commitment_signed and revoke_and_ack messages.
172+
///
173+
/// This can be used to inspect what next message an HTLC is waiting for to advance its state.
174+
#[derive(Clone, Debug, PartialEq)]
175+
pub enum InboundHTLCStateDetails {
176+
/// We have added this HTLC in our commitment transaction by receiving commitment_signed and
177+
/// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote
178+
/// before this HTLC is included on the remote commitment transaction.
179+
AwaitingRemoteRevokeToAdd,
180+
/// This HTLC has been included in the commitment_signed and revoke_and_ack messages on both sides
181+
/// and is included in both commitment transactions.
182+
///
183+
/// This HTLC is now safe to either forward or be claimed as a payment by us. The HTLC will
184+
/// remain in this state until the forwarded upstream HTLC has been resolved and we resolve this
185+
/// HTLC correspondingly, or until we claim it as a payment. If it is part of a multipart
186+
/// payment, it will only be claimed together with other required parts.
187+
Committed,
188+
/// We have received the preimage for this HTLC and it is being removed by fulfilling it with
189+
/// update_fulfill_htlc. This HTLC is still on both commitment transactions, but we are awaiting
190+
/// the appropriate revoke_and_ack's from the remote before this HTLC is removed from the remote
191+
/// commitment transaction after update_fulfill_htlc.
192+
AwaitingRemoteRevokeToRemoveFulfill,
193+
/// The HTLC is being removed by failing it with update_fail_htlc or update_fail_malformed_htlc.
194+
/// This HTLC is still on both commitment transactions, but we are awaiting the appropriate
195+
/// revoke_and_ack's from the remote before this HTLC is removed from the remote commitment
196+
/// transaction.
197+
AwaitingRemoteRevokeToRemoveFail,
198+
}
199+
200+
impl From<&InboundHTLCState> for Option<InboundHTLCStateDetails> {
201+
fn from(state: &InboundHTLCState) -> Option<InboundHTLCStateDetails> {
202+
match state {
203+
InboundHTLCState::RemoteAnnounced(_) => None,
204+
InboundHTLCState::AwaitingRemoteRevokeToAnnounce(_) =>
205+
Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToAdd),
206+
InboundHTLCState::AwaitingAnnouncedRemoteRevoke(_) =>
207+
Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToAdd),
208+
InboundHTLCState::Committed =>
209+
Some(InboundHTLCStateDetails::Committed),
210+
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(_)) =>
211+
Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail),
212+
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed(_)) =>
213+
Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail),
214+
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) =>
215+
Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill),
216+
}
217+
}
218+
}
219+
220+
impl_writeable_tlv_based_enum_upgradable!(InboundHTLCStateDetails,
221+
(0, AwaitingRemoteRevokeToAdd) => {},
222+
(2, Committed) => {},
223+
(4, AwaitingRemoteRevokeToRemoveFulfill) => {},
224+
(6, AwaitingRemoteRevokeToRemoveFail) => {};
225+
);
226+
161227
struct InboundHTLCOutput {
162228
htlc_id: u64,
163229
amount_msat: u64,
@@ -166,6 +232,48 @@ struct InboundHTLCOutput {
166232
state: InboundHTLCState,
167233
}
168234

235+
/// Exposes details around pending inbound HTLCs.
236+
#[derive(Clone, Debug, PartialEq)]
237+
pub struct InboundHTLCDetails {
238+
/// The HTLC ID.
239+
/// The IDs are incremented by 1 starting from 0 for each offered HTLC.
240+
/// They are unique per channel and inbound/outbound direction, unless an HTLC was only announced
241+
/// and not part of any commitment transaction.
242+
pub htlc_id: u64,
243+
/// The amount in msat.
244+
pub amount_msat: u64,
245+
/// The block height at which this HTLC expires.
246+
pub cltv_expiry: u32,
247+
/// The payment hash.
248+
pub payment_hash: PaymentHash,
249+
/// The state of the HTLC in the state machine.
250+
/// Determines on which commitment transactions the HTLC is included and what message the HTLC is
251+
/// waiting for to advance to the next state.
252+
/// See [`InboundHTLCStateDetails`] for information on the specific states.
253+
pub state: Option<InboundHTLCStateDetails>,
254+
/// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
255+
/// from the local commitment transaction and added to the commitment transaction fee.
256+
/// For non-anchor channels, this takes into account the cost of the second-stage HTLC
257+
/// transactions as well.
258+
///
259+
/// When the local commitment transaction is broadcasted as part of a unilateral closure,
260+
/// the value of this HTLC will therefore not be claimable but instead burned as a transaction
261+
/// fee.
262+
///
263+
/// Note that dust limits are specific to each party. An HTLC can be dust for the local
264+
/// commitment transaction but not for the counterparty's commitment transaction and vice versa.
265+
pub is_dust: bool,
266+
}
267+
268+
impl_writeable_tlv_based!(InboundHTLCDetails, {
269+
(0, htlc_id, required),
270+
(2, amount_msat, required),
271+
(4, cltv_expiry, required),
272+
(6, payment_hash, required),
273+
(7, state, upgradable_option),
274+
(8, is_dust, required),
275+
});
276+
169277
#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
170278
enum OutboundHTLCState {
171279
/// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we
@@ -199,6 +307,72 @@ enum OutboundHTLCState {
199307
AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome),
200308
}
201309

310+
/// Exposes the state of pending outbound HTLCs.
311+
///
312+
/// At a high level, an HTLC being forwarded from one Lightning node to another Lightning node goes
313+
/// through the following states in the state machine:
314+
/// - Announced for addition by the originating node through the update_add_htlc message.
315+
/// - Added to the commitment transaction of the receiving node and originating node in turn
316+
/// through the exchange of commitment_signed and revoke_and_ack messages.
317+
/// - Announced for resolution (fulfillment or failure) by the receiving node through either one of
318+
/// the update_fulfill_htlc, update_fail_htlc, and update_fail_malformed_htlc messages.
319+
/// - Removed from the commitment transaction of the originating node and receiving node in turn
320+
/// through the exchange of commitment_signed and revoke_and_ack messages.
321+
///
322+
/// This can be used to inspect what next message an HTLC is waiting for to advance its state.
323+
#[derive(Clone, Debug, PartialEq)]
324+
pub enum OutboundHTLCStateDetails {
325+
/// We are awaiting the appropriate revoke_and_ack's from the remote before the HTLC is added
326+
/// on the remote's commitment transaction after update_add_htlc.
327+
AwaitingRemoteRevokeToAdd,
328+
/// The HTLC has been added to the remote's commitment transaction by sending commitment_signed
329+
/// and receiving revoke_and_ack in return.
330+
///
331+
/// The HTLC will remain in this state until the remote node resolves the HTLC, or until we
332+
/// unilaterally close the channel due to a timeout with an uncooperative remote node.
333+
Committed,
334+
/// The HTLC has been fulfilled successfully by the remote with a preimage in update_fulfill_htlc,
335+
/// and we removed the HTLC from our commitment transaction by receiving commitment_signed and
336+
/// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote
337+
/// for the removal from its commitment transaction.
338+
AwaitingRemoteRevokeToRemoveSuccess,
339+
/// The HTLC has been failed by the remote with update_fail_htlc or update_fail_malformed_htlc,
340+
/// and we removed the HTLC from our commitment transaction by receiving commitment_signed and
341+
/// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote
342+
/// for the removal from its commitment transaction.
343+
AwaitingRemoteRevokeToRemoveFailure,
344+
}
345+
346+
impl From<&OutboundHTLCState> for OutboundHTLCStateDetails {
347+
fn from(state: &OutboundHTLCState) -> OutboundHTLCStateDetails {
348+
match state {
349+
OutboundHTLCState::LocalAnnounced(_) =>
350+
OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd,
351+
OutboundHTLCState::Committed =>
352+
OutboundHTLCStateDetails::Committed,
353+
// RemoteRemoved states are ignored as the state is transient and the remote has not committed to
354+
// the state yet.
355+
OutboundHTLCState::RemoteRemoved(_) =>
356+
OutboundHTLCStateDetails::Committed,
357+
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) =>
358+
OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
359+
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Failure(_)) =>
360+
OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
361+
OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) =>
362+
OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
363+
OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(_)) =>
364+
OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
365+
}
366+
}
367+
}
368+
369+
impl_writeable_tlv_based_enum_upgradable!(OutboundHTLCStateDetails,
370+
(0, AwaitingRemoteRevokeToAdd) => {},
371+
(2, Committed) => {},
372+
(4, AwaitingRemoteRevokeToRemoveSuccess) => {},
373+
(6, AwaitingRemoteRevokeToRemoveFailure) => {};
374+
);
375+
202376
#[derive(Clone)]
203377
#[cfg_attr(test, derive(Debug, PartialEq))]
204378
enum OutboundHTLCOutcome {
@@ -237,6 +411,53 @@ struct OutboundHTLCOutput {
237411
skimmed_fee_msat: Option<u64>,
238412
}
239413

414+
/// Exposes details around pending outbound HTLCs.
415+
#[derive(Clone, Debug, PartialEq)]
416+
pub struct OutboundHTLCDetails {
417+
/// The HTLC ID.
418+
/// The IDs are incremented by 1 starting from 0 for each offered HTLC.
419+
/// They are unique per channel and inbound/outbound direction, unless an HTLC was only announced
420+
/// and not part of any commitment transaction.
421+
///
422+
/// Not present when we are awaiting a remote revocation and the HTLC is not added yet.
423+
pub htlc_id: Option<u64>,
424+
/// The amount in msat.
425+
pub amount_msat: u64,
426+
/// The block height at which this HTLC expires.
427+
pub cltv_expiry: u32,
428+
/// The payment hash.
429+
pub payment_hash: PaymentHash,
430+
/// The state of the HTLC in the state machine.
431+
/// Determines on which commitment transactions the HTLC is included and what message the HTLC is
432+
/// waiting for to advance to the next state.
433+
/// See [`OutboundHTLCStateDetails`] for information on the specific states.
434+
pub state: Option<OutboundHTLCStateDetails>,
435+
/// The extra fee being skimmed off the top of this HTLC.
436+
pub skimmed_fee_msat: Option<u64>,
437+
/// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
438+
/// from the local commitment transaction and added to the commitment transaction fee.
439+
/// For non-anchor channels, this takes into account the cost of the second-stage HTLC
440+
/// transactions as well.
441+
///
442+
/// When the local commitment transaction is broadcasted as part of a unilateral closure,
443+
/// the value of this HTLC will therefore not be claimable but instead burned as a transaction
444+
/// fee.
445+
///
446+
/// Note that dust limits are specific to each party. An HTLC can be dust for the local
447+
/// commitment transaction but not for the counterparty's commitment transaction and vice versa.
448+
pub is_dust: bool,
449+
}
450+
451+
impl_writeable_tlv_based!(OutboundHTLCDetails, {
452+
(0, htlc_id, required),
453+
(2, amount_msat, required),
454+
(4, cltv_expiry, required),
455+
(6, payment_hash, required),
456+
(7, state, upgradable_option),
457+
(8, skimmed_fee_msat, required),
458+
(10, is_dust, required),
459+
});
460+
240461
/// See AwaitingRemoteRevoke ChannelState for more info
241462
#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
242463
enum HTLCUpdateAwaitingACK {
@@ -1994,6 +2215,99 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
19942215
stats
19952216
}
19962217

2218+
/// Returns information on all pending inbound HTLCs.
2219+
pub fn get_pending_inbound_htlc_details(&self) -> Vec<InboundHTLCDetails> {
2220+
let mut holding_cell_states = new_hash_map();
2221+
for holding_cell_update in self.holding_cell_htlc_updates.iter() {
2222+
match holding_cell_update {
2223+
HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => {
2224+
holding_cell_states.insert(
2225+
htlc_id,
2226+
InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill,
2227+
);
2228+
},
2229+
HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
2230+
holding_cell_states.insert(
2231+
htlc_id,
2232+
InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
2233+
);
2234+
},
2235+
HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } => {
2236+
holding_cell_states.insert(
2237+
htlc_id,
2238+
InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
2239+
);
2240+
},
2241+
// Outbound HTLC.
2242+
HTLCUpdateAwaitingACK::AddHTLC { .. } => {},
2243+
}
2244+
}
2245+
let mut inbound_details = Vec::new();
2246+
let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
2247+
0
2248+
} else {
2249+
let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
2250+
dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
2251+
};
2252+
let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
2253+
for htlc in self.pending_inbound_htlcs.iter() {
2254+
if let Some(state_details) = (&htlc.state).into() {
2255+
inbound_details.push(InboundHTLCDetails{
2256+
htlc_id: htlc.htlc_id,
2257+
amount_msat: htlc.amount_msat,
2258+
cltv_expiry: htlc.cltv_expiry,
2259+
payment_hash: htlc.payment_hash,
2260+
state: Some(holding_cell_states.remove(&htlc.htlc_id).unwrap_or(state_details)),
2261+
is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
2262+
});
2263+
}
2264+
}
2265+
inbound_details
2266+
}
2267+
2268+
/// Returns information on all pending outbound HTLCs.
2269+
pub fn get_pending_outbound_htlc_details(&self) -> Vec<OutboundHTLCDetails> {
2270+
let mut outbound_details = Vec::new();
2271+
let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
2272+
0
2273+
} else {
2274+
let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
2275+
dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
2276+
};
2277+
let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
2278+
for htlc in self.pending_outbound_htlcs.iter() {
2279+
outbound_details.push(OutboundHTLCDetails{
2280+
htlc_id: Some(htlc.htlc_id),
2281+
amount_msat: htlc.amount_msat,
2282+
cltv_expiry: htlc.cltv_expiry,
2283+
payment_hash: htlc.payment_hash,
2284+
skimmed_fee_msat: htlc.skimmed_fee_msat,
2285+
state: Some((&htlc.state).into()),
2286+
is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
2287+
});
2288+
}
2289+
for holding_cell_update in self.holding_cell_htlc_updates.iter() {
2290+
if let HTLCUpdateAwaitingACK::AddHTLC {
2291+
amount_msat,
2292+
cltv_expiry,
2293+
payment_hash,
2294+
skimmed_fee_msat,
2295+
..
2296+
} = *holding_cell_update {
2297+
outbound_details.push(OutboundHTLCDetails{
2298+
htlc_id: None,
2299+
amount_msat: amount_msat,
2300+
cltv_expiry: cltv_expiry,
2301+
payment_hash: payment_hash,
2302+
skimmed_fee_msat: skimmed_fee_msat,
2303+
state: Some(OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd),
2304+
is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
2305+
});
2306+
}
2307+
}
2308+
outbound_details
2309+
}
2310+
19972311
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
19982312
/// Doesn't bother handling the
19992313
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC

0 commit comments

Comments
 (0)