@@ -152,6 +152,72 @@ enum InboundHTLCState {
152
152
LocalRemoved(InboundHTLCRemovalReason),
153
153
}
154
154
155
+ /// Exposes the state of pending inbound HTLCs.
156
+ ///
157
+ /// Fail and fulfill suffixes indicate the resolution of the HTLC.
158
+ #[derive(Clone, Debug, PartialEq)]
159
+ pub enum InboundHTLCStateDetails {
160
+ /// The RemoteAnnounced states indicate that the HTLC is not any commitment transactions yet,
161
+ /// but the remote node sent update_add_htlc.
162
+ RemoteAnnouncedForward,
163
+ /// See above.
164
+ RemoteAnnouncedFail,
165
+ /// AwaitingRemoteRevoke states indicate that we have received commitment_signed with
166
+ /// this HTLC, returned revoke_and_ack, and this HTLC is included on the local commitment
167
+ /// transaction but not the remote commitment transaction.
168
+ /// We are awaiting the appropriate revoke_and_ack's from the remote before this HTLC is included
169
+ /// on the remote commitment transaction, possibly for a prior state first.
170
+ AwaitingRemoteRevokeToAddForward,
171
+ /// See above.
172
+ AwaitingRemoteRevokeToAddFail,
173
+ /// Committed indicates that this HTLC has been included in the commitment_signed and
174
+ /// revoke_and_ack flow on both sides and is included in both commitment transactions.
175
+ Committed,
176
+ /// AwaitingRemoteRevokeToRemove states indicate that this HTLC is still on both commitment
177
+ /// transactions, but we are awaiting the appropriate revoke_and_ack's to remove this HTLC from
178
+ /// the remote commitment transaction, possibly for a prior state first.
179
+ AwaitingRemoteRevokeToRemoveFulfill,
180
+ /// See above.
181
+ AwaitingRemoteRevokeToRemoveFail,
182
+ }
183
+
184
+ impl From<&InboundHTLCState> for InboundHTLCStateDetails {
185
+ fn from(state: &InboundHTLCState) -> InboundHTLCStateDetails {
186
+ match state {
187
+ InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(_)) =>
188
+ InboundHTLCStateDetails::RemoteAnnouncedForward,
189
+ InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Fail(_)) =>
190
+ InboundHTLCStateDetails::RemoteAnnouncedFail,
191
+ InboundHTLCState::AwaitingRemoteRevokeToAnnounce(PendingHTLCStatus::Forward(_)) =>
192
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddForward,
193
+ InboundHTLCState::AwaitingRemoteRevokeToAnnounce(PendingHTLCStatus::Fail(_)) =>
194
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddFail,
195
+ InboundHTLCState::AwaitingAnnouncedRemoteRevoke(PendingHTLCStatus::Forward(_)) =>
196
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddForward,
197
+ InboundHTLCState::AwaitingAnnouncedRemoteRevoke(PendingHTLCStatus::Fail(_)) =>
198
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToAddFail,
199
+ InboundHTLCState::Committed =>
200
+ InboundHTLCStateDetails::Committed,
201
+ InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(_)) =>
202
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
203
+ InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed(_)) =>
204
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
205
+ InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) =>
206
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill,
207
+ }
208
+ }
209
+ }
210
+
211
+ impl_writeable_tlv_based_enum!(InboundHTLCStateDetails,
212
+ (0, RemoteAnnouncedForward) => {},
213
+ (2, RemoteAnnouncedFail) => {},
214
+ (4, AwaitingRemoteRevokeToAddForward) => {},
215
+ (6, AwaitingRemoteRevokeToAddFail) => {},
216
+ (8, Committed) => {},
217
+ (10, AwaitingRemoteRevokeToRemoveFulfill) => {},
218
+ (12, AwaitingRemoteRevokeToRemoveFail) => {};
219
+ );
220
+
155
221
struct InboundHTLCOutput {
156
222
htlc_id: u64,
157
223
amount_msat: u64,
@@ -160,6 +226,35 @@ struct InboundHTLCOutput {
160
226
state: InboundHTLCState,
161
227
}
162
228
229
+ /// Exposes details around pending inbound HTLCs.
230
+ #[derive(Clone, Debug, PartialEq)]
231
+ pub struct InboundHTLCDetails {
232
+ /// The corresponding HTLC ID.
233
+ pub htlc_id: u64,
234
+ /// The amount in msat.
235
+ pub amount_msat: u64,
236
+ /// The CLTV expiry.
237
+ pub cltv_expiry: u32,
238
+ /// The payment hash.
239
+ pub payment_hash: PaymentHash,
240
+ /// The state of the HTLC in the update_*_htlc, commitment_signed, revoke_and_ack flow.
241
+ /// Informs on which commitment transactions the HTLC is included.
242
+ pub state: InboundHTLCStateDetails,
243
+ /// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
244
+ /// from the local commitment transaction and added to the commitment transaction fee.
245
+ /// This takes into account the second-stage HTLC transactions as well.
246
+ pub is_dust: bool,
247
+ }
248
+
249
+ impl_writeable_tlv_based!(InboundHTLCDetails, {
250
+ (0, htlc_id, required),
251
+ (2, amount_msat, required),
252
+ (4, cltv_expiry, required),
253
+ (6, payment_hash, required),
254
+ (8, state, required),
255
+ (10, is_dust, required),
256
+ });
257
+
163
258
enum OutboundHTLCState {
164
259
/// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we
165
260
/// created it we would have put it in the holding cell instead). When they next revoke_and_ack
@@ -192,6 +287,65 @@ enum OutboundHTLCState {
192
287
AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome),
193
288
}
194
289
290
+ /// Exposes the state of pending outbound HTLCs.
291
+ ///
292
+ /// Failure and success suffixes indicate the resolution of the HTLC.
293
+ #[derive(Clone, Debug, PartialEq)]
294
+ pub enum OutboundHTLCStateDetails {
295
+ /// THe AwaitingRemoteRevoke state indicates that this HTLC is not on any commitment transactions
296
+ /// yet, but we are awaiting the appropriate revoke_and_ack's from the remote before it will be
297
+ /// on the remote commitment transaction, possibly for a prior state first.
298
+ AwaitingRemoteRevokeToAdd,
299
+ /// The Committed state indicates that this HTLC is included on the remote's commitment
300
+ /// transaction. This includes the subsequent state where we include it in the local commitment
301
+ /// transaction, as:
302
+ /// * they've revoked, so worst case we can announce an old state and get our (option on)
303
+ /// money back (though we won't), and,
304
+ /// * we'll send them a revoke when they send a commitment_signed, and since only they're
305
+ /// allowed to remove it, the "can only be removed once committed on both sides" requirement
306
+ /// doesn't matter to us and it's up to them to enforce it, worst-case they jump ahead but
307
+ /// we'll never get out of sync).
308
+ Committed,
309
+ /// AwaitingRemoteRevokeToRemove states indicate that the remote removed this HTLC and sent a
310
+ /// commitment_signed and we've revoke_and_ack'ed it. It is removed from the local commitment
311
+ /// transaction and we're awaiting the appropriate revoke_and_ack's from the remote before it
312
+ /// will be removed from the remote's commitmen transaction as well, possibly for a prior state
313
+ /// first.
314
+ AwaitingRemoteRevokeToRemoveSuccess,
315
+ /// See above.
316
+ AwaitingRemoteRevokeToRemoveFailure,
317
+ }
318
+
319
+ impl From<&OutboundHTLCState> for OutboundHTLCStateDetails {
320
+ fn from(state: &OutboundHTLCState) -> OutboundHTLCStateDetails {
321
+ match state {
322
+ OutboundHTLCState::LocalAnnounced(_) =>
323
+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd,
324
+ OutboundHTLCState::Committed =>
325
+ OutboundHTLCStateDetails::Committed,
326
+ // RemoteRemoved states are ignored as the state is transient and the remote has not committed to
327
+ // the state yet.
328
+ OutboundHTLCState::RemoteRemoved(_) =>
329
+ OutboundHTLCStateDetails::Committed,
330
+ OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) =>
331
+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
332
+ OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Failure(_)) =>
333
+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
334
+ OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) =>
335
+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
336
+ OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(_)) =>
337
+ OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
338
+ }
339
+ }
340
+ }
341
+
342
+ impl_writeable_tlv_based_enum!(OutboundHTLCStateDetails,
343
+ (0, AwaitingRemoteRevokeToAdd) => {},
344
+ (2, Committed) => {},
345
+ (4, AwaitingRemoteRevokeToRemoveSuccess) => {},
346
+ (6, AwaitingRemoteRevokeToRemoveFailure) => {};
347
+ );
348
+
195
349
#[derive(Clone)]
196
350
enum OutboundHTLCOutcome {
197
351
/// LDK version 0.0.105+ will always fill in the preimage here.
@@ -227,6 +381,39 @@ struct OutboundHTLCOutput {
227
381
skimmed_fee_msat: Option<u64>,
228
382
}
229
383
384
+ /// Exposes details around pending outbound HTLCs.
385
+ #[derive(Clone, Debug, PartialEq)]
386
+ pub struct OutboundHTLCDetails {
387
+ /// The corresponding HTLC ID.
388
+ /// Not present when we are awaiting a remote revocation and the HTLC is not added yet.
389
+ pub htlc_id: Option<u64>,
390
+ /// The amount in msat.
391
+ pub amount_msat: u64,
392
+ /// The CLTV expiry.
393
+ pub cltv_expiry: u32,
394
+ /// The payment hash.
395
+ pub payment_hash: PaymentHash,
396
+ /// The state of the HTLC in the update_*_htlc, commitment_signed, revoke_and_ack flow.
397
+ /// Informs on which commitment transactions the HTLC is included.
398
+ pub state: OutboundHTLCStateDetails,
399
+ /// The extra fee being skimmed off the top of this HTLC.
400
+ pub skimmed_fee_msat: Option<u64>,
401
+ /// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
402
+ /// from the local commitment transaction and added to the commitment transaction fee.
403
+ /// This takes into account the second-stage HTLC transactions as well.
404
+ pub is_dust: bool,
405
+ }
406
+
407
+ impl_writeable_tlv_based!(OutboundHTLCDetails, {
408
+ (0, htlc_id, required),
409
+ (2, amount_msat, required),
410
+ (4, cltv_expiry, required),
411
+ (6, payment_hash, required),
412
+ (8, state, required),
413
+ (10, skimmed_fee_msat, required),
414
+ (12, is_dust, required),
415
+ });
416
+
230
417
/// See AwaitingRemoteRevoke ChannelState for more info
231
418
enum HTLCUpdateAwaitingACK {
232
419
AddHTLC { // TODO: Time out if we're getting close to cltv_expiry
@@ -1598,6 +1785,90 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
1598
1785
stats
1599
1786
}
1600
1787
1788
+ /// Returns information on all pending inbound HTLCs.
1789
+ pub fn get_pending_inbound_htlc_details(&self) -> Vec<InboundHTLCDetails> {
1790
+ let mut holding_cell_states = HashMap::new();
1791
+ for holding_cell_update in self.holding_cell_htlc_updates.iter() {
1792
+ match holding_cell_update {
1793
+ HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => {
1794
+ holding_cell_states.insert(
1795
+ htlc_id,
1796
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill,
1797
+ );
1798
+ },
1799
+ HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
1800
+ holding_cell_states.insert(
1801
+ htlc_id,
1802
+ InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
1803
+ );
1804
+ },
1805
+ _ => {},
1806
+ }
1807
+ }
1808
+ let mut inbound_details = Vec::new();
1809
+ let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1810
+ 0
1811
+ } else {
1812
+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1813
+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1814
+ };
1815
+ let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
1816
+ for htlc in self.pending_inbound_htlcs.iter() {
1817
+ inbound_details.push(InboundHTLCDetails{
1818
+ htlc_id: htlc.htlc_id,
1819
+ amount_msat: htlc.amount_msat,
1820
+ cltv_expiry: htlc.cltv_expiry,
1821
+ payment_hash: htlc.payment_hash,
1822
+ state: holding_cell_states.remove(&htlc.htlc_id).unwrap_or((&htlc.state).into()),
1823
+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
1824
+ });
1825
+ }
1826
+ inbound_details
1827
+ }
1828
+
1829
+ /// Returns information on all pending outbound HTLCs.
1830
+ pub fn get_pending_outbound_htlc_details(&self) -> Vec<OutboundHTLCDetails> {
1831
+ let mut outbound_details = Vec::new();
1832
+ let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1833
+ 0
1834
+ } else {
1835
+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1836
+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1837
+ };
1838
+ let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
1839
+ for htlc in self.pending_outbound_htlcs.iter() {
1840
+ outbound_details.push(OutboundHTLCDetails{
1841
+ htlc_id: Some(htlc.htlc_id),
1842
+ amount_msat: htlc.amount_msat,
1843
+ cltv_expiry: htlc.cltv_expiry,
1844
+ payment_hash: htlc.payment_hash,
1845
+ skimmed_fee_msat: htlc.skimmed_fee_msat,
1846
+ state: (&htlc.state).into(),
1847
+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
1848
+ });
1849
+ }
1850
+ for holding_cell_update in self.holding_cell_htlc_updates.iter() {
1851
+ if let HTLCUpdateAwaitingACK::AddHTLC {
1852
+ amount_msat,
1853
+ cltv_expiry,
1854
+ payment_hash,
1855
+ skimmed_fee_msat,
1856
+ ..
1857
+ } = *holding_cell_update {
1858
+ outbound_details.push(OutboundHTLCDetails{
1859
+ htlc_id: None,
1860
+ amount_msat: amount_msat,
1861
+ cltv_expiry: cltv_expiry,
1862
+ payment_hash: payment_hash,
1863
+ skimmed_fee_msat: skimmed_fee_msat,
1864
+ state: OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd,
1865
+ is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
1866
+ });
1867
+ }
1868
+ }
1869
+ outbound_details
1870
+ }
1871
+
1601
1872
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
1602
1873
/// Doesn't bother handling the
1603
1874
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
0 commit comments