@@ -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+ (0, htlc_id, required),
464+ (2, amount_msat, required),
465+ (4, cltv_expiry, required),
466+ (6, payment_hash, required),
467+ (8, skimmed_fee_msat, required),
468+ (10, 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,70 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
15981617 stats
15991618 }
16001619
1620+ /// Returns information on all pending inbound HTLCs.
1621+ pub fn get_pending_inbound_htlc_details(&self) -> Vec<HTLCDetails> {
1622+ let mut inbound_details = Vec::new();
1623+ let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1624+ 0
1625+ } else {
1626+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1627+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1628+ };
1629+ let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
1630+ for ref htlc in self.pending_inbound_htlcs.iter() {
1631+ inbound_details.push(HTLCDetails{
1632+ htlc_id: Some(htlc.htlc_id),
1633+ amount_msat: htlc.amount_msat,
1634+ cltv_expiry: htlc.cltv_expiry,
1635+ payment_hash: htlc.payment_hash,
1636+ skimmed_fee_msat: None,
1637+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
1638+ });
1639+ }
1640+ inbound_details
1641+ }
1642+
1643+ /// Returns information on all pending outbound HTLCs.
1644+ pub fn get_pending_outbound_htlc_details(&self) -> Vec<HTLCDetails> {
1645+ let mut outbound_details = Vec::new();
1646+ let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1647+ 0
1648+ } else {
1649+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1650+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1651+ };
1652+ let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
1653+ for ref htlc in self.pending_outbound_htlcs.iter() {
1654+ outbound_details.push(HTLCDetails{
1655+ htlc_id: Some(htlc.htlc_id),
1656+ amount_msat: htlc.amount_msat,
1657+ cltv_expiry: htlc.cltv_expiry,
1658+ payment_hash: htlc.payment_hash,
1659+ skimmed_fee_msat: htlc.skimmed_fee_msat,
1660+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
1661+ });
1662+ }
1663+ for update in self.holding_cell_htlc_updates.iter() {
1664+ if let &HTLCUpdateAwaitingACK::AddHTLC {
1665+ amount_msat,
1666+ cltv_expiry,
1667+ payment_hash,
1668+ skimmed_fee_msat,
1669+ ..
1670+ } = update {
1671+ outbound_details.push(HTLCDetails{
1672+ htlc_id: None,
1673+ amount_msat: amount_msat,
1674+ cltv_expiry: cltv_expiry,
1675+ payment_hash: payment_hash,
1676+ skimmed_fee_msat: skimmed_fee_msat,
1677+ is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
1678+ });
1679+ }
1680+ }
1681+ outbound_details
1682+ }
1683+
16011684 /// Get the available balances, see [`AvailableBalances`]'s fields for more info.
16021685 /// Doesn't bother handling the
16031686 /// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
@@ -1611,13 +1694,7 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
16111694 let inbound_stats = context.get_inbound_pending_htlc_stats(None);
16121695 let outbound_stats = context.get_outbound_pending_htlc_stats(None);
16131696
1614- let mut balance_msat = context.value_to_self_msat;
1615- for ref htlc in context.pending_inbound_htlcs.iter() {
1616- if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state {
1617- balance_msat += htlc.amount_msat;
1618- }
1619- }
1620- balance_msat -= outbound_stats.pending_htlcs_value_msat;
1697+ let balance_msat = context.value_to_self_msat - outbound_stats.pending_htlcs_value_msat;
16211698
16221699 let outbound_capacity_msat = context.value_to_self_msat
16231700 .saturating_sub(outbound_stats.pending_htlcs_value_msat)
@@ -7455,16 +7532,17 @@ mod tests {
74557532 use bitcoin::blockdata::opcodes;
74567533 use bitcoin::network::constants::Network;
74577534 use hex;
7458- use crate::ln::PaymentHash;
7459- use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
7535+ use crate::ln::{ PaymentHash, PaymentPreimage} ;
7536+ use crate::ln::channelmanager::{self, HTLCSource, PaymentId, PendingHTLCRouting, PendingHTLCStatus, PendingHTLCInfo };
74607537 use crate::ln::channel::InitFeatures;
7461- use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
7538+ use crate::ln::channel::{Channel, InboundHTLCOutput, InboundHTLCRemovalReason, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat, HTLCUpdateAwaitingACK, OutboundHTLCOutcome };
74627539 use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
74637540 use crate::ln::features::ChannelTypeFeatures;
7464- use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
7541+ use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT, OnionPacket, OnionErrorPacket };
74657542 use crate::ln::script::ShutdownScript;
74667543 use crate::ln::chan_utils;
74677544 use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7545+ use crate::ln::onion_utils::HTLCFailReason;
74687546 use crate::chain::BestBlock;
74697547 use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
74707548 use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8933,4 +9011,168 @@ mod tests {
89339011 );
89349012 assert!(res.is_err());
89359013 }
9014+
9015+ #[test]
9016+ fn test_channel_balance_slices() {
9017+ let fee_est = TestFeeEstimator{fee_est: 15000};
9018+ let secp_ctx = Secp256k1::new();
9019+ let signer = InMemorySigner::new(
9020+ &secp_ctx,
9021+ SecretKey::from_slice(&hex::decode("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749").unwrap()[..]).unwrap(),
9022+ SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
9023+ SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
9024+ SecretKey::from_slice(&hex::decode("3333333333333333333333333333333333333333333333333333333333333333").unwrap()[..]).unwrap(),
9025+ SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
9026+ // These aren't set in the test vectors:
9027+ [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],
9028+ 10_000_000,
9029+ [0; 32],
9030+ [0; 32],
9031+ );
9032+ let keys_provider = Keys { signer: signer.clone() };
9033+ let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
9034+ let config = UserConfig::default();
9035+ 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();
9036+
9037+ chan.context.counterparty_selected_channel_reserve_satoshis = Some(123_456);
9038+ chan.context.value_to_self_msat = 7_000_000_000;
9039+ chan.context.feerate_per_kw = 0;
9040+ chan.context.counterparty_max_htlc_value_in_flight_msat = 1_000_000_000;
9041+
9042+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9043+ htlc_id: 0,
9044+ amount_msat: 1_000_000,
9045+ cltv_expiry: 500,
9046+ payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
9047+ state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(PaymentPreimage([1; 32]))),
9048+ });
9049+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9050+ htlc_id: 1,
9051+ amount_msat: 2_000_000,
9052+ cltv_expiry: 501,
9053+ payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
9054+ state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(OnionErrorPacket { data: [1; 32].to_vec() })),
9055+ });
9056+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9057+ htlc_id: 2,
9058+ amount_msat: 4_000_000,
9059+ cltv_expiry: 502,
9060+ payment_hash: PaymentHash(Sha256::hash(&[2; 32]).into_inner()),
9061+ state: InboundHTLCState::Committed,
9062+ });
9063+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9064+ htlc_id: 3,
9065+ amount_msat: 8_000_000,
9066+ cltv_expiry: 503,
9067+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9068+ state: InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(PendingHTLCInfo{
9069+ routing: PendingHTLCRouting::Forward {
9070+ onion_packet: OnionPacket{
9071+ version: 0,
9072+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9073+ hop_data: [0; 20*65],
9074+ hmac: [0; 32],
9075+ },
9076+ short_channel_id: 0,
9077+ },
9078+ incoming_shared_secret: [0; 32],
9079+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9080+ incoming_amt_msat: Some(4_000_000),
9081+ outgoing_amt_msat: 4_000_000,
9082+ outgoing_cltv_value: 10000,
9083+ skimmed_fee_msat: None,
9084+ })),
9085+ });
9086+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9087+ htlc_id: 4,
9088+ amount_msat: 16_000_000,
9089+ cltv_expiry: 504,
9090+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9091+ state: OutboundHTLCState::Committed,
9092+ source: HTLCSource::OutboundRoute {
9093+ path: Path { hops: Vec::new(), blinded_tail: None },
9094+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9095+ first_hop_htlc_msat: 0,
9096+ payment_id: PaymentId([5; 32]),
9097+ },
9098+ skimmed_fee_msat: None,
9099+ });
9100+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9101+ htlc_id: 5,
9102+ amount_msat: 32_000_000,
9103+ cltv_expiry: 505,
9104+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9105+ state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(None)),
9106+ source: HTLCSource::OutboundRoute {
9107+ path: Path { hops: Vec::new(), blinded_tail: None },
9108+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9109+ first_hop_htlc_msat: 0,
9110+ payment_id: PaymentId([5; 32]),
9111+ },
9112+ skimmed_fee_msat: None,
9113+ });
9114+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9115+ htlc_id: 6,
9116+ amount_msat: 64_000_000,
9117+ cltv_expiry: 506,
9118+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9119+ state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(HTLCFailReason::from_failure_code(0x4000 | 8))),
9120+ source: HTLCSource::OutboundRoute {
9121+ path: Path { hops: Vec::new(), blinded_tail: None },
9122+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9123+ first_hop_htlc_msat: 0,
9124+ payment_id: PaymentId([5; 32]),
9125+ },
9126+ skimmed_fee_msat: None,
9127+ });
9128+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9129+ htlc_id: 7,
9130+ amount_msat: 128_000_000,
9131+ cltv_expiry: 507,
9132+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9133+ state: OutboundHTLCState::LocalAnnounced(Box::new(OnionPacket{
9134+ version: 0,
9135+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9136+ hop_data: [0; 20*65],
9137+ hmac: [0; 32],
9138+ })),
9139+ source: HTLCSource::OutboundRoute {
9140+ path: Path { hops: Vec::new(), blinded_tail: None },
9141+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9142+ first_hop_htlc_msat: 0,
9143+ payment_id: PaymentId([5; 32]),
9144+ },
9145+ skimmed_fee_msat: None,
9146+ });
9147+ chan.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::AddHTLC {
9148+ amount_msat: 256_000_000,
9149+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9150+ cltv_expiry: 506,
9151+ source: HTLCSource::OutboundRoute {
9152+ path: Path { hops: Vec::new(), blinded_tail: None },
9153+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9154+ first_hop_htlc_msat: 0,
9155+ payment_id: PaymentId([5; 32]),
9156+ },
9157+ onion_routing_packet: OnionPacket{
9158+ version: 0,
9159+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9160+ hop_data: [0; 20*65],
9161+ hmac: [0; 32],
9162+ },
9163+ skimmed_fee_msat: None,
9164+ });
9165+
9166+ let pending_inbound_total_msat: u64 = chan.context.get_pending_inbound_htlc_details().iter().map(|details| details.amount_msat).sum();
9167+ let pending_outbound_total_msat: u64 = chan.context.get_pending_outbound_htlc_details().iter().map(|details| details.amount_msat).sum();
9168+ let balances = chan.context.get_available_balances(&LowerBoundedFeeEstimator::new(&fee_est));
9169+
9170+ assert_eq!(
9171+ chan.context.channel_value_satoshis * 1000,
9172+ pending_inbound_total_msat + pending_outbound_total_msat +
9173+ balances.inbound_capacity_msat + balances.outbound_capacity_msat +
9174+ chan.context.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000 +
9175+ chan.context.holder_selected_channel_reserve_satoshis * 1000
9176+ );
9177+ }
89369178}
0 commit comments