Skip to content

Commit 67e872b

Browse files
committed
Include pending HTLC's in ChannelDetails
1 parent 8a8f29a commit 67e872b

File tree

4 files changed

+288
-5
lines changed

4 files changed

+288
-5
lines changed

fuzz/src/router.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
271271
config: None,
272272
feerate_sat_per_1000_weight: None,
273273
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
274+
pending_inbound_htlcs: Vec::new(),
275+
pending_outbound_htlcs: Vec::new(),
274276
});
275277
}
276278
Some(&first_hops_vec[..])

lightning/src/ln/channel.rs

Lines changed: 262 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
453472
struct CommitmentStats<'a> {
454473
tx: CommitmentTransaction, // the transaction info
@@ -1571,6 +1590,84 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
15711590
stats
15721591
}
15731592

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+
15741671
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
15751672
/// Doesn't bother handling the
15761673
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
@@ -7415,16 +7512,17 @@ mod tests {
74157512
use bitcoin::blockdata::opcodes;
74167513
use bitcoin::network::constants::Network;
74177514
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};
74207517
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};
74227519
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
74237520
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};
74257522
use crate::ln::script::ShutdownScript;
74267523
use crate::ln::chan_utils;
74277524
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7525+
use crate::ln::onion_utils::HTLCFailReason;
74287526
use crate::chain::BestBlock;
74297527
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
74307528
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8893,4 +8991,164 @@ mod tests {
88938991
);
88948992
assert!(res.is_err());
88958993
}
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+
}
88969154
}

lightning/src/ln/channelmanager.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa
4040
// Since this struct is returned in `list_channels` methods, expose it here in case users want to
4141
// construct one themselves.
4242
use crate::ln::{inbound_payment, PaymentHash, PaymentPreimage, PaymentSecret};
43-
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
43+
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, HTLCDetails};
4444
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
4545
#[cfg(any(feature = "_test_utils", test))]
4646
use crate::ln::features::Bolt11InvoiceFeatures;
@@ -124,6 +124,9 @@ pub(super) enum PendingHTLCRouting {
124124
pub(super) struct PendingHTLCInfo {
125125
pub(super) routing: PendingHTLCRouting,
126126
pub(super) incoming_shared_secret: [u8; 32],
127+
#[cfg(test)]
128+
pub(super) payment_hash: PaymentHash,
129+
#[cfg(not(test))]
127130
payment_hash: PaymentHash,
128131
/// Amount received
129132
pub(super) incoming_amt_msat: Option<u64>, // Added in 0.0.113
@@ -1505,6 +1508,14 @@ pub struct ChannelDetails {
15051508
///
15061509
/// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109.
15071510
pub config: Option<ChannelConfig>,
1511+
/// Pending inbound HTLC's.
1512+
///
1513+
/// This field is empty for objects serialized with LDK versions prior to 0.0.117.
1514+
pub pending_inbound_htlcs: Vec<HTLCDetails>,
1515+
/// Pending outbound HTLC's.
1516+
///
1517+
/// This field is empty for objects serialized with LDK versions prior to 0.0.117.
1518+
pub pending_outbound_htlcs: Vec<HTLCDetails>,
15081519
}
15091520

15101521
impl ChannelDetails {
@@ -1580,6 +1591,8 @@ impl ChannelDetails {
15801591
inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(),
15811592
config: Some(context.config()),
15821593
channel_shutdown_state: Some(context.shutdown_state()),
1594+
pending_inbound_htlcs: context.get_pending_inbound_htlc_details(),
1595+
pending_outbound_htlcs: context.get_pending_outbound_htlc_details(),
15831596
}
15841597
}
15851598
}
@@ -7477,6 +7490,8 @@ impl Writeable for ChannelDetails {
74777490
(37, user_channel_id_high_opt, option),
74787491
(39, self.feerate_sat_per_1000_weight, option),
74797492
(41, self.channel_shutdown_state, option),
7493+
(43, self.pending_inbound_htlcs, optional_vec),
7494+
(45, self.pending_outbound_htlcs, optional_vec),
74807495
});
74817496
Ok(())
74827497
}
@@ -7515,6 +7530,8 @@ impl Readable for ChannelDetails {
75157530
(37, user_channel_id_high_opt, option),
75167531
(39, feerate_sat_per_1000_weight, option),
75177532
(41, channel_shutdown_state, option),
7533+
(43, pending_inbound_htlcs, optional_vec),
7534+
(45, pending_outbound_htlcs, optional_vec),
75187535
});
75197536

75207537
// `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with
@@ -7551,6 +7568,8 @@ impl Readable for ChannelDetails {
75517568
inbound_htlc_maximum_msat,
75527569
feerate_sat_per_1000_weight,
75537570
channel_shutdown_state,
7571+
pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()),
7572+
pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()),
75547573
})
75557574
}
75567575
}

lightning/src/routing/router.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,6 +2697,8 @@ mod tests {
26972697
config: None,
26982698
feerate_sat_per_1000_weight: None,
26992699
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
2700+
pending_inbound_htlcs: Vec::new(),
2701+
pending_outbound_htlcs: Vec::new(),
27002702
}
27012703
}
27022704

@@ -6768,6 +6770,8 @@ pub(crate) mod bench_utils {
67686770
config: None,
67696771
feerate_sat_per_1000_weight: None,
67706772
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
6773+
pending_inbound_htlcs: Vec::new(),
6774+
pending_outbound_htlcs: Vec::new(),
67716775
}
67726776
}
67736777

0 commit comments

Comments
 (0)