Skip to content

Commit dbcd317

Browse files
committed
Include pending HTLC's in ChannelDetails
1 parent 5cddf5e commit dbcd317

File tree

4 files changed

+279
-12
lines changed

4 files changed

+279
-12
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

+253-11
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+
(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+
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,70 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
15981617
stats
15991618
}
16001619

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+
16011684
/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
16021685
/// Doesn't bother handling the
16031686
/// 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> {
16111694
let inbound_stats = context.get_inbound_pending_htlc_stats(None);
16121695
let outbound_stats = context.get_outbound_pending_htlc_stats(None);
16131696

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;
16211698

16221699
let outbound_capacity_msat = context.value_to_self_msat
16231700
.saturating_sub(outbound_stats.pending_htlcs_value_msat)
@@ -7455,16 +7532,17 @@ mod tests {
74557532
use bitcoin::blockdata::opcodes;
74567533
use bitcoin::network::constants::Network;
74577534
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};
74607537
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};
74627539
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
74637540
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};
74657542
use crate::ln::script::ShutdownScript;
74667543
use crate::ln::chan_utils;
74677544
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
7545+
use crate::ln::onion_utils::HTLCFailReason;
74687546
use crate::chain::BestBlock;
74697547
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
74707548
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
@@ -8933,4 +9011,168 @@ mod tests {
89339011
);
89349012
assert!(res.is_err());
89359013
}
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+
}
89369178
}

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 HTLCs.
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 HTLCs.
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
}
@@ -7528,6 +7541,8 @@ impl Writeable for ChannelDetails {
75287541
(37, user_channel_id_high_opt, option),
75297542
(39, self.feerate_sat_per_1000_weight, option),
75307543
(41, self.channel_shutdown_state, option),
7544+
(43, self.pending_inbound_htlcs, optional_vec),
7545+
(45, self.pending_outbound_htlcs, optional_vec),
75317546
});
75327547
Ok(())
75337548
}
@@ -7566,6 +7581,8 @@ impl Readable for ChannelDetails {
75667581
(37, user_channel_id_high_opt, option),
75677582
(39, feerate_sat_per_1000_weight, option),
75687583
(41, channel_shutdown_state, option),
7584+
(43, pending_inbound_htlcs, optional_vec),
7585+
(45, pending_outbound_htlcs, optional_vec),
75697586
});
75707587

75717588
// `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with
@@ -7602,6 +7619,8 @@ impl Readable for ChannelDetails {
76027619
inbound_htlc_maximum_msat,
76037620
feerate_sat_per_1000_weight,
76047621
channel_shutdown_state,
7622+
pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()),
7623+
pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()),
76057624
})
76067625
}
76077626
}

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)