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 21, 2023
1 parent 8a8f29a commit 5c48cfa
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 3 deletions.
206 changes: 204 additions & 2 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, {
(1, htlc_id, option),
(2, amount_msat, required),
(4, cltv_expiry, required),
(6, payment_hash, required),
(7, skimmed_fee_msat, option),
(8, 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 @@ -1571,6 +1590,71 @@ impl<Signer: ChannelSigner> ChannelContext<Signer> {
stats
}

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() {
// if let InboundHTLCState::LocalRemoved(_) = htlc.state {
// continue;
// }
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
}

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 Down Expand Up @@ -7415,10 +7499,10 @@ mod tests {
use bitcoin::blockdata::opcodes;
use bitcoin::network::constants::Network;
use hex;
use crate::ln::PaymentHash;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
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};
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};
Expand Down Expand Up @@ -8893,4 +8977,122 @@ 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 mut 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 mut 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(&[2; 32]).into_inner()),
state: InboundHTLCState::Committed,
});
chan.context.pending_inbound_htlcs.push(InboundHTLCOutput{
htlc_id: 1,
amount_msat: 2_000_000,
cltv_expiry: 501,
payment_hash: PaymentHash(Sha256::hash(&[2; 32]).into_inner()),
state: InboundHTLCState::RemoteAnnounced(PendingHTLCStatus::Forward(PendingHTLCInfo{
routing: PendingHTLCRouting::Forward {
onion_packet: msgs::OnionPacket{
version: 0,
public_key: Ok(PublicKey()),
hop_data: [0; 20*65],
hmac: [0; 32],
},
short_channel_id: 0,
},
incoming_shared_secret: [0; 32],
payment_hash: PaymentHash(Sha256::hash(&[2; 32]).into_inner()),
incoming_amt_msat: Some(2_000_000),
outgoing_amt_msat: Some(2_000_000),
outgoing_cltv_value: 10000,
skimmed_fee_msat: None,
})),
});
chan.context.pending_outbound_htlcs.push(OutboundHTLCOutput{
htlc_id: 2,
amount_msat: 4_000_000,
cltv_expiry: 502,
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: 2,
amount_msat: 4_000_000,
cltv_expiry: 502,
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: 2,
amount_msat: 4_000_000,
cltv_expiry: 502,
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,
});

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,
balances.inbound_capacity_msat + balances.outbound_capacity_msat + pending_inbound_total_msat + pending_outbound_total_msat +
chan.context.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000 + chan.context.holder_selected_channel_reserve_satoshis * 1000,
);
}
}
14 changes: 13 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, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, 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 @@ -1505,6 +1505,10 @@ pub struct ChannelDetails {
///
/// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109.
pub config: Option<ChannelConfig>,
/// The pending inbound HTLC's.
pub pending_inbound_htlcs: Vec<HTLCDetails>,
/// The pending outbound HTLC's.
pub pending_outbound_htlcs: Vec<HTLCDetails>,
}

impl ChannelDetails {
Expand Down Expand Up @@ -1580,6 +1584,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 @@ -7477,6 +7483,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 @@ -7515,6 +7523,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 @@ -7551,6 +7561,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

0 comments on commit 5c48cfa

Please sign in to comment.