@@ -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
+ (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
+
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,84 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
1598
1617
stats
1599
1618
}
1600
1619
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
+
1601
1698
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
1602
1699
/// Doesn't bother handling the
1603
1700
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
@@ -7453,16 +7550,17 @@ mod tests {
7453
7550
use bitcoin::blockdata::opcodes;
7454
7551
use bitcoin::network::constants::Network;
7455
7552
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 };
7458
7555
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 };
7460
7557
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
7461
7558
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 };
7463
7560
use crate::ln::script::ShutdownScript;
7464
7561
use crate::ln::chan_utils;
7465
7562
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7563
+ use crate::ln::onion_utils::HTLCFailReason;
7466
7564
use crate::chain::BestBlock;
7467
7565
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
7468
7566
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8931,4 +9029,164 @@ mod tests {
8931
9029
);
8932
9030
assert!(res.is_err());
8933
9031
}
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
+ }
8934
9192
}
0 commit comments