@@ -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
@@ -1571,6 +1590,84 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
1571
1590
stats
1572
1591
}
1573
1592
1593
+
1594
+ /// Returns information on all pending inbound HTLCs.
1595
+ pub fn get_pending_inbound_htlc_details(&self) -> Vec<HTLCDetails> {
1596
+ let mut inbound_details = Vec::new();
1597
+ let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1598
+ 0
1599
+ } else {
1600
+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1601
+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1602
+ };
1603
+ let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
1604
+ for ref htlc in self.pending_inbound_htlcs.iter() {
1605
+ // Does not include HTLCs in the process of being fulfilled to be compatible with
1606
+ // the computation of `AvailableBalances::balance_msat`.
1607
+ if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state {
1608
+ continue;
1609
+ }
1610
+ inbound_details.push(HTLCDetails{
1611
+ htlc_id: Some(htlc.htlc_id),
1612
+ amount_msat: htlc.amount_msat,
1613
+ cltv_expiry: htlc.cltv_expiry,
1614
+ payment_hash: htlc.payment_hash,
1615
+ skimmed_fee_msat: None,
1616
+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
1617
+ });
1618
+ }
1619
+ inbound_details
1620
+ }
1621
+
1622
+ /// Returns information on all pending outbound HTLCs.
1623
+ pub fn get_pending_outbound_htlc_details(&self) -> Vec<HTLCDetails> {
1624
+ let mut outbound_details = Vec::new();
1625
+ let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
1626
+ 0
1627
+ } else {
1628
+ let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
1629
+ dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
1630
+ };
1631
+ let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
1632
+ for ref htlc in self.pending_outbound_htlcs.iter() {
1633
+ // Does not include HTLCs in the process of being fulfilled to be compatible with
1634
+ // the computation of `AvailableBalances::balance_msat`.
1635
+ match htlc.state {
1636
+ OutboundHTLCState::RemoteRemoved(OutboundHTLCOutcome::Success(_))|OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_))|OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) => {
1637
+ continue;
1638
+ },
1639
+ _ => {},
1640
+ }
1641
+ outbound_details.push(HTLCDetails{
1642
+ htlc_id: Some(htlc.htlc_id),
1643
+ amount_msat: htlc.amount_msat,
1644
+ cltv_expiry: htlc.cltv_expiry,
1645
+ payment_hash: htlc.payment_hash,
1646
+ skimmed_fee_msat: htlc.skimmed_fee_msat,
1647
+ is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
1648
+ });
1649
+ }
1650
+ for update in self.holding_cell_htlc_updates.iter() {
1651
+ if let &HTLCUpdateAwaitingACK::AddHTLC {
1652
+ amount_msat,
1653
+ cltv_expiry,
1654
+ payment_hash,
1655
+ skimmed_fee_msat,
1656
+ ..
1657
+ } = update {
1658
+ outbound_details.push(HTLCDetails{
1659
+ htlc_id: None,
1660
+ amount_msat: amount_msat,
1661
+ cltv_expiry: cltv_expiry,
1662
+ payment_hash: payment_hash,
1663
+ skimmed_fee_msat: skimmed_fee_msat,
1664
+ is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
1665
+ });
1666
+ }
1667
+ }
1668
+ outbound_details
1669
+ }
1670
+
1574
1671
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
1575
1672
/// Doesn't bother handling the
1576
1673
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
@@ -7415,16 +7512,17 @@ mod tests {
7415
7512
use bitcoin::blockdata::opcodes;
7416
7513
use bitcoin::network::constants::Network;
7417
7514
use hex;
7418
- use crate::ln::PaymentHash;
7419
- use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
7515
+ use crate::ln::{ PaymentHash, PaymentPreimage} ;
7516
+ use crate::ln::channelmanager::{self, HTLCSource, PaymentId, PendingHTLCRouting, PendingHTLCStatus, PendingHTLCInfo };
7420
7517
use crate::ln::channel::InitFeatures;
7421
- use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
7518
+ use crate::ln::channel::{Channel, InboundHTLCOutput, InboundHTLCRemovalReason, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat, HTLCUpdateAwaitingACK, OutboundHTLCOutcome };
7422
7519
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
7423
7520
use crate::ln::features::ChannelTypeFeatures;
7424
- use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
7521
+ use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT, OnionPacket, OnionErrorPacket };
7425
7522
use crate::ln::script::ShutdownScript;
7426
7523
use crate::ln::chan_utils;
7427
7524
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7525
+ use crate::ln::onion_utils::HTLCFailReason;
7428
7526
use crate::chain::BestBlock;
7429
7527
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
7430
7528
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8893,4 +8991,164 @@ mod tests {
8893
8991
);
8894
8992
assert!(res.is_err());
8895
8993
}
8994
+
8995
+ #[test]
8996
+ fn test_channel_balance_slices() {
8997
+ let fee_est = TestFeeEstimator{fee_est: 15000};
8998
+ let secp_ctx = Secp256k1::new();
8999
+ let signer = InMemorySigner::new(
9000
+ &secp_ctx,
9001
+ SecretKey::from_slice(&hex::decode("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749").unwrap()[..]).unwrap(),
9002
+ SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
9003
+ SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
9004
+ SecretKey::from_slice(&hex::decode("3333333333333333333333333333333333333333333333333333333333333333").unwrap()[..]).unwrap(),
9005
+ SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
9006
+ // These aren't set in the test vectors:
9007
+ [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],
9008
+ 10_000_000,
9009
+ [0; 32],
9010
+ [0; 32],
9011
+ );
9012
+ let keys_provider = Keys { signer: signer.clone() };
9013
+ let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
9014
+ let config = UserConfig::default();
9015
+ 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();
9016
+
9017
+ chan.context.counterparty_selected_channel_reserve_satoshis = Some(123_456);
9018
+ chan.context.value_to_self_msat = 7_000_000_000;
9019
+ chan.context.feerate_per_kw = 0;
9020
+ chan.context.counterparty_max_htlc_value_in_flight_msat = 1_000_000_000;
9021
+
9022
+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9023
+ htlc_id: 0,
9024
+ amount_msat: 1_000_000,
9025
+ cltv_expiry: 500,
9026
+ payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
9027
+ state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(PaymentPreimage([1; 32]))),
9028
+ });
9029
+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9030
+ htlc_id: 1,
9031
+ amount_msat: 2_000_000,
9032
+ cltv_expiry: 501,
9033
+ payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
9034
+ state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(OnionErrorPacket { data: [1; 32].to_vec() })),
9035
+ });
9036
+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9037
+ htlc_id: 2,
9038
+ amount_msat: 4_000_000,
9039
+ cltv_expiry: 502,
9040
+ payment_hash: PaymentHash(Sha256::hash(&[2; 32]).into_inner()),
9041
+ state: InboundHTLCState::Committed,
9042
+ });
9043
+ chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
9044
+ htlc_id: 3,
9045
+ amount_msat: 8_000_000,
9046
+ cltv_expiry: 503,
9047
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9048
+ state: InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(PendingHTLCInfo{
9049
+ routing: PendingHTLCRouting::Forward {
9050
+ onion_packet: OnionPacket{
9051
+ version: 0,
9052
+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9053
+ hop_data: [0; 20*65],
9054
+ hmac: [0; 32],
9055
+ },
9056
+ short_channel_id: 0,
9057
+ },
9058
+ incoming_shared_secret: [0; 32],
9059
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9060
+ incoming_amt_msat: Some(4_000_000),
9061
+ outgoing_amt_msat: 4_000_000,
9062
+ outgoing_cltv_value: 10000,
9063
+ skimmed_fee_msat: None,
9064
+ })),
9065
+ });
9066
+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9067
+ htlc_id: 4,
9068
+ amount_msat: 16_000_000,
9069
+ cltv_expiry: 504,
9070
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9071
+ state: OutboundHTLCState::Committed,
9072
+ source: HTLCSource::OutboundRoute {
9073
+ path: Path { hops: Vec::new(), blinded_tail: None },
9074
+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9075
+ first_hop_htlc_msat: 0,
9076
+ payment_id: PaymentId([5; 32]),
9077
+ },
9078
+ skimmed_fee_msat: None,
9079
+ });
9080
+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9081
+ htlc_id: 5,
9082
+ amount_msat: 32_000_000,
9083
+ cltv_expiry: 505,
9084
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9085
+ state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(None)),
9086
+ source: HTLCSource::OutboundRoute {
9087
+ path: Path { hops: Vec::new(), blinded_tail: None },
9088
+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9089
+ first_hop_htlc_msat: 0,
9090
+ payment_id: PaymentId([5; 32]),
9091
+ },
9092
+ skimmed_fee_msat: None,
9093
+ });
9094
+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9095
+ htlc_id: 6,
9096
+ amount_msat: 64_000_000,
9097
+ cltv_expiry: 506,
9098
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9099
+ state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(HTLCFailReason::from_failure_code(0x4000 | 8))),
9100
+ source: HTLCSource::OutboundRoute {
9101
+ path: Path { hops: Vec::new(), blinded_tail: None },
9102
+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9103
+ first_hop_htlc_msat: 0,
9104
+ payment_id: PaymentId([5; 32]),
9105
+ },
9106
+ skimmed_fee_msat: None,
9107
+ });
9108
+ chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
9109
+ htlc_id: 7,
9110
+ amount_msat: 128_000_000,
9111
+ cltv_expiry: 507,
9112
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9113
+ state: OutboundHTLCState::LocalAnnounced(Box::new(OnionPacket{
9114
+ version: 0,
9115
+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9116
+ hop_data: [0; 20*65],
9117
+ hmac: [0; 32],
9118
+ })),
9119
+ source: HTLCSource::OutboundRoute {
9120
+ path: Path { hops: Vec::new(), blinded_tail: None },
9121
+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9122
+ first_hop_htlc_msat: 0,
9123
+ payment_id: PaymentId([5; 32]),
9124
+ },
9125
+ skimmed_fee_msat: None,
9126
+ });
9127
+ chan.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::AddHTLC {
9128
+ amount_msat: 256_000_000,
9129
+ payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
9130
+ cltv_expiry: 506,
9131
+ source: HTLCSource::OutboundRoute {
9132
+ path: Path { hops: Vec::new(), blinded_tail: None },
9133
+ session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
9134
+ first_hop_htlc_msat: 0,
9135
+ payment_id: PaymentId([5; 32]),
9136
+ },
9137
+ onion_routing_packet: OnionPacket{
9138
+ version: 0,
9139
+ public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
9140
+ hop_data: [0; 20*65],
9141
+ hmac: [0; 32],
9142
+ },
9143
+ skimmed_fee_msat: None,
9144
+ });
9145
+
9146
+ let pending_inbound_total_msat: u64 = chan.context.get_pending_inbound_htlc_details().iter().map(|details| details.amount_msat).sum();
9147
+ let pending_outbound_total_msat: u64 = chan.context.get_pending_outbound_htlc_details().iter().map(|details| details.amount_msat).sum();
9148
+ let balances = chan.context.get_available_balances(&LowerBoundedFeeEstimator::new(&fee_est));
9149
+
9150
+ 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);
9151
+ assert_eq!(pending_inbound_total_msat, 2_000_000 + 4_000_000 + 8_000_000);
9152
+ assert_eq!(pending_outbound_total_msat, 16_000_000 + 64_000_000 + 128_000_000 + 256_000_000);
9153
+ }
8896
9154
}
0 commit comments