@@ -449,6 +449,25 @@ struct HTLCStats {
449
449
on_holder_tx_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
450
450
}
451
451
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
+
452
471
/// An enum gathering stats on commitment transaction, either local or remote.
453
472
struct CommitmentStats<'a> {
454
473
tx: CommitmentTransaction, // the transaction info
@@ -1598,6 +1617,70 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
1598
1617
stats
1599
1618
}
1600
1619
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
+
1601
1684
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
1602
1685
/// Doesn't bother handling the
1603
1686
/// 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> {
1611
1694
let inbound_stats = context.get_inbound_pending_htlc_stats(None);
1612
1695
let outbound_stats = context.get_outbound_pending_htlc_stats(None);
1613
1696
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;
1621
1698
1622
1699
let outbound_capacity_msat = context.value_to_self_msat
1623
1700
.saturating_sub(outbound_stats.pending_htlcs_value_msat)
@@ -7455,16 +7532,17 @@ mod tests {
7455
7532
use bitcoin::blockdata::opcodes;
7456
7533
use bitcoin::network::constants::Network;
7457
7534
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 };
7460
7537
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 };
7462
7539
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
7463
7540
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 };
7465
7542
use crate::ln::script::ShutdownScript;
7466
7543
use crate::ln::chan_utils;
7467
7544
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7545
+ use crate::ln::onion_utils::HTLCFailReason;
7468
7546
use crate::chain::BestBlock;
7469
7547
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
7470
7548
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8933,4 +9011,168 @@ mod tests {
8933
9011
);
8934
9012
assert!(res.is_err());
8935
9013
}
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
+ }
8936
9178
}
0 commit comments