Skip to content

Commit

Permalink
Include pending HTLCs in ChannelDetails
Browse files Browse the repository at this point in the history
  • Loading branch information
wvanlint committed Aug 12, 2023
1 parent 9e4a35a commit 1a3a29f
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 0 deletions.
2 changes: 2 additions & 0 deletions fuzz/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
config: None,
feerate_sat_per_1000_weight: None,
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
pending_inbound_htlcs: Vec::new(),
pending_outbound_htlcs: Vec::new(),
});
}
Some(&$first_hops_vec[..])
Expand Down
301 changes: 301 additions & 0 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,85 @@ enum InboundHTLCState {
LocalRemoved(InboundHTLCRemovalReason),
}

/// Exposes the state of pending inbound HTLCs.
///
/// Fail and fulfill suffixes indicate the resolution of the HTLC.
#[derive(Clone, Debug, PartialEq)]
pub enum InboundHTLCStateDetails {
/// RemoteAnnounced states indicate that the remote sent update_add_htlc for this HTLC while the
/// HTLC is not on any commitment transactions yet. It will be included in the next local
/// commitment transaction when we receive commitment_signed and return revoke_and_ack.
RemoteAnnouncedForward,
/// See above.
RemoteAnnouncedFail,
/// AwaitingRemoteRevokeToAnnounce states indicate that we have received commitment_signed with
/// this HTLC, returned revoke_and_ack, and this HTLC is included on the local commitment
/// transaction but not the remote commitment transaction. The remote hasn't yet revoked their
/// previous state and we have not yet included this HTLC in commitment_signed because we are
/// waiting on the remote's revocation.
AwaitingRemoteRevokeToAnnounceForward,
/// See above.
AwaitingRemoteRevokeToAnnounceFail,
/// AwaitingAnnouncedRemoteRevoke states indicate that we have received commitment_signed with
/// this HTLC, returned revoke_and_ack, and this HTLC is included on the local commitment
/// transaction. We have also included this HTLC in our latest commitment_signed and are now just
/// waiting on the remote's revoke_and_ack before this HTLC will be included on the remote
/// commitment transaction as well and can then get forwarded and/or removed.
AwaitingAnnouncedRemoteRevokeForward,
/// See above.
AwaitingAnnouncedRemoteRevokeFail,
/// Committed indicates that this HTLC has been included in the commitment_signed and
/// revoke_and_ack flow on both sides and is included in both commitment transactions.
Committed,
/// AwaitingRemoteRevokeToRemove states indicate that this HTLC will be removed by us sending
/// update_*_htlc and commitment_signed, but the remote has not sent revoke_and_ack for the
/// previous commitment_signed yet. The HTLC is still on both commitment transactions.
AwaitingRemoteRevokeToRemoveFulfill,
/// See above.
AwaitingRemoteRevokeToRemoveFail,
/// LocalRemoved states indicate that this HTLC has been removed by us and a new
/// commitment_signed was sent, but the HTLC is still on both commitment transactions. When the
/// remote sends the next revoke_and_ack, it will be removed from the remote's commitment
/// transaction.
LocalRemovedFailRelay,
/// See above.
LocalRemovedFailMalformed,
/// See above.
LocalRemovedFulfill,
}

impl From<&InboundHTLCState> for InboundHTLCStateDetails {
fn from(state: &InboundHTLCState) -> InboundHTLCStateDetails {
match state {
InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(_)) => InboundHTLCStateDetails::RemoteAnnouncedForward,
InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Fail(_)) => InboundHTLCStateDetails::RemoteAnnouncedFail,
InboundHTLCState::AwaitingRemoteRevokeToAnnounce(PendingHTLCStatus::Forward(_)) => InboundHTLCStateDetails::AwaitingRemoteRevokeToAnnounceForward,
InboundHTLCState::AwaitingRemoteRevokeToAnnounce(PendingHTLCStatus::Fail(_)) => InboundHTLCStateDetails::AwaitingRemoteRevokeToAnnounceFail,
InboundHTLCState::AwaitingAnnouncedRemoteRevoke(PendingHTLCStatus::Forward(_)) => InboundHTLCStateDetails::AwaitingAnnouncedRemoteRevokeForward,
InboundHTLCState::AwaitingAnnouncedRemoteRevoke(PendingHTLCStatus::Fail(_)) => InboundHTLCStateDetails::AwaitingAnnouncedRemoteRevokeFail,
InboundHTLCState::Committed => InboundHTLCStateDetails::Committed,
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(_)) => InboundHTLCStateDetails::LocalRemovedFailRelay,
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed(_)) => InboundHTLCStateDetails::LocalRemovedFailMalformed,
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) => InboundHTLCStateDetails::LocalRemovedFulfill,
}
}
}

impl_writeable_tlv_based_enum!(InboundHTLCStateDetails,
(0, RemoteAnnouncedForward) => {},
(2, RemoteAnnouncedFail) => {},
(4, AwaitingRemoteRevokeToAnnounceForward) => {},
(6, AwaitingRemoteRevokeToAnnounceFail) => {},
(8, AwaitingAnnouncedRemoteRevokeForward) => {},
(10, AwaitingAnnouncedRemoteRevokeFail) => {},
(12, Committed) => {},
(14, AwaitingRemoteRevokeToRemoveFulfill) => {},
(16, AwaitingRemoteRevokeToRemoveFail) => {},
(18, LocalRemovedFailRelay) => {},
(20, LocalRemovedFailMalformed) => {},
(22, LocalRemovedFulfill) => {};
);

struct InboundHTLCOutput {
htlc_id: u64,
amount_msat: u64,
Expand All @@ -160,6 +239,35 @@ struct InboundHTLCOutput {
state: InboundHTLCState,
}

/// Exposes details around pending inbound HTLCs.
#[derive(Clone, Debug, PartialEq)]
pub struct InboundHTLCDetails {
/// The corresponding HTLC ID.
pub htlc_id: u64,
/// The amount in msat.
pub amount_msat: u64,
/// The CLTV expiry.
pub cltv_expiry: u32,
/// The payment hash.
pub payment_hash: PaymentHash,
/// The state of the HTLC in the update_*_htlc, commitment_signed, revoke_and_ack flow.
/// Informs on which commitment transactions the HTLC is included.
pub state: InboundHTLCStateDetails,
/// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
/// from the local commitment transaction and added to the commitment transaction fee.
/// This takes into account the second-stage HTLC transactions as well.
pub is_dust: bool,
}

impl_writeable_tlv_based!(InboundHTLCDetails, {
(0, htlc_id, required),
(2, amount_msat, required),
(4, cltv_expiry, required),
(6, payment_hash, required),
(8, state, required),
(10, is_dust, required),
});

enum OutboundHTLCState {
/// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we
/// created it we would have put it in the holding cell instead). When they next revoke_and_ack
Expand Down Expand Up @@ -192,6 +300,82 @@ enum OutboundHTLCState {
AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome),
}

/// Exposes the state of pending outbound HTLCs.
///
/// Failure and success suffixes indicate the resolution of the HTLC.
#[derive(Clone, Debug, PartialEq)]
pub enum OutboundHTLCStateDetails {
/// THe AwaitingRemoteRevokeToAnnounce state indicates that this HTLC will be added by us sending
/// update_add_htlc and commitment_signed, but the remote has not sent revoke_and_ack for the
/// previous commitment_signed yet. The HTLC is not on any commitment transactions yet.
AwaitingRemoteRevokeToAnnounce,
/// The LocalAnnounced state indicates that this HTLC has been added by us and included in a
/// commitment_signed, but the HTLC is not on any commitment transactions yet. When the remote
/// sends the next revoke_and_ack, it will be included in the remote's commitment transaction.
LocalAnnounced,
/// The Committed state indicates that this HTLC has been included in a commitment_signed sent by
/// us and we have received a corresponding revoke_and_ack. The HTLC is therefore included in the
/// remote's commitment transaction. Note that this includes the subsequent state where we
/// receive the remote's commitment_signed with this HTLC, respond with the corresponding
/// revoke_and_ack, and include it in the local commitment transaction, as:
/// * they've revoked, so worst case we can announce an old state and get our (option on)
/// money back (though we won't), and,
/// * we'll send them a revoke when they send a commitment_signed, and since only they're
/// allowed to remove it, the "can only be removed once committed on both sides" requirement
/// doesn't matter to us and it's up to them to enforce it, worst-case they jump ahead but
/// we'll never get out of sync).
Committed,
/// RemoteRemoved states indicate that this HTLC has been removed by the remote with
/// update_*_htlc. The HTLC is still on both commitment transactions and we are waiting on their
/// commitment_signed mesage.
RemoteRemovedSuccess,
/// See above.
RemoteRemovedFailure,
/// AwaitingRemoteRevokeToRemove states indicate that the remote removed this HTLC and sent a
/// commitment_signed and we've revoke_and_ack'ed it. It is removed from the local commitment
/// transaction but the remote side hasn't yet revoked their previous state and therefore we
/// haven't yet removed this HTLC in our latest commitment_signed. This HTLC is still included in
/// the remote's commitment transaction.
AwaitingRemoteRevokeToRemoveSuccess,
/// See above.
AwaitingRemoteRevokeToRemoveFailure,
/// AwaitingRemovedRemoteRevoke states indicate that the remote removed this and sent a
/// commitment_signed and we've revoke_and_ack'ed it. It is therefore removed from the local
/// commitment transaction. This HTLC has also been removed in our latest commitment_signed and
/// will be removed from the remote's commitment transaction when we receive their
/// revoke_and_ack, after which we can do any backwards failing.
AwaitingRemovedRemoteRevokeSuccess,
/// See above.
AwaitingRemovedRemoteRevokeFailure,
}

impl From<&OutboundHTLCState> for OutboundHTLCStateDetails {
fn from(state: &OutboundHTLCState) -> OutboundHTLCStateDetails {
match state {
OutboundHTLCState::LocalAnnounced(_) => OutboundHTLCStateDetails::LocalAnnounced,
OutboundHTLCState::Committed => OutboundHTLCStateDetails::Committed,
OutboundHTLCState::RemoteRemoved(OutboundHTLCOutcome::Success(_)) => OutboundHTLCStateDetails::RemoteRemovedSuccess,
OutboundHTLCState::RemoteRemoved(OutboundHTLCOutcome::Failure(_)) => OutboundHTLCStateDetails::RemoteRemovedFailure,
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) => OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess,
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Failure(_)) => OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure,
OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) => OutboundHTLCStateDetails::AwaitingRemovedRemoteRevokeSuccess,
OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(_)) => OutboundHTLCStateDetails::AwaitingRemovedRemoteRevokeFailure,
}
}
}

impl_writeable_tlv_based_enum!(OutboundHTLCStateDetails,
(0, AwaitingRemoteRevokeToAnnounce) => {},
(2, LocalAnnounced) => {},
(4, Committed) => {},
(6, RemoteRemovedSuccess) => {},
(8, RemoteRemovedFailure) => {},
(10, AwaitingRemoteRevokeToRemoveSuccess) => {},
(12, AwaitingRemoteRevokeToRemoveFailure) => {},
(14, AwaitingRemovedRemoteRevokeSuccess) => {},
(16, AwaitingRemovedRemoteRevokeFailure) => {};
);

#[derive(Clone)]
enum OutboundHTLCOutcome {
/// LDK version 0.0.105+ will always fill in the preimage here.
Expand Down Expand Up @@ -227,6 +411,39 @@ struct OutboundHTLCOutput {
skimmed_fee_msat: Option<u64>,
}

/// Exposes details around pending outbound HTLCs.
#[derive(Clone, Debug, PartialEq)]
pub struct OutboundHTLCDetails {
/// The corresponding HTLC ID.
/// Not present when we are awaiting a remote revocation and the HTLC is not added yet.
pub htlc_id: Option<u64>,
/// The amount in msat.
pub amount_msat: u64,
/// The CLTV expiry.
pub cltv_expiry: u32,
/// The payment hash.
pub payment_hash: PaymentHash,
/// The state of the HTLC in the update_*_htlc, commitment_signed, revoke_and_ack flow.
/// Informs on which commitment transactions the HTLC is included.
pub state: OutboundHTLCStateDetails,
/// The extra fee being skimmed off the top of this HTLC.
pub skimmed_fee_msat: Option<u64>,
/// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed
/// from the local commitment transaction and added to the commitment transaction fee.
/// This takes into account the second-stage HTLC transactions as well.
pub is_dust: bool,
}

impl_writeable_tlv_based!(OutboundHTLCDetails, {
(0, htlc_id, required),
(2, amount_msat, required),
(4, cltv_expiry, required),
(6, payment_hash, required),
(8, state, required),
(10, skimmed_fee_msat, required),
(12, is_dust, required),
});

/// See AwaitingRemoteRevoke ChannelState for more info
enum HTLCUpdateAwaitingACK {
AddHTLC { // TODO: Time out if we're getting close to cltv_expiry
Expand Down Expand Up @@ -1549,6 +1766,90 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
stats
}

/// Returns information on all pending inbound HTLCs.
pub fn get_pending_inbound_htlc_details(&self) -> Vec<InboundHTLCDetails> {
let mut holding_cell_states = HashMap::new();
for holding_cell_update in self.holding_cell_htlc_updates.iter() {
match holding_cell_update {
HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => {
holding_cell_states.insert(
htlc_id,
InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill,
);
},
HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
holding_cell_states.insert(
htlc_id,
InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail,
);
},
_ => {},
}
}
let mut inbound_details = Vec::new();
let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
for htlc in self.pending_inbound_htlcs.iter() {
inbound_details.push(InboundHTLCDetails{
htlc_id: htlc.htlc_id,
amount_msat: htlc.amount_msat,
cltv_expiry: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
state: holding_cell_states.remove(&htlc.htlc_id).unwrap_or((&htlc.state).into()),
is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
});
}
inbound_details
}

/// Returns information on all pending outbound HTLCs.
pub fn get_pending_outbound_htlc_details(&self) -> Vec<OutboundHTLCDetails> {
let mut outbound_details = Vec::new();
let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
for htlc in self.pending_outbound_htlcs.iter() {
outbound_details.push(OutboundHTLCDetails{
htlc_id: Some(htlc.htlc_id),
amount_msat: htlc.amount_msat,
cltv_expiry: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
skimmed_fee_msat: htlc.skimmed_fee_msat,
state: (&htlc.state).into(),
is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
});
}
for holding_cell_update in self.holding_cell_htlc_updates.iter() {
if let HTLCUpdateAwaitingACK::AddHTLC {
amount_msat,
cltv_expiry,
payment_hash,
skimmed_fee_msat,
..
} = *holding_cell_update {
outbound_details.push(OutboundHTLCDetails{
htlc_id: None,
amount_msat: amount_msat,
cltv_expiry: cltv_expiry,
payment_hash: payment_hash,
skimmed_fee_msat: skimmed_fee_msat,
state: OutboundHTLCStateDetails::AwaitingRemoteRevokeToAnnounce,
is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
});
}
}
outbound_details
}

/// Returns a HTLCStats about pending outbound htlcs, *including* pending adds in our holding cell.
fn get_outbound_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>) -> HTLCStats {
let context = self;
Expand Down
Loading

0 comments on commit 1a3a29f

Please sign in to comment.