@@ -449,6 +449,25 @@ struct HTLCStats {
449449 on_holder_tx_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
450450}
451451
452+ #[derive(Clone, Debug, PartialEq)]
453+ pub struct HTLCDetails {
454+ htlc_id: Option<u64>,
455+ amount_msat: u64,
456+ cltv_expiry: u32,
457+ payment_hash: PaymentHash,
458+ skimmed_fee_msat: Option<u64>,
459+ is_dust: bool,
460+ }
461+
462+ impl_writeable_tlv_based!(HTLCDetails, {
463+ (1, htlc_id, option),
464+ (2, amount_msat, required),
465+ (4, cltv_expiry, required),
466+ (6, payment_hash, required),
467+ (7, skimmed_fee_msat, option),
468+ (8, is_dust, required),
469+ });
470+
452471/// An enum gathering stats on commitment transaction, either local or remote.
453472struct CommitmentStats<'a> {
454473 tx: CommitmentTransaction, // the transaction info
@@ -1598,6 +1617,84 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
15981617 stats
15991618 }
16001619
1620+
1621+ /// Returns information on all pending inbound HTLCs.
1622+ pub fn get_pending_inbound_htlc_details(&self) -> Vec<HTLCDetails> {
1623+ let mut inbound_details = Vec::new();
1624+ let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1625+ 0
1626+ } else {
1627+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1628+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1629+ };
1630+ let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
1631+ for ref htlc in self.pending_inbound_htlcs.iter() {
1632+ // Does not include HTLCs in the process of being fulfilled to be compatible with
1633+ // the computation of `AvailableBalances::balance_msat`.
1634+ if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state {
1635+ continue;
1636+ }
1637+ inbound_details.push(HTLCDetails{
1638+ htlc_id: Some(htlc.htlc_id),
1639+ amount_msat: htlc.amount_msat,
1640+ cltv_expiry: htlc.cltv_expiry,
1641+ payment_hash: htlc.payment_hash,
1642+ skimmed_fee_msat: None,
1643+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
1644+ });
1645+ }
1646+ inbound_details
1647+ }
1648+
1649+ /// Returns information on all pending outbound HTLCs.
1650+ pub fn get_pending_outbound_htlc_details(&self) -> Vec<HTLCDetails> {
1651+ let mut outbound_details = Vec::new();
1652+ let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1653+ 0
1654+ } else {
1655+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1656+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1657+ };
1658+ let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
1659+ for ref htlc in self.pending_outbound_htlcs.iter() {
1660+ // Does not include HTLCs in the process of being fulfilled to be compatible with
1661+ // the computation of `AvailableBalances::balance_msat`.
1662+ match htlc.state {
1663+ OutboundHTLCState::RemoteRemoved(OutboundHTLCOutcome::Success(_))|OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_))|OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) => {
1664+ continue;
1665+ },
1666+ _ => {},
1667+ }
1668+ outbound_details.push(HTLCDetails{
1669+ htlc_id: Some(htlc.htlc_id),
1670+ amount_msat: htlc.amount_msat,
1671+ cltv_expiry: htlc.cltv_expiry,
1672+ payment_hash: htlc.payment_hash,
1673+ skimmed_fee_msat: htlc.skimmed_fee_msat,
1674+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
1675+ });
1676+ }
1677+ for update in self.holding_cell_htlc_updates.iter() {
1678+ if let &HTLCUpdateAwaitingACK::AddHTLC {
1679+ amount_msat,
1680+ cltv_expiry,
1681+ payment_hash,
1682+ skimmed_fee_msat,
1683+ ..
1684+ } = update {
1685+ outbound_details.push(HTLCDetails{
1686+ htlc_id: None,
1687+ amount_msat: amount_msat,
1688+ cltv_expiry: cltv_expiry,
1689+ payment_hash: payment_hash,
1690+ skimmed_fee_msat: skimmed_fee_msat,
1691+ is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
1692+ });
1693+ }
1694+ }
1695+ outbound_details
1696+ }
1697+
16011698 /// Get the available balances, see [`AvailableBalances`]'s fields for more info.
16021699 /// Doesn't bother handling the
16031700 /// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
@@ -7453,16 +7550,17 @@ mod tests {
74537550 use bitcoin::blockdata::opcodes;
74547551 use bitcoin::network::constants::Network;
74557552 use hex;
7456- use crate::ln::PaymentHash;
7457- use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
7553+ use crate::ln::{ PaymentHash, PaymentPreimage} ;
7554+ use crate::ln::channelmanager::{self, HTLCSource, PaymentId, PendingHTLCRouting, PendingHTLCStatus, PendingHTLCInfo };
74587555 use crate::ln::channel::InitFeatures;
7459- use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
7556+ use crate::ln::channel::{Channel, InboundHTLCOutput, InboundHTLCRemovalReason, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat, HTLCUpdateAwaitingACK, OutboundHTLCOutcome };
74607557 use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
74617558 use crate::ln::features::ChannelTypeFeatures;
7462- use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
7559+ use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT, OnionPacket, OnionErrorPacket };
74637560 use crate::ln::script::ShutdownScript;
74647561 use crate::ln::chan_utils;
74657562 use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7563+ use crate::ln::onion_utils::HTLCFailReason;
74667564 use crate::chain::BestBlock;
74677565 use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
74687566 use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8931,4 +9029,164 @@ mod tests {
89319029 );
89329030 assert!(res.is_err());
89339031 }
9032+
9033+ #[test]
9034+ fn test_channel_balance_slices() {
9035+ let fee_est = TestFeeEstimator{fee_est: 15000};
9036+ let secp_ctx = Secp256k1::new();
9037+ let signer = InMemorySigner::new(
9038+ &secp_ctx,
9039+ SecretKey::from_slice(&hex::decode("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749").unwrap()[..]).unwrap(),
9040+ SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
9041+ SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
9042+ SecretKey::from_slice(&hex::decode("3333333333333333333333333333333333333333333333333333333333333333").unwrap()[..]).unwrap(),
9043+ SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
9044+ // These aren't set in the test vectors:
9045+ [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
9046+ 10_000_000,
9047+ [0; 32],
9048+ [0; 32],
9049+ );
9050+ let keys_provider = Keys { signer: signer.clone() };
9051+ let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
9052+ let config = UserConfig::default();
9053+ let mut chan = OutboundV1Channel::<InMemorySigner>::new(&LowerBoundedFeeEstimator::new(&fee_est), &&keys_provider, &&keys_provider, counterparty_node_id, &channelmanager::provided_init_features(&config), 10_000_000, 0, 42, &config, 0, 42).unwrap();
9054+
9055+ chan.context.counterparty_selected_channel_reserve_satoshis = Some(123_456);
9056+ chan.context.value_to_self_msat = 7_000_000_000;
9057+ chan.context.feerate_per_kw = 0;
9058+ chan.context.counterparty_max_htlc_value_in_flight_msat = 1_000_000_000;
9059+
9060+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9061+ htlc_id: 0,
9062+ amount_msat: 1_000_000,
9063+ cltv_expiry: 500,
9064+ payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
9065+ state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(PaymentPreimage([1; 32]))),
9066+ });
9067+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9068+ htlc_id: 1,
9069+ amount_msat: 2_000_000,
9070+ cltv_expiry: 501,
9071+ payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
9072+ state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(OnionErrorPacket { data: [1; 32].to_vec() })),
9073+ });
9074+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9075+ htlc_id: 2,
9076+ amount_msat: 4_000_000,
9077+ cltv_expiry: 502,
9078+ payment_hash: PaymentHash(Sha256::hash(&[2; 32]).into_inner()),
9079+ state: InboundHTLCState::Committed,
9080+ });
9081+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9082+ htlc_id: 3,
9083+ amount_msat: 8_000_000,
9084+ cltv_expiry: 503,
9085+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9086+ state: InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(PendingHTLCInfo{
9087+ routing: PendingHTLCRouting::Forward {
9088+ onion_packet: OnionPacket{
9089+ version: 0,
9090+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9091+ hop_data: [0; 20*65],
9092+ hmac: [0; 32],
9093+ },
9094+ short_channel_id: 0,
9095+ },
9096+ incoming_shared_secret: [0; 32],
9097+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9098+ incoming_amt_msat: Some(4_000_000),
9099+ outgoing_amt_msat: 4_000_000,
9100+ outgoing_cltv_value: 10000,
9101+ skimmed_fee_msat: None,
9102+ })),
9103+ });
9104+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9105+ htlc_id: 4,
9106+ amount_msat: 16_000_000,
9107+ cltv_expiry: 504,
9108+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9109+ state: OutboundHTLCState::Committed,
9110+ source: HTLCSource::OutboundRoute {
9111+ path: Path { hops: Vec::new(), blinded_tail: None },
9112+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9113+ first_hop_htlc_msat: 0,
9114+ payment_id: PaymentId([5; 32]),
9115+ },
9116+ skimmed_fee_msat: None,
9117+ });
9118+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9119+ htlc_id: 5,
9120+ amount_msat: 32_000_000,
9121+ cltv_expiry: 505,
9122+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9123+ state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(None)),
9124+ source: HTLCSource::OutboundRoute {
9125+ path: Path { hops: Vec::new(), blinded_tail: None },
9126+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9127+ first_hop_htlc_msat: 0,
9128+ payment_id: PaymentId([5; 32]),
9129+ },
9130+ skimmed_fee_msat: None,
9131+ });
9132+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9133+ htlc_id: 6,
9134+ amount_msat: 64_000_000,
9135+ cltv_expiry: 506,
9136+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9137+ state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(HTLCFailReason::from_failure_code(0x4000 | 8))),
9138+ source: HTLCSource::OutboundRoute {
9139+ path: Path { hops: Vec::new(), blinded_tail: None },
9140+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9141+ first_hop_htlc_msat: 0,
9142+ payment_id: PaymentId([5; 32]),
9143+ },
9144+ skimmed_fee_msat: None,
9145+ });
9146+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9147+ htlc_id: 7,
9148+ amount_msat: 128_000_000,
9149+ cltv_expiry: 507,
9150+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9151+ state: OutboundHTLCState::LocalAnnounced(Box::new(OnionPacket{
9152+ version: 0,
9153+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9154+ hop_data: [0; 20*65],
9155+ hmac: [0; 32],
9156+ })),
9157+ source: HTLCSource::OutboundRoute {
9158+ path: Path { hops: Vec::new(), blinded_tail: None },
9159+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9160+ first_hop_htlc_msat: 0,
9161+ payment_id: PaymentId([5; 32]),
9162+ },
9163+ skimmed_fee_msat: None,
9164+ });
9165+ chan.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::AddHTLC {
9166+ amount_msat: 256_000_000,
9167+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9168+ cltv_expiry: 506,
9169+ source: HTLCSource::OutboundRoute {
9170+ path: Path { hops: Vec::new(), blinded_tail: None },
9171+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9172+ first_hop_htlc_msat: 0,
9173+ payment_id: PaymentId([5; 32]),
9174+ },
9175+ onion_routing_packet: OnionPacket{
9176+ version: 0,
9177+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9178+ hop_data: [0; 20*65],
9179+ hmac: [0; 32],
9180+ },
9181+ skimmed_fee_msat: None,
9182+ });
9183+
9184+ let pending_inbound_total_msat: u64 = chan.context.get_pending_inbound_htlc_details().iter().map(|details| details.amount_msat).sum();
9185+ let pending_outbound_total_msat: u64 = chan.context.get_pending_outbound_htlc_details().iter().map(|details| details.amount_msat).sum();
9186+ let balances = chan.context.get_available_balances(&LowerBoundedFeeEstimator::new(&fee_est));
9187+
9188+ assert_eq!(balances.balance_msat, 7_000_000_000 + 1_000_000 - 16_000_000 - 32_000_000 - 64_000_000 - 128_000_000 - 256_000_000);
9189+ assert_eq!(pending_inbound_total_msat, 2_000_000 + 4_000_000 + 8_000_000);
9190+ assert_eq!(pending_outbound_total_msat, 16_000_000 + 64_000_000 + 128_000_000 + 256_000_000);
9191+ }
89349192}
0 commit comments