Skip to content

Commit 9ca0041

Browse files
committed
Include pending HTLC's in ChannelDetails
1 parent 0fadb54 commit 9ca0041

File tree

4 files changed

+288
-5
lines changed

4 files changed

+288
-5
lines changed

fuzz/src/router.rs

+2
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

+262-4
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
@@ -1598,6 +1617,84 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
15981617
stats
15991618
}
16001619

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+
16011698
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
16021699
/// Doesn't bother handling the
16031700
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
@@ -7453,16 +7550,17 @@ mod tests {
74537550
use bitcoin::blockdata::opcodes;
74547551
use bitcoin::network::constants::Network;
74557552
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};
74587555
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};
74607557
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
74617558
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};
74637560
use crate::ln::script::ShutdownScript;
74647561
use crate::ln::chan_utils;
74657562
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7563+
use crate::ln::onion_utils::HTLCFailReason;
74667564
use crate::chain::BestBlock;
74677565
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
74687566
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8931,4 +9029,164 @@ mod tests {
89319029
);
89329030
assert!(res.is_err());
89339031
}
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+
}
89349192
}

lightning/src/ln/channelmanager.rs

+20-1
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, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
43+
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, 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
}
@@ -7529,6 +7542,8 @@ impl Writeable for ChannelDetails {
75297542
(37, user_channel_id_high_opt, option),
75307543
(39, self.feerate_sat_per_1000_weight, option),
75317544
(41, self.channel_shutdown_state, option),
7545+
(43, self.pending_inbound_htlcs, optional_vec),
7546+
(45, self.pending_outbound_htlcs, optional_vec),
75327547
});
75337548
Ok(())
75347549
}
@@ -7567,6 +7582,8 @@ impl Readable for ChannelDetails {
75677582
(37, user_channel_id_high_opt, option),
75687583
(39, feerate_sat_per_1000_weight, option),
75697584
(41, channel_shutdown_state, option),
7585+
(43, pending_inbound_htlcs, optional_vec),
7586+
(45, pending_outbound_htlcs, optional_vec),
75707587
});
75717588

75727589
// `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with
@@ -7603,6 +7620,8 @@ impl Readable for ChannelDetails {
76037620
inbound_htlc_maximum_msat,
76047621
feerate_sat_per_1000_weight,
76057622
channel_shutdown_state,
7623+
pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()),
7624+
pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()),
76067625
})
76077626
}
76087627
}

lightning/src/routing/router.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2740,6 +2740,8 @@ mod tests {
27402740
config: None,
27412741
feerate_sat_per_1000_weight: None,
27422742
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
2743+
pending_inbound_htlcs: Vec::new(),
2744+
pending_outbound_htlcs: Vec::new(),
27432745
}
27442746
}
27452747

@@ -6811,6 +6813,8 @@ pub(crate) mod bench_utils {
68116813
config: None,
68126814
feerate_sat_per_1000_weight: None,
68136815
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
6816+
pending_inbound_htlcs: Vec::new(),
6817+
pending_outbound_htlcs: Vec::new(),
68146818
}
68156819
}
68166820

0 commit comments

Comments
 (0)