Skip to content

Commit

Permalink
Include pending HTLC's in ChannelDetails
Browse files Browse the repository at this point in the history
  • Loading branch information
wvanlint committed Jul 27, 2023
1 parent 5cddf5e commit dbcd317
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 12 deletions.
2 changes: 2 additions & 0 deletions fuzz/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
config: None,
feerate_sat_per_1000_weight: None,
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
pending_inbound_htlcs: Vec::new(),
pending_outbound_htlcs: Vec::new(),
});
}
Some(&first_hops_vec[..])
Expand Down
264 changes: 253 additions & 11 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,25 @@ struct HTLCStats {
on_holder_tx_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
}

#[derive(Clone, Debug, PartialEq)]
pub struct HTLCDetails {
htlc_id: Option<u64>,
amount_msat: u64,
cltv_expiry: u32,
payment_hash: PaymentHash,
skimmed_fee_msat: Option<u64>,
is_dust: bool,
}

impl_writeable_tlv_based!(HTLCDetails, {
(0, htlc_id, required),
(2, amount_msat, required),
(4, cltv_expiry, required),
(6, payment_hash, required),
(8, skimmed_fee_msat, required),
(10, is_dust, required),
});

/// An enum gathering stats on commitment transaction, either local or remote.
struct CommitmentStats<'a> {
tx: CommitmentTransaction, // the transaction info
Expand Down Expand Up @@ -1598,6 +1617,70 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
stats
}

/// Returns information on all pending inbound HTLCs.
pub fn get_pending_inbound_htlc_details(&self) -> Vec<HTLCDetails> {
let mut inbound_details = Vec::new();
let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis;
for ref htlc in self.pending_inbound_htlcs.iter() {
inbound_details.push(HTLCDetails{
htlc_id: Some(htlc.htlc_id),
amount_msat: htlc.amount_msat,
cltv_expiry: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
skimmed_fee_msat: None,
is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat,
});
}
inbound_details
}

/// Returns information on all pending outbound HTLCs.
pub fn get_pending_outbound_htlc_details(&self) -> Vec<HTLCDetails> {
let mut outbound_details = Vec::new();
let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64;
dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis;
for ref htlc in self.pending_outbound_htlcs.iter() {
outbound_details.push(HTLCDetails{
htlc_id: Some(htlc.htlc_id),
amount_msat: htlc.amount_msat,
cltv_expiry: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
skimmed_fee_msat: htlc.skimmed_fee_msat,
is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat,
});
}
for update in self.holding_cell_htlc_updates.iter() {
if let &HTLCUpdateAwaitingACK::AddHTLC {
amount_msat,
cltv_expiry,
payment_hash,
skimmed_fee_msat,
..
} = update {
outbound_details.push(HTLCDetails{
htlc_id: None,
amount_msat: amount_msat,
cltv_expiry: cltv_expiry,
payment_hash: payment_hash,
skimmed_fee_msat: skimmed_fee_msat,
is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat,
});
}
}
outbound_details
}

/// Get the available balances, see [`AvailableBalances`]'s fields for more info.
/// Doesn't bother handling the
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
Expand All @@ -1611,13 +1694,7 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
let inbound_stats = context.get_inbound_pending_htlc_stats(None);
let outbound_stats = context.get_outbound_pending_htlc_stats(None);

let mut balance_msat = context.value_to_self_msat;
for ref htlc in context.pending_inbound_htlcs.iter() {
if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state {
balance_msat += htlc.amount_msat;
}
}
balance_msat -= outbound_stats.pending_htlcs_value_msat;
let balance_msat = context.value_to_self_msat - outbound_stats.pending_htlcs_value_msat;

let outbound_capacity_msat = context.value_to_self_msat
.saturating_sub(outbound_stats.pending_htlcs_value_msat)
Expand Down Expand Up @@ -7455,16 +7532,17 @@ mod tests {
use bitcoin::blockdata::opcodes;
use bitcoin::network::constants::Network;
use hex;
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId, PendingHTLCRouting, PendingHTLCStatus, PendingHTLCInfo};
use crate::ln::channel::InitFeatures;
use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
use crate::ln::channel::{Channel, InboundHTLCOutput, InboundHTLCRemovalReason, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat, HTLCUpdateAwaitingACK, OutboundHTLCOutcome};
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT, OnionPacket, OnionErrorPacket};
use crate::ln::script::ShutdownScript;
use crate::ln::chan_utils;
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
use crate::ln::onion_utils::HTLCFailReason;
use crate::chain::BestBlock;
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
Expand Down Expand Up @@ -8933,4 +9011,168 @@ mod tests {
);
assert!(res.is_err());
}

#[test]
fn test_channel_balance_slices() {
let fee_est = TestFeeEstimator{fee_est: 15000};
let secp_ctx = Secp256k1::new();
let signer = InMemorySigner::new(
&secp_ctx,
SecretKey::from_slice(&hex::decode("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("3333333333333333333333333333333333333333333333333333333333333333").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
// These aren't set in the test vectors:
[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],
10_000_000,
[0; 32],
[0; 32],
);
let keys_provider = Keys { signer: signer.clone() };
let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
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();

chan.context.counterparty_selected_channel_reserve_satoshis = Some(123_456);
chan.context.value_to_self_msat = 7_000_000_000;
chan.context.feerate_per_kw = 0;
chan.context.counterparty_max_htlc_value_in_flight_msat = 1_000_000_000;

chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
htlc_id: 0,
amount_msat: 1_000_000,
cltv_expiry: 500,
payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(PaymentPreimage([1; 32]))),
});
chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
htlc_id: 1,
amount_msat: 2_000_000,
cltv_expiry: 501,
payment_hash: PaymentHash(Sha256::hash(&[1; 32]).into_inner()),
state: InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(OnionErrorPacket { data: [1; 32].to_vec() })),
});
chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
htlc_id: 2,
amount_msat: 4_000_000,
cltv_expiry: 502,
payment_hash: PaymentHash(Sha256::hash(&[2; 32]).into_inner()),
state: InboundHTLCState::Committed,
});
chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
htlc_id: 3,
amount_msat: 8_000_000,
cltv_expiry: 503,
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
state: InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(PendingHTLCInfo{
routing: PendingHTLCRouting::Forward {
onion_packet: OnionPacket{
version: 0,
public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
hop_data: [0; 20*65],
hmac: [0; 32],
},
short_channel_id: 0,
},
incoming_shared_secret: [0; 32],
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
incoming_amt_msat: Some(4_000_000),
outgoing_amt_msat: 4_000_000,
outgoing_cltv_value: 10000,
skimmed_fee_msat: None,
})),
});
chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
htlc_id: 4,
amount_msat: 16_000_000,
cltv_expiry: 504,
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
state: OutboundHTLCState::Committed,
source: HTLCSource::OutboundRoute {
path: Path { hops: Vec::new(), blinded_tail: None },
session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
first_hop_htlc_msat: 0,
payment_id: PaymentId([5; 32]),
},
skimmed_fee_msat: None,
});
chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
htlc_id: 5,
amount_msat: 32_000_000,
cltv_expiry: 505,
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(None)),
source: HTLCSource::OutboundRoute {
path: Path { hops: Vec::new(), blinded_tail: None },
session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
first_hop_htlc_msat: 0,
payment_id: PaymentId([5; 32]),
},
skimmed_fee_msat: None,
});
chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
htlc_id: 6,
amount_msat: 64_000_000,
cltv_expiry: 506,
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
state: OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(HTLCFailReason::from_failure_code(0x4000 | 8))),
source: HTLCSource::OutboundRoute {
path: Path { hops: Vec::new(), blinded_tail: None },
session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
first_hop_htlc_msat: 0,
payment_id: PaymentId([5; 32]),
},
skimmed_fee_msat: None,
});
chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
htlc_id: 7,
amount_msat: 128_000_000,
cltv_expiry: 507,
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
state: OutboundHTLCState::LocalAnnounced(Box::new(OnionPacket{
version: 0,
public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
hop_data: [0; 20*65],
hmac: [0; 32],
})),
source: HTLCSource::OutboundRoute {
path: Path { hops: Vec::new(), blinded_tail: None },
session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
first_hop_htlc_msat: 0,
payment_id: PaymentId([5; 32]),
},
skimmed_fee_msat: None,
});
chan.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::AddHTLC {
amount_msat: 256_000_000,
payment_hash: PaymentHash(Sha256::hash(&[3; 32]).into_inner()),
cltv_expiry: 506,
source: HTLCSource::OutboundRoute {
path: Path { hops: Vec::new(), blinded_tail: None },
session_priv: SecretKey::from_slice(&[4; 32]).unwrap(),
first_hop_htlc_msat: 0,
payment_id: PaymentId([5; 32]),
},
onion_routing_packet: OnionPacket{
version: 0,
public_key: Ok(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())),
hop_data: [0; 20*65],
hmac: [0; 32],
},
skimmed_fee_msat: None,
});

let pending_inbound_total_msat: u64 = chan.context.get_pending_inbound_htlc_details().iter().map(|details| details.amount_msat).sum();
let pending_outbound_total_msat: u64 = chan.context.get_pending_outbound_htlc_details().iter().map(|details| details.amount_msat).sum();
let balances = chan.context.get_available_balances(&LowerBoundedFeeEstimator::new(&fee_est));

assert_eq!(
chan.context.channel_value_satoshis * 1000,
pending_inbound_total_msat + pending_outbound_total_msat +
balances.inbound_capacity_msat + balances.outbound_capacity_msat +
chan.context.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000 +
chan.context.holder_selected_channel_reserve_satoshis * 1000
);
}
}
21 changes: 20 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa
// Since this struct is returned in `list_channels` methods, expose it here in case users want to
// construct one themselves.
use crate::ln::{inbound_payment, PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, HTLCDetails};
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
#[cfg(any(feature = "_test_utils", test))]
use crate::ln::features::Bolt11InvoiceFeatures;
Expand Down Expand Up @@ -124,6 +124,9 @@ pub(super) enum PendingHTLCRouting {
pub(super) struct PendingHTLCInfo {
pub(super) routing: PendingHTLCRouting,
pub(super) incoming_shared_secret: [u8; 32],
#[cfg(test)]
pub(super) payment_hash: PaymentHash,
#[cfg(not(test))]
payment_hash: PaymentHash,
/// Amount received
pub(super) incoming_amt_msat: Option<u64>, // Added in 0.0.113
Expand Down Expand Up @@ -1505,6 +1508,14 @@ pub struct ChannelDetails {
///
/// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109.
pub config: Option<ChannelConfig>,
/// Pending inbound HTLCs.
///
/// This field is empty for objects serialized with LDK versions prior to 0.0.117.
pub pending_inbound_htlcs: Vec<HTLCDetails>,
/// Pending outbound HTLCs.
///
/// This field is empty for objects serialized with LDK versions prior to 0.0.117.
pub pending_outbound_htlcs: Vec<HTLCDetails>,
}

impl ChannelDetails {
Expand Down Expand Up @@ -1580,6 +1591,8 @@ impl ChannelDetails {
inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(),
config: Some(context.config()),
channel_shutdown_state: Some(context.shutdown_state()),
pending_inbound_htlcs: context.get_pending_inbound_htlc_details(),
pending_outbound_htlcs: context.get_pending_outbound_htlc_details(),
}
}
}
Expand Down Expand Up @@ -7528,6 +7541,8 @@ impl Writeable for ChannelDetails {
(37, user_channel_id_high_opt, option),
(39, self.feerate_sat_per_1000_weight, option),
(41, self.channel_shutdown_state, option),
(43, self.pending_inbound_htlcs, optional_vec),
(45, self.pending_outbound_htlcs, optional_vec),
});
Ok(())
}
Expand Down Expand Up @@ -7566,6 +7581,8 @@ impl Readable for ChannelDetails {
(37, user_channel_id_high_opt, option),
(39, feerate_sat_per_1000_weight, option),
(41, channel_shutdown_state, option),
(43, pending_inbound_htlcs, optional_vec),
(45, pending_outbound_htlcs, optional_vec),
});

// `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with
Expand Down Expand Up @@ -7602,6 +7619,8 @@ impl Readable for ChannelDetails {
inbound_htlc_maximum_msat,
feerate_sat_per_1000_weight,
channel_shutdown_state,
pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()),
pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()),
})
}
}
Expand Down
4 changes: 4 additions & 0 deletions lightning/src/routing/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,8 @@ mod tests {
config: None,
feerate_sat_per_1000_weight: None,
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
pending_inbound_htlcs: Vec::new(),
pending_outbound_htlcs: Vec::new(),
}
}

Expand Down Expand Up @@ -6811,6 +6813,8 @@ pub(crate) mod bench_utils {
config: None,
feerate_sat_per_1000_weight: None,
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
pending_inbound_htlcs: Vec::new(),
pending_outbound_htlcs: Vec::new(),
}
}

Expand Down

0 comments on commit dbcd317

Please sign in to comment.