diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index 653491024d5..9620051e627 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -241,6 +241,9 @@ pub fn do_test(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(), + holding_cell_updates: Vec::new(), }); } Some(&$first_hops_vec[..]) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index fd0d94d3eb6..93ec68d5182 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -152,6 +152,50 @@ enum InboundHTLCState { LocalRemoved(InboundHTLCRemovalReason), } +#[derive(Clone, Debug, PartialEq)] +pub enum InboundHTLCStateDetails { + RemoteAnnouncedForward, + RemoteAnnouncedFail, + AwaitingRemoteRevokeToAnnounceForward, + AwaitingRemoteRevokeToAnnounceFail, + AwaitingAnnouncedRemoteRevokeForward, + AwaitingAnnouncedRemoteRevokeFail, + Committed, + LocalRemovedFailRelay, + LocalRemovedFailMalformed, + 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) => {}, + (1, RemoteAnnouncedFail) => {}, + (2, AwaitingRemoteRevokeToAnnounceForward) => {}, + (3, AwaitingRemoteRevokeToAnnounceFail) => {}, + (4, AwaitingAnnouncedRemoteRevokeForward) => {}, + (5, AwaitingAnnouncedRemoteRevokeFail) => {}, + (6, Committed) => {}, + (7, LocalRemovedFailRelay) => {}, + (8, LocalRemovedFailMalformed) => {}, + (9, LocalRemovedFulfill) => {}; +); + struct InboundHTLCOutput { htlc_id: u64, amount_msat: u64, @@ -160,6 +204,25 @@ struct InboundHTLCOutput { state: InboundHTLCState, } +#[derive(Clone, Debug, PartialEq)] +pub struct InboundHTLCDetails { + pub htlc_id: u64, + pub amount_msat: u64, + pub cltv_expiry: u32, + pub payment_hash: PaymentHash, + pub state: InboundHTLCStateDetails, + 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 @@ -192,6 +255,44 @@ enum OutboundHTLCState { AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome), } +#[derive(Clone, Debug, PartialEq)] +pub enum OutboundHTLCStateDetails { + LocalAnnounced, + Committed, + RemoteRemovedSuccess, + RemoteRemovedFailure, + AwaitingRemoteRevokeToRemoveSuccess, + AwaitingRemoteRevokeToRemoveFailure, + AwaitingRemovedRemoteRevokeSuccess, + 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, LocalAnnounced) => {}, + (1, Committed) => {}, + (2, RemoteRemovedSuccess) => {}, + (3, RemoteRemovedFailure) => {}, + (4, AwaitingRemoteRevokeToRemoveSuccess) => {}, + (5, AwaitingRemoteRevokeToRemoveFailure) => {}, + (6, AwaitingRemovedRemoteRevokeSuccess) => {}, + (7, AwaitingRemovedRemoteRevokeFailure) => {}; +); + #[derive(Clone)] enum OutboundHTLCOutcome { /// LDK version 0.0.105+ will always fill in the preimage here. @@ -227,6 +328,27 @@ struct OutboundHTLCOutput { skimmed_fee_msat: Option, } +#[derive(Clone, Debug, PartialEq)] +pub struct OutboundHTLCDetails { + pub htlc_id: u64, + pub amount_msat: u64, + pub cltv_expiry: u32, + pub payment_hash: PaymentHash, + pub state: OutboundHTLCStateDetails, + pub skimmed_fee_msat: Option, + 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 @@ -249,6 +371,39 @@ enum HTLCUpdateAwaitingACK { }, } +#[derive(Clone, Debug, PartialEq)] +pub enum HTLCUpdateAwaitingACKDetails { + AddHTLC { + amount_msat: u64, + cltv_expiry: u32, + payment_hash: PaymentHash, + skimmed_fee_msat: Option, + is_dust: bool, + }, + ClaimHTLC { + htlc_id: u64, + }, + FailHTLC { + htlc_id: u64, + }, +} + +impl_writeable_tlv_based_enum!(HTLCUpdateAwaitingACKDetails, + (0, AddHTLC) => { + (0, amount_msat, required), + (2, cltv_expiry, required), + (4, payment_hash, required), + (6, skimmed_fee_msat, required), + (8, is_dust, required), + }, + (1, ClaimHTLC) => { + (0, htlc_id, required), + }, + (2, FailHTLC) => { + (0, htlc_id, required), + }; +); + /// There are a few "states" and then a number of flags which can be applied: /// We first move through init with `OurInitSent` -> `TheirInitSent` -> `FundingCreated` -> `FundingSent`. /// `TheirChannelReady` and `OurChannelReady` then get set on `FundingSent`, and when both are set we @@ -1549,6 +1704,94 @@ impl ChannelContext { stats } + /// Returns information on all pending inbound HTLCs. + pub fn get_pending_inbound_htlc_details(&self) -> Vec { + 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 ref 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: (&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 { + 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 ref htlc in self.pending_outbound_htlcs.iter() { + outbound_details.push(OutboundHTLCDetails{ + htlc_id: 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, + }); + } + outbound_details + } + + pub fn get_holding_cell_details(&self) -> Vec { + let mut holding_cell_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 ref htlc_update in self.holding_cell_htlc_updates.iter() { + holding_cell_details.push(match htlc_update { + HTLCUpdateAwaitingACK::AddHTLC { + amount_msat, + cltv_expiry, + payment_hash, + skimmed_fee_msat, + .. + } => HTLCUpdateAwaitingACKDetails::AddHTLC { + amount_msat: *amount_msat, + cltv_expiry: *cltv_expiry, + payment_hash: *payment_hash, + skimmed_fee_msat: *skimmed_fee_msat, + is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat, + }, + HTLCUpdateAwaitingACK::ClaimHTLC { + htlc_id, + .. + } => HTLCUpdateAwaitingACKDetails::ClaimHTLC { + htlc_id: *htlc_id, + }, + HTLCUpdateAwaitingACK::FailHTLC { + htlc_id, + .. + } => HTLCUpdateAwaitingACKDetails::FailHTLC { + htlc_id: *htlc_id, + }, + }); + } + holding_cell_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) -> HTLCStats { let context = self; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6cd4799ff3c..6b9eae2e7ed 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -40,7 +40,7 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa // Since this struct is returned in `list_channels` methods, expose it here in case users want to // construct one themselves. use crate::ln::{inbound_payment, PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel}; +use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, InboundHTLCDetails, OutboundHTLCDetails, HTLCUpdateAwaitingACKDetails}; use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; @@ -1505,6 +1505,18 @@ pub struct ChannelDetails { /// /// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109. pub config: Option, + /// Pending inbound HTLCs. + /// + /// This field is empty for objects serialized with LDK versions prior to 0.0.117. + pub pending_inbound_htlcs: Vec, + /// Pending outbound HTLCs. + /// + /// This field is empty for objects serialized with LDK versions prior to 0.0.117. + pub pending_outbound_htlcs: Vec, + /// Pending HTLC updates that are held awaiting a revoke_and_ack. + /// + /// This field is empty for objects serialized with LDK versions prior to 0.0.117. + pub holding_cell_updates: Vec, } impl ChannelDetails { @@ -1580,6 +1592,9 @@ impl ChannelDetails { inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(), config: Some(context.config()), channel_shutdown_state: Some(context.shutdown_state()), + pending_inbound_htlcs: context.get_pending_inbound_htlc_details(), + pending_outbound_htlcs: context.get_pending_outbound_htlc_details(), + holding_cell_updates: context.get_holding_cell_details(), } } } @@ -7530,6 +7545,9 @@ impl Writeable for ChannelDetails { (37, user_channel_id_high_opt, option), (39, self.feerate_sat_per_1000_weight, option), (41, self.channel_shutdown_state, option), + (43, self.pending_inbound_htlcs, optional_vec), + (45, self.pending_outbound_htlcs, optional_vec), + (47, self.holding_cell_updates, optional_vec), }); Ok(()) } @@ -7568,6 +7586,9 @@ impl Readable for ChannelDetails { (37, user_channel_id_high_opt, option), (39, feerate_sat_per_1000_weight, option), (41, channel_shutdown_state, option), + (43, pending_inbound_htlcs, optional_vec), + (45, pending_outbound_htlcs, optional_vec), + (47, holding_cell_updates, optional_vec), }); // `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with @@ -7604,6 +7625,9 @@ impl Readable for ChannelDetails { inbound_htlc_maximum_msat, feerate_sat_per_1000_weight, channel_shutdown_state, + pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()), + pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()), + holding_cell_updates: holding_cell_updates.unwrap_or(Vec::new()), }) } } diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index b4316165bd1..d4fd6503c67 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -2741,6 +2741,9 @@ mod tests { 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(), + holding_cell_updates: Vec::new(), } } @@ -6812,6 +6815,9 @@ pub(crate) mod bench_utils { 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(), + holding_cell_updates: Vec::new(), } }