use alloc::vec::Vec;
use bitcoin::secp256k1::PublicKey;
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator};
use crate::chain::transaction::OutPoint;
use crate::ln::channel::Channel;
use crate::ln::types::ChannelId;
use crate::sign::SignerProvider;
use crate::types::features::{ChannelTypeFeatures, InitFeatures};
use crate::types::payment::PaymentHash;
use crate::util::config::ChannelConfig;
use core::ops::Deref;
#[derive(Clone, Debug, PartialEq)]
pub enum InboundHTLCStateDetails {
AwaitingRemoteRevokeToAdd,
Committed,
AwaitingRemoteRevokeToRemoveFulfill,
AwaitingRemoteRevokeToRemoveFail,
}
impl_writeable_tlv_based_enum_upgradable!(InboundHTLCStateDetails,
(0, AwaitingRemoteRevokeToAdd) => {},
(2, Committed) => {},
(4, AwaitingRemoteRevokeToRemoveFulfill) => {},
(6, AwaitingRemoteRevokeToRemoveFail) => {},
);
#[derive(Clone, Debug, PartialEq)]
pub struct InboundHTLCDetails {
pub htlc_id: u64,
pub amount_msat: u64,
pub cltv_expiry: u32,
pub payment_hash: PaymentHash,
pub state: Option<InboundHTLCStateDetails>,
pub is_dust: bool,
}
impl_writeable_tlv_based!(InboundHTLCDetails, {
(0, htlc_id, required),
(2, amount_msat, required),
(4, cltv_expiry, required),
(6, payment_hash, required),
(7, state, upgradable_option),
(8, is_dust, required),
});
#[derive(Clone, Debug, PartialEq)]
pub enum OutboundHTLCStateDetails {
AwaitingRemoteRevokeToAdd,
Committed,
AwaitingRemoteRevokeToRemoveSuccess,
AwaitingRemoteRevokeToRemoveFailure,
}
impl_writeable_tlv_based_enum_upgradable!(OutboundHTLCStateDetails,
(0, AwaitingRemoteRevokeToAdd) => {},
(2, Committed) => {},
(4, AwaitingRemoteRevokeToRemoveSuccess) => {},
(6, AwaitingRemoteRevokeToRemoveFailure) => {},
);
#[derive(Clone, Debug, PartialEq)]
pub struct OutboundHTLCDetails {
pub htlc_id: Option<u64>,
pub amount_msat: u64,
pub cltv_expiry: u32,
pub payment_hash: PaymentHash,
pub state: Option<OutboundHTLCStateDetails>,
pub skimmed_fee_msat: Option<u64>,
pub is_dust: bool,
}
impl_writeable_tlv_based!(OutboundHTLCDetails, {
(0, htlc_id, required),
(2, amount_msat, required),
(4, cltv_expiry, required),
(6, payment_hash, required),
(7, state, upgradable_option),
(8, skimmed_fee_msat, required),
(10, is_dust, required),
});
#[derive(Clone, Debug, PartialEq)]
pub struct CounterpartyForwardingInfo {
pub fee_base_msat: u32,
pub fee_proportional_millionths: u32,
pub cltv_expiry_delta: u16,
}
impl_writeable_tlv_based!(CounterpartyForwardingInfo, {
(2, fee_base_msat, required),
(4, fee_proportional_millionths, required),
(6, cltv_expiry_delta, required),
});
#[derive(Clone, Debug, PartialEq)]
pub struct ChannelCounterparty {
pub node_id: PublicKey,
pub features: InitFeatures,
pub unspendable_punishment_reserve: u64,
pub forwarding_info: Option<CounterpartyForwardingInfo>,
pub outbound_htlc_minimum_msat: Option<u64>,
pub outbound_htlc_maximum_msat: Option<u64>,
}
impl_writeable_tlv_based!(ChannelCounterparty, {
(2, node_id, required),
(4, features, required),
(6, unspendable_punishment_reserve, required),
(8, forwarding_info, option),
(9, outbound_htlc_minimum_msat, option),
(11, outbound_htlc_maximum_msat, option),
});
#[derive(Clone, Debug, PartialEq)]
pub struct ChannelDetails {
pub channel_id: ChannelId,
pub counterparty: ChannelCounterparty,
pub funding_txo: Option<OutPoint>,
pub channel_type: Option<ChannelTypeFeatures>,
pub short_channel_id: Option<u64>,
pub outbound_scid_alias: Option<u64>,
pub inbound_scid_alias: Option<u64>,
pub channel_value_satoshis: u64,
pub unspendable_punishment_reserve: Option<u64>,
pub user_channel_id: u128,
pub feerate_sat_per_1000_weight: Option<u32>,
pub outbound_capacity_msat: u64,
pub next_outbound_htlc_limit_msat: u64,
pub next_outbound_htlc_minimum_msat: u64,
pub inbound_capacity_msat: u64,
pub confirmations_required: Option<u32>,
pub confirmations: Option<u32>,
pub force_close_spend_delay: Option<u16>,
pub is_outbound: bool,
pub is_channel_ready: bool,
pub channel_shutdown_state: Option<ChannelShutdownState>,
pub is_usable: bool,
pub is_announced: bool,
pub inbound_htlc_minimum_msat: Option<u64>,
pub inbound_htlc_maximum_msat: Option<u64>,
pub config: Option<ChannelConfig>,
pub pending_inbound_htlcs: Vec<InboundHTLCDetails>,
pub pending_outbound_htlcs: Vec<OutboundHTLCDetails>,
pub funding_redeem_script: Option<bitcoin::ScriptBuf>,
}
impl ChannelDetails {
pub fn get_inbound_payment_scid(&self) -> Option<u64> {
self.inbound_scid_alias.or(self.short_channel_id)
}
pub fn get_outbound_payment_scid(&self) -> Option<u64> {
self.short_channel_id.or(self.outbound_scid_alias)
}
pub fn get_funding_output(&self) -> Option<bitcoin::TxOut> {
match self.funding_redeem_script.as_ref() {
None => None,
Some(redeem_script) => Some(bitcoin::TxOut {
value: bitcoin::Amount::from_sat(self.channel_value_satoshis),
script_pubkey: redeem_script.to_p2wsh(),
}),
}
}
pub(super) fn from_channel<SP: Deref, F: Deref>(
channel: &Channel<SP>, best_block_height: u32, latest_features: InitFeatures,
fee_estimator: &LowerBoundedFeeEstimator<F>,
) -> Self
where
SP::Target: SignerProvider,
F::Target: FeeEstimator,
{
let context = channel.context();
let funding = channel.funding();
let balance = channel.get_available_balances(fee_estimator);
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =
funding.get_holder_counterparty_selected_channel_reserve_satoshis();
#[allow(deprecated)] ChannelDetails {
channel_id: context.channel_id(),
counterparty: ChannelCounterparty {
node_id: context.get_counterparty_node_id(),
features: latest_features,
unspendable_punishment_reserve: to_remote_reserve_satoshis,
forwarding_info: context.counterparty_forwarding_info(),
outbound_htlc_minimum_msat: if context.have_received_message() {
Some(context.get_counterparty_htlc_minimum_msat())
} else {
None
},
outbound_htlc_maximum_msat: context.get_counterparty_htlc_maximum_msat(funding),
},
funding_txo: funding.get_funding_txo(),
funding_redeem_script: funding
.channel_transaction_parameters
.make_funding_redeemscript_opt(),
channel_type: if context.have_received_message() {
Some(funding.get_channel_type().clone())
} else {
None
},
short_channel_id: funding.get_short_channel_id(),
outbound_scid_alias: if context.is_usable() {
Some(context.outbound_scid_alias())
} else {
None
},
inbound_scid_alias: context.latest_inbound_scid_alias(),
channel_value_satoshis: funding.get_value_satoshis(),
feerate_sat_per_1000_weight: Some(context.get_feerate_sat_per_1000_weight()),
unspendable_punishment_reserve: to_self_reserve_satoshis,
inbound_capacity_msat: balance.inbound_capacity_msat,
outbound_capacity_msat: balance.outbound_capacity_msat,
next_outbound_htlc_limit_msat: balance.next_outbound_htlc_limit_msat,
next_outbound_htlc_minimum_msat: balance.next_outbound_htlc_minimum_msat,
user_channel_id: context.get_user_id(),
confirmations_required: channel.minimum_depth(),
confirmations: Some(funding.get_funding_tx_confirmations(best_block_height)),
force_close_spend_delay: funding.get_counterparty_selected_contest_delay(),
is_outbound: funding.is_outbound(),
is_channel_ready: context.is_usable(),
is_usable: context.is_live(),
is_announced: context.should_announce(),
inbound_htlc_minimum_msat: Some(context.get_holder_htlc_minimum_msat()),
inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(funding),
config: Some(context.config()),
channel_shutdown_state: Some(context.shutdown_state()),
pending_inbound_htlcs: context.get_pending_inbound_htlc_details(funding),
pending_outbound_htlcs: context.get_pending_outbound_htlc_details(funding),
}
}
}
impl_writeable_tlv_based!(ChannelDetails, {
(1, inbound_scid_alias, option),
(2, channel_id, required),
(3, channel_type, option),
(4, counterparty, required),
(5, outbound_scid_alias, option),
(6, funding_txo, option),
(7, config, option),
(8, short_channel_id, option),
(9, confirmations, option),
(10, channel_value_satoshis, required),
(12, unspendable_punishment_reserve, option),
(14, _user_channel_id_low, (legacy, u64,
|us: &ChannelDetails| Some(us.user_channel_id as u64))),
(16, _balance_msat, (legacy, u64, |us: &ChannelDetails| Some(us.next_outbound_htlc_limit_msat))),
(18, outbound_capacity_msat, required),
(19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat)),
(20, inbound_capacity_msat, required),
(21, next_outbound_htlc_minimum_msat, (default_value, 0)),
(22, confirmations_required, option),
(24, force_close_spend_delay, option),
(26, is_outbound, required),
(28, is_channel_ready, required),
(30, is_usable, required),
(32, is_announced, required),
(33, inbound_htlc_minimum_msat, option),
(35, inbound_htlc_maximum_msat, option),
(37, _user_channel_id_high, (legacy, u64,
|us: &ChannelDetails| Some((us.user_channel_id >> 64) as u64))),
(39, feerate_sat_per_1000_weight, option),
(41, channel_shutdown_state, option),
(43, pending_inbound_htlcs, optional_vec),
(45, pending_outbound_htlcs, optional_vec),
(47, funding_redeem_script, option),
(_unused, user_channel_id, (static_value,
_user_channel_id_low.unwrap_or(0) as u128 | ((_user_channel_id_high.unwrap_or(0) as u128) << 64)
)),
});
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelShutdownState {
NotShuttingDown,
ShutdownInitiated,
ResolvingHTLCs,
NegotiatingClosingFee,
ShutdownComplete,
}
impl_writeable_tlv_based_enum!(ChannelShutdownState,
(0, NotShuttingDown) => {},
(2, ShutdownInitiated) => {},
(4, ResolvingHTLCs) => {},
(6, NegotiatingClosingFee) => {},
(8, ShutdownComplete) => {},
);
#[cfg(test)]
mod tests {
use bitcoin::{hashes::Hash as _, secp256k1::PublicKey};
use lightning_types::features::Features;
use types::payment::PaymentHash;
use crate::{
chain::transaction::OutPoint,
ln::{
chan_utils::make_funding_redeemscript,
channel_state::{
InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails,
OutboundHTLCStateDetails,
},
types::ChannelId,
},
util::{
config::ChannelConfig,
ser::{Readable, Writeable},
},
};
use super::{ChannelCounterparty, ChannelDetails, ChannelShutdownState};
#[test]
fn test_channel_details_serialization() {
#[allow(deprecated)]
let channel_details = ChannelDetails {
channel_id: ChannelId::new_zero(),
counterparty: ChannelCounterparty {
features: Features::empty(),
node_id: PublicKey::from_slice(&[2; 33]).unwrap(),
unspendable_punishment_reserve: 1983,
forwarding_info: None,
outbound_htlc_minimum_msat: None,
outbound_htlc_maximum_msat: None,
},
funding_txo: Some(OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
index: 1,
}),
funding_redeem_script: Some(make_funding_redeemscript(
&PublicKey::from_slice(&[2; 33]).unwrap(),
&PublicKey::from_slice(&[2; 33]).unwrap(),
)),
channel_type: None,
short_channel_id: None,
outbound_scid_alias: None,
inbound_scid_alias: None,
channel_value_satoshis: 50_100,
user_channel_id: (u64::MAX as u128) + 1, outbound_capacity_msat: 24_300,
next_outbound_htlc_limit_msat: 20_000,
next_outbound_htlc_minimum_msat: 132,
inbound_capacity_msat: 42,
unspendable_punishment_reserve: Some(8273),
confirmations_required: Some(5),
confirmations: Some(73),
force_close_spend_delay: Some(10),
is_outbound: true,
is_channel_ready: false,
is_usable: true,
is_announced: false,
inbound_htlc_minimum_msat: Some(98),
inbound_htlc_maximum_msat: Some(983274),
config: Some(ChannelConfig::default()),
feerate_sat_per_1000_weight: Some(212),
channel_shutdown_state: Some(ChannelShutdownState::NotShuttingDown),
pending_inbound_htlcs: vec![InboundHTLCDetails {
htlc_id: 12,
amount_msat: 333,
cltv_expiry: 127,
payment_hash: PaymentHash([3; 32]),
state: Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToAdd),
is_dust: true,
}],
pending_outbound_htlcs: vec![OutboundHTLCDetails {
htlc_id: Some(81),
amount_msat: 5000,
cltv_expiry: 129,
payment_hash: PaymentHash([4; 32]),
state: Some(OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd),
skimmed_fee_msat: Some(42),
is_dust: false,
}],
};
let mut buffer = Vec::new();
channel_details.write(&mut buffer).unwrap();
let deser_channel_details = ChannelDetails::read(&mut buffer.as_slice()).unwrap();
assert_eq!(deser_channel_details, channel_details);
}
}