Skip to content

Commit 4a7ba67

Browse files
authored
Merge pull request #2442 from wvanlint/list_pending_htlcs
Include pending HTLC's in ChannelDetails
2 parents a0917e5 + 67e788e commit 4a7ba67

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)