@@ -158,6 +158,102 @@ 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+ /// The remote node announced the HTLC with update_add_htlc but the HTLC is not added to any
177+ /// commitment transactions yet.
178+ ///
179+ /// We intend to forward the HTLC as it is correctly formed and is forwardable to the next hop.
180+ RemoteAnnouncedForward,
181+ /// The remote node announced the HTLC with update_add_htlc but the HTLC is not added to any
182+ /// commitment transactions yet.
183+ ///
184+ /// We intend to fail the HTLC as it is malformed or we are unable to forward to the next hop,
185+ /// for example if the peer is disconnected or not enough capacity is available.
186+ RemoteAnnouncedFail,
187+ /// We have added this HTLC in our commitment transaction by receiving commitment_signed and
188+ /// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote
189+ /// before this HTLC is included on the remote commitment transaction.
190+ ///
191+ /// We intend to forward the HTLC as it is correctly formed and is forwardable to the next hop.
192+ AwaitingRemoteRevokeToAddForward,
193+ /// We have added this HTLC in our commitment transaction by receiving commitment_signed and
194+ /// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote
195+ /// before this HTLC is included on the remote commitment transaction.
196+ ///
197+ /// We intend to fail the HTLC as it is malformed or we are unable to forward to the next hop,
198+ /// for example if the peer is disconnected or not enough capacity is available.
199+ AwaitingRemoteRevokeToAddFail,
200+ /// This HTLC has been included in the commitment_signed and revoke_and_ack messages on both sides
201+ /// and is included in both commitment transactions.
202+ ///
203+ /// This HTLC is now safe to either forward or be claimed as a payment by us. The HTLC will
204+ /// remain in this state until the forwarded upstream HTLC has been resolved and we resolve this
205+ /// HTLC correspondingly, or until we claim it as a payment. If it is part of a multipart
206+ /// payment, it will only be claimed together with other required parts.
207+ Committed,
208+ /// We have received the preimage for this HTLC and it is being removed by fulfilling it with
209+ /// update_fulfill_htlc. This HTLC is still on both commitment transactions, but we are awaiting
210+ /// the appropriate revoke_and_ack's from the remote before this HTLC is removed from the remote
211+ /// commitment transaction after update_fulfill_htlc.
212+ AwaitingRemoteRevokeToRemoveFulfill,
213+ /// The HTLC is being removed by failing it with update_fail_htlc or update_fail_malformed_htlc.
214+ /// This HTLC is still on both commitment transactions, but we are awaiting the appropriate
215+ /// revoke_and_ack's from the remote before this HTLC is removed from the remote commitment
216+ /// transaction.
217+ AwaitingRemoteRevokeToRemoveFail,
218+ }
219+
220+ impl From<&InboundHTLCState> for InboundHTLCStateDetails {
221+ fn from(state: &InboundHTLCState) -> InboundHTLCStateDetails {
222+ match state {
223+ InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(_)) =>
224+ InboundHTLCStateDetails::RemoteAnnouncedForward,
225+ InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Fail(_)) =>
226+ InboundHTLCStateDetails::RemoteAnnouncedFail,
227+ InboundHTLCState::AwaitingRemoteRevokeToAnnounce(PendingHTLCStatus::Forward(_)) =>
228+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddForward,
229+ InboundHTLCState::AwaitingRemoteRevokeToAnnounce(PendingHTLCStatus::Fail(_)) =>
230+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddFail,
231+ InboundHTLCState::AwaitingAnnouncedRemoteRevoke(PendingHTLCStatus::Forward(_)) =>
232+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddForward,
233+ InboundHTLCState::AwaitingAnnouncedRemoteRevoke(PendingHTLCStatus::Fail(_)) =>
234+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddFail,
235+ InboundHTLCState::Committed =>
236+ InboundHTLCStateDetails::Committed,
237+ InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(_)) =>
238+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
239+ InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed(_)) =>
240+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
241+ InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) =>
242+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill,
243+ }
244+ }
245+ }
246+
247+ impl_writeable_tlv_based_enum!(InboundHTLCStateDetails,
248+ (0, RemoteAnnouncedForward) => {},
249+ (2, RemoteAnnouncedFail) => {},
250+ (4, AwaitingRemoteRevokeToAddForward) => {},
251+ (6, AwaitingRemoteRevokeToAddFail) => {},
252+ (8, Committed) => {},
253+ (10, AwaitingRemoteRevokeToRemoveFulfill) => {},
254+ (12, AwaitingRemoteRevokeToRemoveFail) => {};
255+ );
256+
161257struct InboundHTLCOutput {
162258 htlc_id: u64,
163259 amount_msat: u64,
@@ -166,6 +262,44 @@ struct InboundHTLCOutput {
166262 state: InboundHTLCState,
167263}
168264
265+ /// Exposes details around pending inbound HTLCs.
266+ #[derive(Clone, Debug, PartialEq)]
267+ pub struct InboundHTLCDetails {
268+ /// The HTLC ID.
269+ /// The IDs are incremented by 1 starting from 0 for each offered HTLC.
270+ /// They are unique per channel and inbound/outbound direction, unless an HTLC was only announced
271+ /// and not part of any commitment transaction.
272+ pub htlc_id: u64,
273+ /// The amount in msat.
274+ pub amount_msat: u64,
275+ /// The block height at which this HTLC expires.
276+ pub cltv_expiry: u32,
277+ /// The payment hash.
278+ pub payment_hash: PaymentHash,
279+ /// The state of the HTLC in the state machine.
280+ /// Determines on which commitment transactions the HTLC is included and what message the HTLC is
281+ /// waiting for to advance to the next state.
282+ /// See [InboundHTLCStateDetails] for information on the specific states.
283+ pub state: InboundHTLCStateDetails,
284+ /// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
285+ /// from the local commitment transaction and added to the commitment transaction fee.
286+ /// This takes into account the second-stage HTLC transactions as well.
287+ ///
288+ /// When the local commitment transaction is broadcasted as part of a unilateral closure,
289+ /// the value of this HTLC will therefore not be claimable but instead burned as a transaction
290+ /// fee.
291+ pub is_dust: bool,
292+ }
293+
294+ impl_writeable_tlv_based!(InboundHTLCDetails, {
295+ (0, htlc_id, required),
296+ (2, amount_msat, required),
297+ (4, cltv_expiry, required),
298+ (6, payment_hash, required),
299+ (8, state, required),
300+ (10, is_dust, required),
301+ });
302+
169303#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
170304enum OutboundHTLCState {
171305 /// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we
@@ -199,6 +333,72 @@ enum OutboundHTLCState {
199333 AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome),
200334}
201335
336+ /// Exposes the state of pending outbound HTLCs.
337+ ///
338+ /// At a high level, an HTLC being forwarded from one Lightning node to another Lightning node goes
339+ /// through the following states in the state machine:
340+ /// - Announced for addition by the originating node through the update_add_htlc message.
341+ /// - Added to the commitment transaction of the receiving node and originating node in turn
342+ /// through the exchange of commitment_signed and revoke_and_ack messages.
343+ /// - Announced for resolution (fulfillment or failure) by the receiving node through either one of
344+ /// the update_fulfill_htlc, update_fail_htlc, and update_fail_malformed_htlc messages.
345+ /// - Removed from the commitment transaction of the originating node and receiving node in turn
346+ /// through the exchange of commitment_signed and revoke_and_ack messages.
347+ ///
348+ /// This can be used to inspect what next message an HTLC is waiting for to advance its state.
349+ #[derive(Clone, Debug, PartialEq)]
350+ pub enum OutboundHTLCStateDetails {
351+ /// We are awaiting the appropriate revoke_and_ack's from the remote before the HTLC is be added
352+ /// on the remote's commitment transaction after update_add_htlc.
353+ AwaitingRemoteRevokeToAdd,
354+ /// The HTLC is included on the remote's commitment transaction through a commitment_signed and
355+ /// revoke_and_ack exchange.
356+ ///
357+ /// The HTLC will remain in this state until the remote node resolves the HTLC, or until we
358+ /// unilaterally close the channel due to a timeout with an uncooperative remote node.
359+ Committed,
360+ /// The HTLC has been fulfilled succesfully by the remote with a preimage in update_fulfill_htlc,
361+ /// and we removed the HTLC from our commitment transaction through a commitment_signed and
362+ /// revoke_and_ack exchange. We are awaiting the appropriate revoke_and_ack's from the remote for
363+ /// the removal from its commitment transaction.
364+ AwaitingRemoteRevokeToRemoveSuccess,
365+ /// The HTLC has been failed by the remote with update_fail_htlc or update_fail_malformed_htlc,
366+ /// and we removed the HTLC from our commitment transaction through a commitment_signed and
367+ /// revoke_and_ack exchange. We are awaiting the appropriate revoke_and_ack's from the remote for
368+ /// the removal from its commitment transaction.
369+ AwaitingRemoteRevokeToRemoveFailure,
370+ }
371+
372+ impl From<&OutboundHTLCState> for OutboundHTLCStateDetails {
373+ fn from(state: &OutboundHTLCState) -> OutboundHTLCStateDetails {
374+ match state {
375+ OutboundHTLCState::LocalAnnounced(_) =>
376+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd,
377+ OutboundHTLCState::Committed =>
378+ OutboundHTLCStateDetails::Committed,
379+ // RemoteRemoved states are ignored as the state is transient and the remote has not committed to
380+ // the state yet.
381+ OutboundHTLCState::RemoteRemoved(_) =>
382+ OutboundHTLCStateDetails::Committed,
383+ OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) =>
384+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
385+ OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Failure(_)) =>
386+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
387+ OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) =>
388+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
389+ OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(_)) =>
390+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
391+ }
392+ }
393+ }
394+
395+ impl_writeable_tlv_based_enum!(OutboundHTLCStateDetails,
396+ (0, AwaitingRemoteRevokeToAdd) => {},
397+ (2, Committed) => {},
398+ (4, AwaitingRemoteRevokeToRemoveSuccess) => {},
399+ (6, AwaitingRemoteRevokeToRemoveFailure) => {};
400+ );
401+
202402#[derive(Clone)]
203403#[cfg_attr(test, derive(Debug, PartialEq))]
204404enum OutboundHTLCOutcome {
@@ -237,6 +437,49 @@ struct OutboundHTLCOutput {
237437 skimmed_fee_msat: Option<u64>,
238438}
239439
440+ /// Exposes details around pending outbound HTLCs.
441+ #[derive(Clone, Debug, PartialEq)]
442+ pub struct OutboundHTLCDetails {
443+ /// The HTLC ID.
444+ /// The IDs are incremented by 1 starting from 0 for each offered HTLC.
445+ /// They are unique per channel and inbound/outbound direction, unless an HTLC was only announced
446+ /// and not part of any commitment transaction.
447+ ///
448+ /// Not present when we are awaiting a remote revocation and the HTLC is not added yet.
449+ pub htlc_id: Option<u64>,
450+ /// The amount in msat.
451+ pub amount_msat: u64,
452+ /// The block height at which this HTLC expires.
453+ pub cltv_expiry: u32,
454+ /// The payment hash.
455+ pub payment_hash: PaymentHash,
456+ /// The state of the HTLC in the state machine.
457+ /// Determines on which commitment transactions the HTLC is included and what message the HTLC is
458+ /// waiting for to advance to the next state.
459+ /// See [OutboundHTLCStateDetails] for information on the specific states.
460+ pub state: OutboundHTLCStateDetails,
461+ /// The extra fee being skimmed off the top of this HTLC.
462+ pub skimmed_fee_msat: Option<u64>,
463+ /// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
464+ /// from the local commitment transaction and added to the commitment transaction fee.
465+ /// This takes into account the second-stage HTLC transactions as well.
466+ ///
467+ /// When the local commitment transaction is broadcasted as part of a unilateral closure,
468+ /// the value of this HTLC will therefore not be claimable but instead burned as a transaction
469+ /// fee.
470+ pub is_dust: bool,
471+ }
472+
473+ impl_writeable_tlv_based!(OutboundHTLCDetails, {
474+ (0, htlc_id, required),
475+ (2, amount_msat, required),
476+ (4, cltv_expiry, required),
477+ (6, payment_hash, required),
478+ (8, state, required),
479+ (10, skimmed_fee_msat, required),
480+ (12, is_dust, required),
481+ });
482+
240483/// See AwaitingRemoteRevoke ChannelState for more info
241484#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
242485enum HTLCUpdateAwaitingACK {
@@ -1966,6 +2209,96 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
19662209 stats
19672210 }
19682211
2212+ /// Returns information on all pending inbound HTLCs.
2213+ pub fn get_pending_inbound_htlc_details(&self) -> Vec<InboundHTLCDetails> {
2214+ let mut holding_cell_states = HashMap::new();
2215+ for holding_cell_update in self.holding_cell_htlc_updates.iter() {
2216+ match holding_cell_update {
2217+ HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => {
2218+ holding_cell_states.insert(
2219+ htlc_id,
2220+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill,
2221+ );
2222+ },
2223+ HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
2224+ holding_cell_states.insert(
2225+ htlc_id,
2226+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
2227+ );
2228+ },
2229+ HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } => {
2230+ holding_cell_states.insert(
2231+ htlc_id,
2232+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
2233+ );
2234+ },
2235+ _ => {},
2236+ }
2237+ }
2238+ let mut inbound_details = Vec::new();
2239+ let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
2240+ 0
2241+ } else {
2242+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
2243+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
2244+ };
2245+ let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
2246+ for htlc in self.pending_inbound_htlcs.iter() {
2247+ inbound_details.push(InboundHTLCDetails{
2248+ htlc_id: htlc.htlc_id,
2249+ amount_msat: htlc.amount_msat,
2250+ cltv_expiry: htlc.cltv_expiry,
2251+ payment_hash: htlc.payment_hash,
2252+ state: holding_cell_states.remove(&htlc.htlc_id).unwrap_or((&htlc.state).into()),
2253+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
2254+ });
2255+ }
2256+ inbound_details
2257+ }
2258+
2259+ /// Returns information on all pending outbound HTLCs.
2260+ pub fn get_pending_outbound_htlc_details(&self) -> Vec<OutboundHTLCDetails> {
2261+ let mut outbound_details = Vec::new();
2262+ let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
2263+ 0
2264+ } else {
2265+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
2266+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
2267+ };
2268+ let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
2269+ for htlc in self.pending_outbound_htlcs.iter() {
2270+ outbound_details.push(OutboundHTLCDetails{
2271+ htlc_id: Some(htlc.htlc_id),
2272+ amount_msat: htlc.amount_msat,
2273+ cltv_expiry: htlc.cltv_expiry,
2274+ payment_hash: htlc.payment_hash,
2275+ skimmed_fee_msat: htlc.skimmed_fee_msat,
2276+ state: (&htlc.state).into(),
2277+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
2278+ });
2279+ }
2280+ for holding_cell_update in self.holding_cell_htlc_updates.iter() {
2281+ if let HTLCUpdateAwaitingACK::AddHTLC {
2282+ amount_msat,
2283+ cltv_expiry,
2284+ payment_hash,
2285+ skimmed_fee_msat,
2286+ ..
2287+ } = *holding_cell_update {
2288+ outbound_details.push(OutboundHTLCDetails{
2289+ htlc_id: None,
2290+ amount_msat: amount_msat,
2291+ cltv_expiry: cltv_expiry,
2292+ payment_hash: payment_hash,
2293+ skimmed_fee_msat: skimmed_fee_msat,
2294+ state: OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd,
2295+ is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
2296+ });
2297+ }
2298+ }
2299+ outbound_details
2300+ }
2301+
19692302 /// Get the available balances, see [`AvailableBalances`]'s fields for more info.
19702303 /// Doesn't bother handling the
19712304 /// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
0 commit comments