Skip to content

Commit d8efd56

Browse files
committed
Include pending HTLCs in ChannelDetails
1 parent 9e4a35a commit d8efd56

File tree

4 files changed

+294
-0
lines changed

4 files changed

+294
-0
lines changed

fuzz/src/router.rs

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

lightning/src/ln/channel.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,72 @@ enum InboundHTLCState {
152152
LocalRemoved(InboundHTLCRemovalReason),
153153
}
154154

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+
155221
struct InboundHTLCOutput {
156222
htlc_id: u64,
157223
amount_msat: u64,
@@ -160,6 +226,35 @@ struct InboundHTLCOutput {
160226
state: InboundHTLCState,
161227
}
162228

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+
163258
enum OutboundHTLCState {
164259
/// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we
165260
/// created it we would have put it in the holding cell instead). When they next revoke_and_ack
@@ -192,6 +287,65 @@ enum OutboundHTLCState {
192287
AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome),
193288
}
194289

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+
195349
#[derive(Clone)]
196350
enum OutboundHTLCOutcome {
197351
/// LDK version 0.0.105+ will always fill in the preimage here.
@@ -227,6 +381,39 @@ struct OutboundHTLCOutput {
227381
skimmed_fee_msat: Option<u64>,
228382
}
229383

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+
230417
/// See AwaitingRemoteRevoke ChannelState for more info
231418
enum HTLCUpdateAwaitingACK {
232419
AddHTLC { // TODO: Time out if we're getting close to cltv_expiry
@@ -1598,6 +1785,90 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
15981785
stats
15991786
}
16001787

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+
16011872
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
16021873
/// Doesn't bother handling the
16031874
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC

lightning/src/ln/channelmanager.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa
4141
// construct one themselves.
4242
use crate::ln::{inbound_payment, PaymentHash, PaymentPreimage, PaymentSecret};
4343
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
44+
pub use crate::ln::channel::{InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails};
4445
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
4546
#[cfg(any(feature = "_test_utils", test))]
4647
use crate::ln::features::Bolt11InvoiceFeatures;
@@ -1526,6 +1527,14 @@ pub struct ChannelDetails {
15261527
///
15271528
/// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109.
15281529
pub config: Option<ChannelConfig>,
1530+
/// Pending inbound HTLCs.
1531+
///
1532+
/// This field is empty for objects serialized with LDK versions prior to 0.0.117.
1533+
pub pending_inbound_htlcs: Vec<InboundHTLCDetails>,
1534+
/// Pending outbound HTLCs.
1535+
///
1536+
/// This field is empty for objects serialized with LDK versions prior to 0.0.117.
1537+
pub pending_outbound_htlcs: Vec<OutboundHTLCDetails>,
15291538
}
15301539

15311540
impl ChannelDetails {
@@ -1601,6 +1610,8 @@ impl ChannelDetails {
16011610
inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(),
16021611
config: Some(context.config()),
16031612
channel_shutdown_state: Some(context.shutdown_state()),
1613+
pending_inbound_htlcs: context.get_pending_inbound_htlc_details(),
1614+
pending_outbound_htlcs: context.get_pending_outbound_htlc_details(),
16041615
}
16051616
}
16061617
}
@@ -7614,6 +7625,8 @@ impl Writeable for ChannelDetails {
76147625
(37, user_channel_id_high_opt, option),
76157626
(39, self.feerate_sat_per_1000_weight, option),
76167627
(41, self.channel_shutdown_state, option),
7628+
(43, self.pending_inbound_htlcs, optional_vec),
7629+
(45, self.pending_outbound_htlcs, optional_vec),
76177630
});
76187631
Ok(())
76197632
}
@@ -7652,6 +7665,8 @@ impl Readable for ChannelDetails {
76527665
(37, user_channel_id_high_opt, option),
76537666
(39, feerate_sat_per_1000_weight, option),
76547667
(41, channel_shutdown_state, option),
7668+
(43, pending_inbound_htlcs, optional_vec),
7669+
(45, pending_outbound_htlcs, optional_vec),
76557670
});
76567671

76577672
// `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with
@@ -7688,6 +7703,8 @@ impl Readable for ChannelDetails {
76887703
inbound_htlc_maximum_msat,
76897704
feerate_sat_per_1000_weight,
76907705
channel_shutdown_state,
7706+
pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()),
7707+
pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()),
76917708
})
76927709
}
76937710
}

lightning/src/routing/router.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2741,6 +2741,8 @@ mod tests {
27412741
config: None,
27422742
feerate_sat_per_1000_weight: None,
27432743
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
2744+
pending_inbound_htlcs: Vec::new(),
2745+
pending_outbound_htlcs: Vec::new(),
27442746
}
27452747
}
27462748

@@ -6812,6 +6814,8 @@ pub(crate) mod bench_utils {
68126814
config: None,
68136815
feerate_sat_per_1000_weight: None,
68146816
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
6817+
pending_inbound_htlcs: Vec::new(),
6818+
pending_outbound_htlcs: Vec::new(),
68156819
}
68166820
}
68176821

0 commit comments

Comments
 (0)