use bitcoin::amount::Amount;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
use bitcoin::hash_types::Txid;
use bitcoin::locktime::absolute::LockTime;
use bitcoin::script::{Script, ScriptBuf};
use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::sighash::EcdsaSighashType;
use bitcoin::transaction::OutPoint as BitcoinOutPoint;
use bitcoin::transaction::Version;
use bitcoin::transaction::{Transaction, TxIn, TxOut};
use bitcoin::{Sequence, Witness};
use crate::chain::chaininterface::{
compute_feerate_sat_per_1000_weight, ConfirmationTarget, FeeEstimator,
FEERATE_FLOOR_SATS_PER_KW, INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT,
};
use crate::chain::channelmonitor::COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE;
use crate::chain::onchaintx::{FeerateStrategy, OnchainTxHandler};
use crate::chain::transaction::MaybeSignedTransaction;
use crate::ln::chan_utils::{
self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction,
TxCreationKeys,
};
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint};
use crate::ln::channelmanager::MIN_CLTV_EXPIRY_DELTA;
use crate::ln::msgs::DecodeError;
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::sign::{ChannelDerivationParameters, HTLCDescriptor};
use crate::types::features::ChannelTypeFeatures;
use crate::types::payment::PaymentPreimage;
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, Writeable, Writer};
use crate::io;
use core::cmp;
use core::ops::Deref;
#[allow(unused_imports)]
use crate::prelude::*;
use super::chaininterface::LowerBoundedFeeEstimator;
const MAX_ALLOC_SIZE: usize = 64 * 1024;
#[rustfmt::skip]
pub(crate) fn weight_revoked_offered_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
const WEIGHT_REVOKED_OFFERED_HTLC: u64 = 1 + 1 + 73 + 1 + 33 + 1 + 133;
const WEIGHT_REVOKED_OFFERED_HTLC_ANCHORS: u64 = WEIGHT_REVOKED_OFFERED_HTLC + 3; if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_REVOKED_OFFERED_HTLC_ANCHORS } else { WEIGHT_REVOKED_OFFERED_HTLC }
}
#[rustfmt::skip]
pub(crate) fn weight_revoked_received_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
const WEIGHT_REVOKED_RECEIVED_HTLC: u64 = 1 + 1 + 73 + 1 + 33 + 1 + 139;
const WEIGHT_REVOKED_RECEIVED_HTLC_ANCHORS: u64 = WEIGHT_REVOKED_RECEIVED_HTLC + 3; if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_REVOKED_RECEIVED_HTLC_ANCHORS } else { WEIGHT_REVOKED_RECEIVED_HTLC }
}
#[rustfmt::skip]
pub(crate) fn weight_offered_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
const WEIGHT_OFFERED_HTLC: u64 = 1 + 1 + 73 + 1 + 32 + 1 + 133;
const WEIGHT_OFFERED_HTLC_ANCHORS: u64 = WEIGHT_OFFERED_HTLC + 3; if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_OFFERED_HTLC_ANCHORS } else { WEIGHT_OFFERED_HTLC }
}
#[rustfmt::skip]
pub(crate) fn weight_received_htlc(channel_type_features: &ChannelTypeFeatures) -> u64 {
const WEIGHT_RECEIVED_HTLC: u64 = 1 + 1 + 73 + 1 + 1 + 1 + 139;
const WEIGHT_RECEIVED_HTLC_ANCHORS: u64 = WEIGHT_RECEIVED_HTLC + 3; if channel_type_features.supports_anchors_zero_fee_htlc_tx() { WEIGHT_RECEIVED_HTLC_ANCHORS } else { WEIGHT_RECEIVED_HTLC }
}
#[rustfmt::skip]
pub(crate) fn verify_channel_type_features(channel_type_features: &Option<ChannelTypeFeatures>, additional_permitted_features: Option<&ChannelTypeFeatures>) -> Result<(), DecodeError> {
if let Some(features) = channel_type_features.as_ref() {
if features.requires_unknown_bits() {
return Err(DecodeError::UnknownRequiredFeature);
}
let mut supported_feature_set = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
supported_feature_set.set_scid_privacy_required();
supported_feature_set.set_zero_conf_required();
supported_feature_set.set_anchor_zero_fee_commitments_required();
if let Some(additional_permitted_features) = additional_permitted_features {
supported_feature_set |= additional_permitted_features;
}
if features.requires_unknown_bits_from(&supported_feature_set) {
return Err(DecodeError::UnknownRequiredFeature);
}
}
Ok(())
}
pub(crate) const WEIGHT_REVOKED_OUTPUT: u64 = 1 + 1 + 73 + 1 + 1 + 1 + 77;
#[cfg(not(any(test, feature = "_test_utils")))]
const LOW_FREQUENCY_BUMP_INTERVAL: u32 = 15;
#[cfg(any(test, feature = "_test_utils"))]
pub(crate) const LOW_FREQUENCY_BUMP_INTERVAL: u32 = 15;
const MIDDLE_FREQUENCY_BUMP_INTERVAL: u32 = 3;
const HIGH_FREQUENCY_BUMP_INTERVAL: u32 = 1;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct RevokedOutput {
per_commitment_point: PublicKey,
counterparty_delayed_payment_base_key: DelayedPaymentBasepoint,
counterparty_htlc_base_key: HtlcBasepoint,
per_commitment_key: SecretKey,
weight: u64,
amount: Amount,
on_counterparty_tx_csv: u16,
channel_parameters: Option<ChannelTransactionParameters>,
outpoint_confirmation_height: Option<u32>,
}
impl RevokedOutput {
#[rustfmt::skip]
pub(crate) fn build(
per_commitment_point: PublicKey, per_commitment_key: SecretKey, amount: Amount,
channel_parameters: ChannelTransactionParameters,
outpoint_confirmation_height: u32,
) -> Self {
let directed_params = channel_parameters.as_counterparty_broadcastable();
let counterparty_keys = directed_params.broadcaster_pubkeys();
let counterparty_delayed_payment_base_key = counterparty_keys.delayed_payment_basepoint;
let counterparty_htlc_base_key = counterparty_keys.htlc_basepoint;
let on_counterparty_tx_csv = directed_params.contest_delay();
RevokedOutput {
per_commitment_point,
counterparty_delayed_payment_base_key,
counterparty_htlc_base_key,
per_commitment_key,
weight: WEIGHT_REVOKED_OUTPUT,
amount,
on_counterparty_tx_csv,
channel_parameters: Some(channel_parameters),
outpoint_confirmation_height: Some(outpoint_confirmation_height),
}
}
}
impl_writeable_tlv_based!(RevokedOutput, {
(0, per_commitment_point, required),
(1, outpoint_confirmation_height, option), (2, counterparty_delayed_payment_base_key, required),
(4, counterparty_htlc_base_key, required),
(6, per_commitment_key, required),
(8, weight, required),
(10, amount, required),
(12, on_counterparty_tx_csv, required),
(14, is_counterparty_balance_on_anchors, (legacy, (), |_| Some(()))),
(15, channel_parameters, (option: ReadableArgs, None)), });
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct RevokedHTLCOutput {
per_commitment_point: PublicKey,
counterparty_delayed_payment_base_key: DelayedPaymentBasepoint,
counterparty_htlc_base_key: HtlcBasepoint,
per_commitment_key: SecretKey,
weight: u64,
amount: u64,
htlc: HTLCOutputInCommitment,
channel_parameters: Option<ChannelTransactionParameters>,
outpoint_confirmation_height: Option<u32>,
}
impl RevokedHTLCOutput {
pub(crate) fn build(
per_commitment_point: PublicKey, per_commitment_key: SecretKey,
htlc: HTLCOutputInCommitment, channel_parameters: ChannelTransactionParameters,
outpoint_confirmation_height: u32,
) -> Self {
let weight = if htlc.offered {
weight_revoked_offered_htlc(&channel_parameters.channel_type_features)
} else {
weight_revoked_received_htlc(&channel_parameters.channel_type_features)
};
let directed_params = channel_parameters.as_counterparty_broadcastable();
let counterparty_keys = directed_params.broadcaster_pubkeys();
let counterparty_delayed_payment_base_key = counterparty_keys.delayed_payment_basepoint;
let counterparty_htlc_base_key = counterparty_keys.htlc_basepoint;
RevokedHTLCOutput {
per_commitment_point,
counterparty_delayed_payment_base_key,
counterparty_htlc_base_key,
per_commitment_key,
weight,
amount: htlc.amount_msat / 1000,
htlc,
channel_parameters: Some(channel_parameters),
outpoint_confirmation_height: Some(outpoint_confirmation_height),
}
}
}
impl_writeable_tlv_based!(RevokedHTLCOutput, {
(0, per_commitment_point, required),
(1, outpoint_confirmation_height, option), (2, counterparty_delayed_payment_base_key, required),
(4, counterparty_htlc_base_key, required),
(6, per_commitment_key, required),
(8, weight, required),
(10, amount, required),
(12, htlc, required),
(13, channel_parameters, (option: ReadableArgs, None)), });
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CounterpartyOfferedHTLCOutput {
per_commitment_point: PublicKey,
counterparty_delayed_payment_base_key: DelayedPaymentBasepoint,
counterparty_htlc_base_key: HtlcBasepoint,
preimage: PaymentPreimage,
htlc: HTLCOutputInCommitment,
channel_type_features: ChannelTypeFeatures,
channel_parameters: Option<ChannelTransactionParameters>,
outpoint_confirmation_height: Option<u32>,
}
impl CounterpartyOfferedHTLCOutput {
pub(crate) fn build(
per_commitment_point: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment,
channel_parameters: ChannelTransactionParameters,
outpoint_confirmation_height: Option<u32>,
) -> Self {
let directed_params = channel_parameters.as_counterparty_broadcastable();
let counterparty_keys = directed_params.broadcaster_pubkeys();
let counterparty_delayed_payment_base_key = counterparty_keys.delayed_payment_basepoint;
let counterparty_htlc_base_key = counterparty_keys.htlc_basepoint;
let channel_type_features = channel_parameters.channel_type_features.clone();
CounterpartyOfferedHTLCOutput {
per_commitment_point,
counterparty_delayed_payment_base_key,
counterparty_htlc_base_key,
preimage,
htlc,
channel_type_features,
channel_parameters: Some(channel_parameters),
outpoint_confirmation_height,
}
}
}
impl Writeable for CounterpartyOfferedHTLCOutput {
#[rustfmt::skip]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
write_tlv_fields!(writer, {
(0, self.per_commitment_point, required),
(1, self.outpoint_confirmation_height, option), (2, self.counterparty_delayed_payment_base_key, required),
(4, self.counterparty_htlc_base_key, required),
(6, self.preimage, required),
(8, self.htlc, required),
(10, legacy_deserialization_prevention_marker, option),
(11, self.channel_type_features, required),
(13, self.channel_parameters, option), });
Ok(())
}
}
impl Readable for CounterpartyOfferedHTLCOutput {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let mut per_commitment_point = RequiredWrapper(None);
let mut counterparty_delayed_payment_base_key = RequiredWrapper(None);
let mut counterparty_htlc_base_key = RequiredWrapper(None);
let mut preimage = RequiredWrapper(None);
let mut htlc = RequiredWrapper(None);
let mut _legacy_deserialization_prevention_marker: Option<()> = None;
let mut channel_type_features = None;
let mut channel_parameters = None;
let mut outpoint_confirmation_height = None;
read_tlv_fields!(reader, {
(0, per_commitment_point, required),
(1, outpoint_confirmation_height, option), (2, counterparty_delayed_payment_base_key, required),
(4, counterparty_htlc_base_key, required),
(6, preimage, required),
(8, htlc, required),
(10, _legacy_deserialization_prevention_marker, option),
(11, channel_type_features, option),
(13, channel_parameters, (option: ReadableArgs, None)), });
verify_channel_type_features(&channel_type_features, None)?;
let channel_type_features =
channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key());
Ok(Self {
per_commitment_point: per_commitment_point.0.unwrap(),
counterparty_delayed_payment_base_key: counterparty_delayed_payment_base_key.0.unwrap(),
counterparty_htlc_base_key: counterparty_htlc_base_key.0.unwrap(),
preimage: preimage.0.unwrap(),
htlc: htlc.0.unwrap(),
channel_type_features,
channel_parameters,
outpoint_confirmation_height,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CounterpartyReceivedHTLCOutput {
per_commitment_point: PublicKey,
counterparty_delayed_payment_base_key: DelayedPaymentBasepoint,
counterparty_htlc_base_key: HtlcBasepoint,
htlc: HTLCOutputInCommitment,
channel_type_features: ChannelTypeFeatures,
channel_parameters: Option<ChannelTransactionParameters>,
outpoint_confirmation_height: Option<u32>,
}
impl CounterpartyReceivedHTLCOutput {
pub(crate) fn build(
per_commitment_point: PublicKey, htlc: HTLCOutputInCommitment,
channel_parameters: ChannelTransactionParameters,
outpoint_confirmation_height: Option<u32>,
) -> Self {
let directed_params = channel_parameters.as_counterparty_broadcastable();
let counterparty_keys = directed_params.broadcaster_pubkeys();
let counterparty_delayed_payment_base_key = counterparty_keys.delayed_payment_basepoint;
let counterparty_htlc_base_key = counterparty_keys.htlc_basepoint;
let channel_type_features = channel_parameters.channel_type_features.clone();
Self {
per_commitment_point,
counterparty_delayed_payment_base_key,
counterparty_htlc_base_key,
htlc,
channel_type_features,
channel_parameters: Some(channel_parameters),
outpoint_confirmation_height,
}
}
}
impl Writeable for CounterpartyReceivedHTLCOutput {
#[rustfmt::skip]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
write_tlv_fields!(writer, {
(0, self.per_commitment_point, required),
(1, self.outpoint_confirmation_height, option), (2, self.counterparty_delayed_payment_base_key, required),
(4, self.counterparty_htlc_base_key, required),
(6, self.htlc, required),
(8, legacy_deserialization_prevention_marker, option),
(9, self.channel_type_features, required),
(11, self.channel_parameters, option), });
Ok(())
}
}
impl Readable for CounterpartyReceivedHTLCOutput {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let mut per_commitment_point = RequiredWrapper(None);
let mut counterparty_delayed_payment_base_key = RequiredWrapper(None);
let mut counterparty_htlc_base_key = RequiredWrapper(None);
let mut htlc = RequiredWrapper(None);
let mut _legacy_deserialization_prevention_marker: Option<()> = None;
let mut channel_type_features = None;
let mut channel_parameters = None;
let mut outpoint_confirmation_height = None;
read_tlv_fields!(reader, {
(0, per_commitment_point, required),
(1, outpoint_confirmation_height, option), (2, counterparty_delayed_payment_base_key, required),
(4, counterparty_htlc_base_key, required),
(6, htlc, required),
(8, _legacy_deserialization_prevention_marker, option),
(9, channel_type_features, option),
(11, channel_parameters, (option: ReadableArgs, None)), });
verify_channel_type_features(&channel_type_features, None)?;
let channel_type_features =
channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key());
Ok(Self {
per_commitment_point: per_commitment_point.0.unwrap(),
counterparty_delayed_payment_base_key: counterparty_delayed_payment_base_key.0.unwrap(),
counterparty_htlc_base_key: counterparty_htlc_base_key.0.unwrap(),
htlc: htlc.0.unwrap(),
channel_type_features,
channel_parameters,
outpoint_confirmation_height,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct HolderHTLCOutput {
preimage: Option<PaymentPreimage>,
amount_msat: u64,
cltv_expiry: u32,
channel_type_features: ChannelTypeFeatures,
htlc_descriptor: Option<HTLCDescriptor>,
outpoint_confirmation_height: Option<u32>,
}
impl HolderHTLCOutput {
#[rustfmt::skip]
pub(crate) fn build(
htlc_descriptor: HTLCDescriptor, outpoint_confirmation_height: u32,
) -> Self {
let amount_msat = htlc_descriptor.htlc.amount_msat;
let channel_type_features = htlc_descriptor.channel_derivation_parameters
.transaction_parameters.channel_type_features.clone();
Self {
preimage: if htlc_descriptor.htlc.offered {
None
} else {
Some(htlc_descriptor.preimage.expect("Preimage required for accepted holder HTLC claim"))
},
amount_msat,
cltv_expiry: if htlc_descriptor.htlc.offered {
htlc_descriptor.htlc.cltv_expiry
} else {
0
},
channel_type_features,
htlc_descriptor: Some(htlc_descriptor),
outpoint_confirmation_height: Some(outpoint_confirmation_height),
}
}
#[rustfmt::skip]
pub(crate) fn get_htlc_descriptor<ChannelSigner: EcdsaChannelSigner>(
&self, onchain_tx_handler: &OnchainTxHandler<ChannelSigner>, outp: &::bitcoin::OutPoint,
) -> Option<HTLCDescriptor> {
if let Some(htlc_descriptor) = self.htlc_descriptor.as_ref() {
return Some(htlc_descriptor.clone());
}
let channel_parameters = onchain_tx_handler.channel_parameters();
let get_htlc_descriptor = |holder_commitment: &HolderCommitmentTransaction| {
let trusted_tx = holder_commitment.trust();
if outp.txid != trusted_tx.txid() {
return None;
}
let (htlc, counterparty_sig) =
trusted_tx.nondust_htlcs().iter().zip(holder_commitment.counterparty_htlc_sigs.iter())
.find(|(htlc, _)| htlc.transaction_output_index.unwrap() == outp.vout)
.unwrap();
Some(HTLCDescriptor {
channel_derivation_parameters: ChannelDerivationParameters {
value_satoshis: channel_parameters.channel_value_satoshis,
keys_id: onchain_tx_handler.channel_keys_id(),
transaction_parameters: channel_parameters.clone(),
},
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: trusted_tx.per_commitment_point(),
feerate_per_kw: trusted_tx.negotiated_feerate_per_kw(),
htlc: htlc.clone(),
preimage: self.preimage.clone(),
counterparty_sig: *counterparty_sig,
})
};
get_htlc_descriptor(onchain_tx_handler.current_holder_commitment_tx())
.or_else(|| onchain_tx_handler.prev_holder_commitment_tx().and_then(|c| get_htlc_descriptor(c)))
}
#[rustfmt::skip]
pub(crate) fn get_maybe_signed_htlc_tx<ChannelSigner: EcdsaChannelSigner>(
&self, onchain_tx_handler: &mut OnchainTxHandler<ChannelSigner>, outp: &::bitcoin::OutPoint,
) -> Option<MaybeSignedTransaction> {
let htlc_descriptor = self.get_htlc_descriptor(onchain_tx_handler, outp)?;
let channel_parameters = &htlc_descriptor.channel_derivation_parameters.transaction_parameters;
let directed_parameters = channel_parameters.as_holder_broadcastable();
let keys = TxCreationKeys::from_channel_static_keys(
&htlc_descriptor.per_commitment_point, directed_parameters.broadcaster_pubkeys(),
directed_parameters.countersignatory_pubkeys(), &onchain_tx_handler.secp_ctx,
);
let mut htlc_tx = chan_utils::build_htlc_transaction(
&htlc_descriptor.commitment_txid, htlc_descriptor.feerate_per_kw,
directed_parameters.contest_delay(), &htlc_descriptor.htlc,
&channel_parameters.channel_type_features, &keys.broadcaster_delayed_payment_key,
&keys.revocation_key
);
if let Ok(htlc_sig) = onchain_tx_handler.signer.sign_holder_htlc_transaction(
&htlc_tx, 0, &htlc_descriptor, &onchain_tx_handler.secp_ctx,
) {
let htlc_redeem_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(
&htlc_descriptor.htlc, &channel_parameters.channel_type_features,
&keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key,
);
htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness(
&htlc_sig, &htlc_descriptor.counterparty_sig, &htlc_descriptor.preimage,
&htlc_redeem_script, &channel_parameters.channel_type_features,
);
}
Some(MaybeSignedTransaction(htlc_tx))
}
}
impl Writeable for HolderHTLCOutput {
#[rustfmt::skip]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
write_tlv_fields!(writer, {
(0, self.amount_msat, required),
(1, self.outpoint_confirmation_height, option), (2, self.cltv_expiry, required),
(4, self.preimage, option),
(6, legacy_deserialization_prevention_marker, option),
(7, self.channel_type_features, required),
(9, self.htlc_descriptor, option), });
Ok(())
}
}
impl Readable for HolderHTLCOutput {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let mut amount_msat = RequiredWrapper(None);
let mut cltv_expiry = RequiredWrapper(None);
let mut preimage = None;
let mut _legacy_deserialization_prevention_marker: Option<()> = None;
let mut channel_type_features = None;
let mut htlc_descriptor = None;
let mut outpoint_confirmation_height = None;
read_tlv_fields!(reader, {
(0, amount_msat, required),
(1, outpoint_confirmation_height, option), (2, cltv_expiry, required),
(4, preimage, option),
(6, _legacy_deserialization_prevention_marker, option),
(7, channel_type_features, option),
(9, htlc_descriptor, option), });
verify_channel_type_features(&channel_type_features, None)?;
let channel_type_features =
channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key());
Ok(Self {
amount_msat: amount_msat.0.unwrap(),
cltv_expiry: cltv_expiry.0.unwrap(),
preimage,
channel_type_features,
htlc_descriptor,
outpoint_confirmation_height,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct HolderFundingOutput {
funding_redeemscript: ScriptBuf,
pub(crate) funding_amount_sats: Option<u64>,
channel_type_features: ChannelTypeFeatures,
pub(crate) commitment_tx: Option<HolderCommitmentTransaction>,
pub(crate) channel_parameters: Option<ChannelTransactionParameters>,
}
impl HolderFundingOutput {
pub(crate) fn build(
commitment_tx: HolderCommitmentTransaction,
channel_parameters: ChannelTransactionParameters,
) -> Self {
let funding_redeemscript = channel_parameters.make_funding_redeemscript();
let funding_amount_sats = channel_parameters.channel_value_satoshis;
let channel_type_features = channel_parameters.channel_type_features.clone();
HolderFundingOutput {
funding_redeemscript,
funding_amount_sats: Some(funding_amount_sats),
channel_type_features,
commitment_tx: Some(commitment_tx),
channel_parameters: Some(channel_parameters),
}
}
#[rustfmt::skip]
pub(crate) fn get_maybe_signed_commitment_tx<Signer: EcdsaChannelSigner>(
&self, onchain_tx_handler: &mut OnchainTxHandler<Signer>,
) -> MaybeSignedTransaction {
let channel_parameters = self.channel_parameters.as_ref()
.unwrap_or(onchain_tx_handler.channel_parameters());
let commitment_tx = self.commitment_tx.as_ref()
.unwrap_or(onchain_tx_handler.current_holder_commitment_tx());
let maybe_signed_tx = onchain_tx_handler.signer
.sign_holder_commitment(channel_parameters, commitment_tx, &onchain_tx_handler.secp_ctx)
.map(|holder_sig| {
commitment_tx.add_holder_sig(&self.funding_redeemscript, holder_sig)
})
.unwrap_or_else(|_| {
commitment_tx.trust().built_transaction().transaction.clone()
});
MaybeSignedTransaction(maybe_signed_tx)
}
}
impl Writeable for HolderFundingOutput {
#[rustfmt::skip]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
write_tlv_fields!(writer, {
(0, self.funding_redeemscript, required),
(1, self.channel_type_features, required),
(2, legacy_deserialization_prevention_marker, option),
(3, self.funding_amount_sats, option),
(5, self.commitment_tx, option), (7, self.channel_parameters, option), });
Ok(())
}
}
impl Readable for HolderFundingOutput {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let mut funding_redeemscript = RequiredWrapper(None);
let mut _legacy_deserialization_prevention_marker: Option<()> = None;
let mut channel_type_features = None;
let mut funding_amount_sats = None;
let mut commitment_tx = None;
let mut channel_parameters = None;
read_tlv_fields!(reader, {
(0, funding_redeemscript, required),
(1, channel_type_features, option),
(2, _legacy_deserialization_prevention_marker, option),
(3, funding_amount_sats, option),
(5, commitment_tx, option), (7, channel_parameters, (option: ReadableArgs, None)), });
verify_channel_type_features(&channel_type_features, None)?;
let channel_type_features =
channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key());
Ok(Self {
funding_redeemscript: funding_redeemscript.0.unwrap(),
funding_amount_sats,
channel_type_features,
commitment_tx,
channel_parameters,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum PackageSolvingData {
RevokedOutput(RevokedOutput),
RevokedHTLCOutput(RevokedHTLCOutput),
CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput),
CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput),
HolderHTLCOutput(HolderHTLCOutput),
HolderFundingOutput(HolderFundingOutput),
}
impl PackageSolvingData {
#[rustfmt::skip]
fn amount(&self) -> u64 {
let amt = match self {
PackageSolvingData::RevokedOutput(ref outp) => outp.amount.to_sat(),
PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.amount,
PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000,
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000,
PackageSolvingData::HolderHTLCOutput(ref outp) => {
let free_htlcs = outp.channel_type_features.supports_anchors_zero_fee_htlc_tx();
let free_commitments =
outp.channel_type_features.supports_anchor_zero_fee_commitments();
debug_assert!(free_htlcs || free_commitments);
outp.amount_msat / 1000
},
PackageSolvingData::HolderFundingOutput(ref outp) => {
let free_htlcs = outp.channel_type_features.supports_anchors_zero_fee_htlc_tx();
let free_commitments =
outp.channel_type_features.supports_anchor_zero_fee_commitments();
debug_assert!(free_htlcs || free_commitments);
outp.funding_amount_sats.unwrap()
}
};
amt
}
#[rustfmt::skip]
fn weight(&self) -> usize {
match self {
PackageSolvingData::RevokedOutput(ref outp) => outp.weight as usize,
PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.weight as usize,
PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => weight_offered_htlc(&outp.channel_type_features) as usize,
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => weight_received_htlc(&outp.channel_type_features) as usize,
PackageSolvingData::HolderHTLCOutput(ref outp) => {
let free_htlcs = outp.channel_type_features.supports_anchors_zero_fee_htlc_tx();
let free_commitments =
outp.channel_type_features.supports_anchor_zero_fee_commitments();
debug_assert!(free_htlcs || free_commitments);
if outp.preimage.is_none() {
weight_offered_htlc(&outp.channel_type_features) as usize
} else {
weight_received_htlc(&outp.channel_type_features) as usize
}
},
PackageSolvingData::HolderFundingOutput(..) => unreachable!(),
}
}
#[rustfmt::skip]
fn is_possibly_from_same_tx_tree(&self, other: &PackageSolvingData) -> bool {
match self {
PackageSolvingData::RevokedOutput(_)|PackageSolvingData::RevokedHTLCOutput(_) => {
match other {
PackageSolvingData::RevokedOutput(_)|
PackageSolvingData::RevokedHTLCOutput(_) => true,
_ => false,
}
},
PackageSolvingData::CounterpartyOfferedHTLCOutput(_)|
PackageSolvingData::CounterpartyReceivedHTLCOutput(_) => {
match other {
PackageSolvingData::CounterpartyOfferedHTLCOutput(_)|
PackageSolvingData::CounterpartyReceivedHTLCOutput(_) => true,
_ => false,
}
},
PackageSolvingData::HolderHTLCOutput(_)|
PackageSolvingData::HolderFundingOutput(_) => {
match other {
PackageSolvingData::HolderHTLCOutput(_)|
PackageSolvingData::HolderFundingOutput(_) => true,
_ => false,
}
},
}
}
fn input_confirmation_height(&self) -> Option<u32> {
match self {
PackageSolvingData::RevokedOutput(RevokedOutput {
outpoint_confirmation_height,
..
})
| PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput {
outpoint_confirmation_height,
..
})
| PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput {
outpoint_confirmation_height,
..
})
| PackageSolvingData::CounterpartyReceivedHTLCOutput(
CounterpartyReceivedHTLCOutput { outpoint_confirmation_height, .. },
)
| PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput {
outpoint_confirmation_height,
..
}) => *outpoint_confirmation_height,
PackageSolvingData::HolderFundingOutput(_) => None,
}
}
#[rustfmt::skip]
fn as_tx_input(&self, previous_output: BitcoinOutPoint) -> TxIn {
let sequence = match self {
PackageSolvingData::RevokedOutput(_) => Sequence::ENABLE_RBF_NO_LOCKTIME,
PackageSolvingData::RevokedHTLCOutput(_) => Sequence::ENABLE_RBF_NO_LOCKTIME,
PackageSolvingData::CounterpartyOfferedHTLCOutput(outp) => if outp.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
Sequence::from_consensus(1)
} else {
Sequence::ENABLE_RBF_NO_LOCKTIME
},
PackageSolvingData::CounterpartyReceivedHTLCOutput(outp) => if outp.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
Sequence::from_consensus(1)
} else {
Sequence::ENABLE_RBF_NO_LOCKTIME
},
_ => {
debug_assert!(false, "This should not be reachable by 'untractable' or 'malleable with external funding' packages");
Sequence::ENABLE_RBF_NO_LOCKTIME
},
};
TxIn {
previous_output,
script_sig: ScriptBuf::new(),
sequence,
witness: Witness::new(),
}
}
#[rustfmt::skip]
fn finalize_input<Signer: EcdsaChannelSigner>(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler<Signer>) -> bool {
let channel_parameters = onchain_handler.channel_parameters();
match self {
PackageSolvingData::RevokedOutput(ref outp) => {
let channel_parameters = outp.channel_parameters.as_ref().unwrap_or(channel_parameters);
let directed_parameters = channel_parameters.as_counterparty_broadcastable();
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint,
outp.counterparty_delayed_payment_base_key,
);
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().htlc_basepoint,
outp.counterparty_htlc_base_key,
);
let chan_keys = TxCreationKeys::from_channel_static_keys(
&outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(),
directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx,
);
let witness_script = chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, outp.on_counterparty_tx_csv, &chan_keys.broadcaster_delayed_payment_key);
if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_output(channel_parameters, &bumped_tx, i, outp.amount.to_sat(), &outp.per_commitment_key, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
ser_sig.push(EcdsaSighashType::All as u8);
bumped_tx.input[i].witness.push(ser_sig);
bumped_tx.input[i].witness.push(vec!(1));
bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
} else { return false; }
},
PackageSolvingData::RevokedHTLCOutput(ref outp) => {
let channel_parameters = outp.channel_parameters.as_ref().unwrap_or(channel_parameters);
let directed_parameters = channel_parameters.as_counterparty_broadcastable();
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint,
outp.counterparty_delayed_payment_base_key,
);
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().htlc_basepoint,
outp.counterparty_htlc_base_key,
);
let chan_keys = TxCreationKeys::from_channel_static_keys(
&outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(),
directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx,
);
let witness_script = chan_utils::get_htlc_redeemscript(
&outp.htlc, &channel_parameters.channel_type_features, &chan_keys
);
if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_htlc(channel_parameters, &bumped_tx, i, outp.amount, &outp.per_commitment_key, &outp.htlc, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
ser_sig.push(EcdsaSighashType::All as u8);
bumped_tx.input[i].witness.push(ser_sig);
bumped_tx.input[i].witness.push(chan_keys.revocation_key.to_public_key().serialize().to_vec());
bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
} else { return false; }
},
PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => {
let channel_parameters = outp.channel_parameters.as_ref().unwrap_or(channel_parameters);
let directed_parameters = channel_parameters.as_counterparty_broadcastable();
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint,
outp.counterparty_delayed_payment_base_key,
);
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().htlc_basepoint,
outp.counterparty_htlc_base_key,
);
let chan_keys = TxCreationKeys::from_channel_static_keys(
&outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(),
directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx,
);
let witness_script = chan_utils::get_htlc_redeemscript(
&outp.htlc, &channel_parameters.channel_type_features, &chan_keys,
);
if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(channel_parameters, &bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
ser_sig.push(EcdsaSighashType::All as u8);
bumped_tx.input[i].witness.push(ser_sig);
bumped_tx.input[i].witness.push(outp.preimage.0.to_vec());
bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
}
},
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => {
let channel_parameters = outp.channel_parameters.as_ref().unwrap_or(channel_parameters);
let directed_parameters = channel_parameters.as_counterparty_broadcastable();
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint,
outp.counterparty_delayed_payment_base_key,
);
debug_assert_eq!(
directed_parameters.broadcaster_pubkeys().htlc_basepoint,
outp.counterparty_htlc_base_key,
);
let chan_keys = TxCreationKeys::from_channel_static_keys(
&outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(),
directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx,
);
let witness_script = chan_utils::get_htlc_redeemscript(
&outp.htlc, &channel_parameters.channel_type_features, &chan_keys,
);
if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(channel_parameters, &bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) {
let mut ser_sig = sig.serialize_der().to_vec();
ser_sig.push(EcdsaSighashType::All as u8);
bumped_tx.input[i].witness.push(ser_sig);
bumped_tx.input[i].witness.push(vec![]);
bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
}
},
_ => { panic!("API Error!"); }
}
true
}
#[rustfmt::skip]
fn get_maybe_finalized_tx<Signer: EcdsaChannelSigner>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<MaybeSignedTransaction> {
match self {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
debug_assert!(!outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
debug_assert!(!outp.channel_type_features.supports_anchor_zero_fee_commitments());
outp.get_maybe_signed_htlc_tx(onchain_handler, outpoint)
}
PackageSolvingData::HolderFundingOutput(ref outp) => {
Some(outp.get_maybe_signed_commitment_tx(onchain_handler))
}
_ => { panic!("API Error!"); }
}
}
#[rustfmt::skip]
fn minimum_locktime(&self) -> Option<u32> {
match self {
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => Some(outp.htlc.cltv_expiry),
_ => None,
}
}
fn signed_locktime(&self) -> Option<u32> {
match self {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
if outp.preimage.is_some() {
debug_assert_eq!(outp.cltv_expiry, 0);
}
Some(outp.cltv_expiry)
},
_ => None,
}
}
#[rustfmt::skip]
fn map_output_type_flags(&self) -> PackageMalleability {
match self {
PackageSolvingData::RevokedOutput(RevokedOutput { .. }) =>
PackageMalleability::Malleable(AggregationCluster::Unpinnable),
PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput { htlc, .. }) => {
if htlc.offered {
PackageMalleability::Malleable(AggregationCluster::Unpinnable)
} else {
PackageMalleability::Malleable(AggregationCluster::Pinnable)
}
},
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) =>
PackageMalleability::Malleable(AggregationCluster::Unpinnable),
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) =>
PackageMalleability::Malleable(AggregationCluster::Pinnable),
PackageSolvingData::HolderHTLCOutput(ref outp) => {
let free_htlcs = outp.channel_type_features.supports_anchors_zero_fee_htlc_tx();
let free_commits = outp.channel_type_features.supports_anchor_zero_fee_commitments();
if free_htlcs || free_commits {
if outp.preimage.is_some() {
PackageMalleability::Malleable(AggregationCluster::Unpinnable)
} else {
PackageMalleability::Malleable(AggregationCluster::Pinnable)
}
} else {
PackageMalleability::Untractable
}
},
PackageSolvingData::HolderFundingOutput(..) => PackageMalleability::Untractable,
}
}
}
impl_writeable_tlv_based_enum_legacy!(PackageSolvingData, ;
(0, RevokedOutput),
(1, RevokedHTLCOutput),
(2, CounterpartyOfferedHTLCOutput),
(3, CounterpartyReceivedHTLCOutput),
(4, HolderHTLCOutput),
(5, HolderFundingOutput),
);
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum AggregationCluster {
Pinnable,
Unpinnable,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum PackageMalleability {
Malleable(AggregationCluster),
Untractable,
}
#[derive(Clone, Debug, Eq)]
pub struct PackageTemplate {
inputs: Vec<(BitcoinOutPoint, PackageSolvingData)>,
malleability: PackageMalleability,
counterparty_spendable_height: u32,
feerate_previous: u64,
height_timer: u32,
}
impl PartialEq for PackageTemplate {
fn eq(&self, o: &Self) -> bool {
if self.inputs != o.inputs
|| self.malleability != o.malleability
|| self.feerate_previous != o.feerate_previous
|| self.height_timer != o.height_timer
{
return false;
}
#[cfg(test)]
{
if self.counterparty_spendable_height == 0 {
for (_, input) in self.inputs.iter() {
if let PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput {
htlc, ..
}) = input
{
if !htlc.offered && htlc.cltv_expiry != 0 {
return true;
}
}
}
}
if o.counterparty_spendable_height == 0 {
for (_, input) in o.inputs.iter() {
if let PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput {
htlc, ..
}) = input
{
if !htlc.offered && htlc.cltv_expiry != 0 {
return true;
}
}
}
}
}
self.counterparty_spendable_height == o.counterparty_spendable_height
}
}
impl PackageTemplate {
#[rustfmt::skip]
pub(crate) fn can_merge_with(&self, other: &PackageTemplate, cur_height: u32) -> bool {
match (self.malleability, other.malleability) {
(PackageMalleability::Untractable, _) => false,
(_, PackageMalleability::Untractable) => false,
(PackageMalleability::Malleable(self_cluster), PackageMalleability::Malleable(other_cluster)) => {
if self.inputs.is_empty() {
return false;
}
if other.inputs.is_empty() {
return false;
}
#[cfg(debug_assertions)]
{
for i in 0..self.inputs.len() {
for j in 0..i {
debug_assert!(self.inputs[i].1.is_possibly_from_same_tx_tree(&self.inputs[j].1));
}
}
for i in 0..other.inputs.len() {
for j in 0..i {
assert!(other.inputs[i].1.is_possibly_from_same_tx_tree(&other.inputs[j].1));
}
}
}
if !self.inputs[0].1.is_possibly_from_same_tx_tree(&other.inputs[0].1) {
debug_assert!(false, "We shouldn't have packages from different tx trees");
return false;
}
if self.signed_locktime() != other.signed_locktime() {
return false;
}
if self.package_locktime(cur_height) != other.package_locktime(cur_height) {
return false;
}
let self_pinnable = self_cluster == AggregationCluster::Pinnable ||
self.counterparty_spendable_height <= cur_height + COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE;
let other_pinnable = other_cluster == AggregationCluster::Pinnable ||
other.counterparty_spendable_height <= cur_height + COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE;
if self_pinnable && other_pinnable {
return true;
}
let self_unpinnable = self_cluster == AggregationCluster::Unpinnable &&
self.counterparty_spendable_height > cur_height + COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE;
let other_unpinnable = other_cluster == AggregationCluster::Unpinnable &&
other.counterparty_spendable_height > cur_height + COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE;
if self_unpinnable && other_unpinnable {
return true;
}
false
},
}
}
pub(crate) fn is_malleable(&self) -> bool {
matches!(self.malleability, PackageMalleability::Malleable(..))
}
pub(crate) fn previous_feerate(&self) -> u64 {
self.feerate_previous
}
pub(crate) fn set_feerate(&mut self, new_feerate: u64) {
self.feerate_previous = new_feerate;
}
pub(crate) fn timer(&self) -> u32 {
self.height_timer
}
pub(crate) fn set_timer(&mut self, new_timer: u32) {
self.height_timer = new_timer;
}
pub(crate) fn outpoints(&self) -> Vec<&BitcoinOutPoint> {
self.inputs.iter().map(|(o, _)| o).collect()
}
pub(crate) fn outpoints_and_creation_heights(
&self,
) -> impl Iterator<Item = (&BitcoinOutPoint, Option<u32>)> {
self.inputs.iter().map(|(o, p)| (o, p.input_confirmation_height()))
}
pub(crate) fn inputs(&self) -> impl ExactSizeIterator<Item = &PackageSolvingData> {
self.inputs.iter().map(|(_, i)| i)
}
#[rustfmt::skip]
pub(crate) fn split_package(&mut self, split_outp: &BitcoinOutPoint) -> Option<PackageTemplate> {
match self.malleability {
PackageMalleability::Malleable(cluster) => {
let mut split_package = None;
let feerate_previous = self.feerate_previous;
let height_timer = self.height_timer;
self.inputs.retain(|outp| {
if *split_outp == outp.0 {
split_package = Some(PackageTemplate {
inputs: vec![(outp.0, outp.1.clone())],
malleability: PackageMalleability::Malleable(cluster),
counterparty_spendable_height: self.counterparty_spendable_height,
feerate_previous,
height_timer,
});
return false;
}
return true;
});
return split_package;
},
_ => {
return None;
}
}
}
pub(crate) fn merge_package(
&mut self, mut merge_from: PackageTemplate, cur_height: u32,
) -> Result<(), PackageTemplate> {
if !self.can_merge_with(&merge_from, cur_height) {
return Err(merge_from);
}
for (k, v) in merge_from.inputs.drain(..) {
self.inputs.push((k, v));
}
if self.counterparty_spendable_height > merge_from.counterparty_spendable_height {
self.counterparty_spendable_height = merge_from.counterparty_spendable_height;
}
if self.feerate_previous > merge_from.feerate_previous {
self.feerate_previous = merge_from.feerate_previous;
}
self.height_timer = cmp::min(self.height_timer, merge_from.height_timer);
Ok(())
}
pub(crate) fn package_amount(&self) -> u64 {
let mut amounts = 0;
for (_, outp) in self.inputs.iter() {
amounts += outp.amount();
}
amounts
}
#[rustfmt::skip]
fn signed_locktime(&self) -> Option<u32> {
let signed_locktime = self.inputs.iter().find_map(|(_, outp)| outp.signed_locktime());
#[cfg(debug_assertions)]
for (_, outp) in &self.inputs {
debug_assert!(outp.signed_locktime().is_none() || outp.signed_locktime() == signed_locktime);
}
signed_locktime
}
#[rustfmt::skip]
pub(crate) fn package_locktime(&self, current_height: u32) -> u32 {
let minimum_locktime = self.inputs.iter().filter_map(|(_, outp)| outp.minimum_locktime()).max();
if let Some(signed_locktime) = self.signed_locktime() {
debug_assert!(minimum_locktime.is_none());
signed_locktime
} else {
core::cmp::max(current_height, minimum_locktime.unwrap_or(0))
}
}
pub(crate) fn package_weight(&self, destination_script: &Script) -> u64 {
let mut inputs_weight = 0;
let mut witnesses_weight = 2; for (_, outp) in self.inputs.iter() {
inputs_weight += 41 * WITNESS_SCALE_FACTOR;
witnesses_weight += outp.weight();
}
let transaction_weight = 10 * WITNESS_SCALE_FACTOR;
let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR;
(inputs_weight + witnesses_weight + transaction_weight + output_weight) as u64
}
#[rustfmt::skip]
pub(crate) fn construct_malleable_package_with_external_funding<Signer: EcdsaChannelSigner>(
&self, onchain_handler: &mut OnchainTxHandler<Signer>,
) -> Option<Vec<HTLCDescriptor>> {
debug_assert!(self.requires_external_funding());
let mut htlcs: Option<Vec<HTLCDescriptor>> = None;
for (previous_output, input) in &self.inputs {
match input {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
let free_htlcs = outp.channel_type_features.supports_anchors_zero_fee_htlc_tx();
let free_commitments =
outp.channel_type_features.supports_anchor_zero_fee_commitments();
debug_assert!(free_htlcs || free_commitments);
outp.get_htlc_descriptor(onchain_handler, &previous_output).map(|htlc| {
htlcs.get_or_insert_with(|| Vec::with_capacity(self.inputs.len())).push(htlc);
});
}
_ => debug_assert!(false, "Expected HolderHTLCOutputs to not be aggregated with other input types"),
}
}
htlcs
}
#[rustfmt::skip]
pub(crate) fn maybe_finalize_malleable_package<L: Logger, Signer: EcdsaChannelSigner>(
&self, current_height: u32, onchain_handler: &mut OnchainTxHandler<Signer>, value: Amount,
destination_script: ScriptBuf, logger: &L
) -> Option<MaybeSignedTransaction> {
debug_assert!(self.is_malleable());
let mut bumped_tx = Transaction {
version: Version::TWO,
lock_time: LockTime::from_consensus(self.package_locktime(current_height)),
input: vec![],
output: vec![TxOut {
script_pubkey: destination_script,
value,
}],
};
for (outpoint, outp) in self.inputs.iter() {
bumped_tx.input.push(outp.as_tx_input(*outpoint));
}
for (i, (outpoint, out)) in self.inputs.iter().enumerate() {
log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout);
if !out.finalize_input(&mut bumped_tx, i, onchain_handler) { continue; }
}
Some(MaybeSignedTransaction(bumped_tx))
}
#[rustfmt::skip]
pub(crate) fn maybe_finalize_untractable_package<L: Logger, Signer: EcdsaChannelSigner>(
&self, onchain_handler: &mut OnchainTxHandler<Signer>, logger: &L,
) -> Option<MaybeSignedTransaction> {
debug_assert!(!self.is_malleable());
if let Some((outpoint, outp)) = self.inputs.first() {
if let Some(final_tx) = outp.get_maybe_finalized_tx(outpoint, onchain_handler) {
log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout);
return Some(final_tx);
}
return None;
} else { panic!("API Error: Package must not be inputs empty"); }
}
#[rustfmt::skip]
pub(crate) fn get_height_timer(&self, current_height: u32) -> u32 {
let mut height_timer = current_height + LOW_FREQUENCY_BUMP_INTERVAL;
let timer_for_target_conf = |target_conf| -> u32 {
if target_conf <= current_height + MIDDLE_FREQUENCY_BUMP_INTERVAL {
current_height + HIGH_FREQUENCY_BUMP_INTERVAL
} else if target_conf <= current_height + LOW_FREQUENCY_BUMP_INTERVAL {
current_height + MIDDLE_FREQUENCY_BUMP_INTERVAL
} else {
current_height + LOW_FREQUENCY_BUMP_INTERVAL
}
};
for (_, input) in self.inputs.iter() {
match input {
PackageSolvingData::RevokedOutput(_) => {
height_timer = cmp::min(
height_timer,
timer_for_target_conf(self.counterparty_spendable_height),
);
},
PackageSolvingData::RevokedHTLCOutput(_) => {
},
PackageSolvingData::CounterpartyOfferedHTLCOutput(outp) => {
height_timer = cmp::min(
height_timer,
timer_for_target_conf(outp.htlc.cltv_expiry),
);
},
PackageSolvingData::HolderHTLCOutput(outp) if outp.preimage.is_some() => {
height_timer = cmp::min(
height_timer,
timer_for_target_conf(self.counterparty_spendable_height),
);
},
PackageSolvingData::CounterpartyReceivedHTLCOutput(outp) => {
height_timer = cmp::min(
height_timer,
timer_for_target_conf(outp.htlc.cltv_expiry + MIN_CLTV_EXPIRY_DELTA as u32),
);
},
PackageSolvingData::HolderHTLCOutput(outp) => {
height_timer = cmp::min(
height_timer,
timer_for_target_conf(outp.cltv_expiry + MIN_CLTV_EXPIRY_DELTA as u32),
);
},
PackageSolvingData::HolderFundingOutput(_) => {
height_timer =
cmp::min(height_timer, current_height + HIGH_FREQUENCY_BUMP_INTERVAL);
},
}
}
height_timer
}
#[rustfmt::skip]
pub(crate) fn compute_package_output<F: Deref, L: Logger>(
&self, predicted_weight: u64, dust_limit_sats: u64, feerate_strategy: &FeerateStrategy,
conf_target: ConfirmationTarget, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
) -> Option<(u64, u64)>
where F::Target: FeeEstimator,
{
debug_assert!(matches!(self.malleability, PackageMalleability::Malleable(..)),
"The package output is fixed for non-malleable packages");
let input_amounts = self.package_amount();
assert!(dust_limit_sats as i64 > 0, "Output script must be broadcastable/have a 'real' dust limit.");
if self.feerate_previous != 0 {
if let Some((new_fee, feerate)) = feerate_bump(
predicted_weight, input_amounts, dust_limit_sats, self.feerate_previous,
feerate_strategy, conf_target, fee_estimator, logger,
) {
return Some((cmp::max(input_amounts.saturating_sub(new_fee), dust_limit_sats), feerate));
}
} else {
if let Some((new_fee, feerate)) = compute_fee_from_spent_amounts(input_amounts, predicted_weight, conf_target, fee_estimator, logger) {
return Some((cmp::max(input_amounts.saturating_sub(new_fee), dust_limit_sats), feerate));
}
}
None
}
#[rustfmt::skip]
pub(crate) fn compute_package_feerate<F: Deref>(
&self, fee_estimator: &LowerBoundedFeeEstimator<F>, conf_target: ConfirmationTarget,
feerate_strategy: &FeerateStrategy,
) -> u32 where F::Target: FeeEstimator {
let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
if self.feerate_previous != 0 {
let previous_feerate = self.feerate_previous.try_into().unwrap_or(u32::max_value());
match feerate_strategy {
FeerateStrategy::RetryPrevious => previous_feerate,
FeerateStrategy::HighestOfPreviousOrNew => cmp::max(previous_feerate, feerate_estimate),
FeerateStrategy::ForceBump => if feerate_estimate > previous_feerate {
feerate_estimate
} else {
let previous_feerate = self.feerate_previous.try_into().unwrap_or(u32::max_value());
let mut new_feerate = previous_feerate.saturating_add(previous_feerate / 4);
if new_feerate > feerate_estimate * 5 {
new_feerate = cmp::max(feerate_estimate * 5, previous_feerate);
}
new_feerate
},
}
} else {
feerate_estimate
}
}
#[rustfmt::skip]
pub(crate) fn requires_external_funding(&self) -> bool {
self.inputs.iter().find(|input| match input.1 {
PackageSolvingData::HolderFundingOutput(ref outp) => {
outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()
|| outp.channel_type_features.supports_anchor_zero_fee_commitments()
},
PackageSolvingData::HolderHTLCOutput(ref outp) => {
outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()
|| outp.channel_type_features.supports_anchor_zero_fee_commitments()
},
_ => false,
}).is_some()
}
pub(crate) fn build_package(
txid: Txid, vout: u32, input_solving_data: PackageSolvingData,
counterparty_spendable_height: u32,
) -> Self {
let malleability = PackageSolvingData::map_output_type_flags(&input_solving_data);
let inputs = vec![(BitcoinOutPoint { txid, vout }, input_solving_data)];
PackageTemplate {
inputs,
malleability,
counterparty_spendable_height,
feerate_previous: 0,
height_timer: 0,
}
}
}
impl Writeable for PackageTemplate {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
writer.write_all(&(self.inputs.len() as u64).to_be_bytes())?;
for (ref outpoint, ref rev_outp) in self.inputs.iter() {
outpoint.write(writer)?;
rev_outp.write(writer)?;
}
write_tlv_fields!(writer, {
(0, self.counterparty_spendable_height, required),
(2, self.feerate_previous, required),
(4, 0u32, required),
(6, self.height_timer, required)
});
Ok(())
}
}
impl Readable for PackageTemplate {
#[rustfmt::skip]
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let inputs_count = <u64 as Readable>::read(reader)?;
let mut inputs: Vec<(BitcoinOutPoint, PackageSolvingData)> = Vec::with_capacity(cmp::min(inputs_count as usize, MAX_ALLOC_SIZE / 128));
for _ in 0..inputs_count {
let outpoint = Readable::read(reader)?;
let rev_outp = Readable::read(reader)?;
inputs.push((outpoint, rev_outp));
}
let malleability = if let Some((_, lead_input)) = inputs.first() {
PackageSolvingData::map_output_type_flags(&lead_input)
} else { return Err(DecodeError::InvalidValue); };
let mut counterparty_spendable_height = 0;
let mut feerate_previous = 0;
let mut height_timer = None;
let mut _height_original: Option<u32> = None;
read_tlv_fields!(reader, {
(0, counterparty_spendable_height, required),
(2, feerate_previous, required),
(4, _height_original, option), (6, height_timer, option),
});
for (_, input) in &inputs {
if let PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput { htlc, .. }) = input {
if !htlc.offered && htlc.cltv_expiry == counterparty_spendable_height {
counterparty_spendable_height = 0;
}
}
}
Ok(PackageTemplate {
inputs,
malleability,
counterparty_spendable_height,
feerate_previous,
height_timer: height_timer.unwrap_or(0),
})
}
}
#[rustfmt::skip]
fn compute_fee_from_spent_amounts<F: Deref, L: Logger>(
input_amounts: u64, predicted_weight: u64, conf_target: ConfirmationTarget, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
) -> Option<(u64, u64)>
where F::Target: FeeEstimator,
{
let sweep_feerate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
let fee_rate = cmp::min(sweep_feerate, compute_feerate_sat_per_1000_weight(input_amounts / 2, predicted_weight));
let fee = fee_rate as u64 * (predicted_weight) / 1000;
if fee_rate < FEERATE_FLOOR_SATS_PER_KW {
log_error!(logger, "Failed to generate an on-chain tx with fee ({} sat/kw) was less than the floor ({} sat/kw)",
fee_rate, FEERATE_FLOOR_SATS_PER_KW);
None
} else {
Some((fee, fee_rate as u64))
}
}
#[rustfmt::skip]
fn feerate_bump<F: Deref, L: Logger>(
predicted_weight: u64, input_amounts: u64, dust_limit_sats: u64, previous_feerate: u64,
feerate_strategy: &FeerateStrategy, conf_target: ConfirmationTarget,
fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
) -> Option<(u64, u64)>
where
F::Target: FeeEstimator,
{
let previous_fee = previous_feerate * predicted_weight / 1000;
let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) =
compute_fee_from_spent_amounts(input_amounts, predicted_weight, conf_target, fee_estimator, logger)
{
log_debug!(logger, "Initiating fee rate bump from {} s/kWU ({} s) to {} s/kWU ({} s) using {:?} strategy", previous_feerate, previous_fee, new_feerate, new_fee, feerate_strategy);
match feerate_strategy {
FeerateStrategy::RetryPrevious => {
let previous_fee = previous_feerate * predicted_weight / 1000;
(previous_fee, previous_feerate)
},
FeerateStrategy::HighestOfPreviousOrNew => if new_feerate > previous_feerate {
(new_fee, new_feerate)
} else {
let previous_fee = previous_feerate * predicted_weight / 1000;
(previous_fee, previous_feerate)
},
FeerateStrategy::ForceBump => if new_feerate > previous_feerate {
(new_fee, new_feerate)
} else {
let bumped_feerate = previous_feerate + (previous_feerate / 4);
let bumped_fee = bumped_feerate * predicted_weight / 1000;
(bumped_fee, bumped_feerate)
},
}
} else {
log_warn!(logger, "Can't bump new claiming tx, input amount {} is too small", input_amounts);
return None;
};
debug_assert!(new_feerate >= previous_feerate);
if new_feerate == previous_feerate {
return Some((new_fee, new_feerate));
}
let min_relay_fee = INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT * predicted_weight / 1000;
let naive_new_fee = new_fee;
let new_fee = cmp::max(new_fee, previous_fee + min_relay_fee);
if new_fee > naive_new_fee {
log_debug!(logger, "Naive fee bump of {}s does not meet min relay fee requirements of {}s", naive_new_fee - previous_fee, min_relay_fee);
}
let remaining_output_amount = input_amounts.saturating_sub(new_fee);
if remaining_output_amount < dust_limit_sats {
log_warn!(logger, "Can't bump new claiming tx, output amount {} would end up below dust threshold {}", remaining_output_amount, dust_limit_sats);
return None;
}
let new_feerate = new_fee * 1000 / predicted_weight;
log_debug!(logger, "Fee rate bumped by {}s from {} s/KWU ({} s) to {} s/KWU ({} s)", new_fee - previous_fee, previous_feerate, previous_fee, new_feerate, new_fee);
Some((new_fee, new_feerate))
}
#[cfg(test)]
mod tests {
use crate::chain::package::{
feerate_bump, weight_offered_htlc, weight_received_htlc, CounterpartyOfferedHTLCOutput,
CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageSolvingData,
PackageTemplate, RevokedHTLCOutput, RevokedOutput, WEIGHT_REVOKED_OUTPUT,
};
use crate::chain::Txid;
use crate::ln::chan_utils::{
ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction,
};
use crate::sign::{ChannelDerivationParameters, HTLCDescriptor};
use crate::types::payment::{PaymentHash, PaymentPreimage};
use bitcoin::absolute::LockTime;
use bitcoin::amount::Amount;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
use bitcoin::script::ScriptBuf;
use bitcoin::transaction::OutPoint as BitcoinOutPoint;
use bitcoin::transaction::Version;
use bitcoin::{Transaction, TxOut};
use bitcoin::hex::FromHex;
use crate::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};
use crate::chain::onchaintx::FeerateStrategy;
use crate::types::features::ChannelTypeFeatures;
use crate::util::test_utils::TestLogger;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1::{PublicKey, SecretKey};
#[rustfmt::skip]
fn fake_txid(n: u64) -> Txid {
Transaction {
version: Version(0),
lock_time: LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: Amount::from_sat(n),
script_pubkey: ScriptBuf::new(),
}],
}.compute_txid()
}
#[rustfmt::skip]
macro_rules! dumb_revk_output {
() => {
{
let secp_ctx = Secp256k1::new();
let dumb_scalar = SecretKey::from_slice(&<Vec<u8>>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar);
let channel_parameters = ChannelTransactionParameters::test_dummy(0);
PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, dumb_scalar, Amount::ZERO, channel_parameters, 0))
}
}
}
#[rustfmt::skip]
macro_rules! dumb_revk_htlc_output {
() => {
{
let secp_ctx = Secp256k1::new();
let dumb_scalar = SecretKey::from_slice(&<Vec<u8>>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar);
let hash = PaymentHash([1; 32]);
let htlc = HTLCOutputInCommitment { offered: false, amount_msat: 1_000_000, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None };
let mut channel_parameters = ChannelTransactionParameters::test_dummy(0);
channel_parameters.channel_type_features =
ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput::build(
dumb_point, dumb_scalar, htlc, channel_parameters, 0,
))
}
}
}
#[rustfmt::skip]
macro_rules! dumb_counterparty_received_output {
($amt: expr, $expiry: expr, $features: expr) => {
{
let secp_ctx = Secp256k1::new();
let dumb_scalar = SecretKey::from_slice(&<Vec<u8>>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar);
let hash = PaymentHash([1; 32]);
let htlc = HTLCOutputInCommitment { offered: true, amount_msat: $amt, cltv_expiry: $expiry, payment_hash: hash, transaction_output_index: None };
let mut channel_parameters = ChannelTransactionParameters::test_dummy(0);
channel_parameters.channel_type_features = $features;
PackageSolvingData::CounterpartyReceivedHTLCOutput(
CounterpartyReceivedHTLCOutput::build(dumb_point, htlc, channel_parameters, None)
)
}
}
}
#[rustfmt::skip]
macro_rules! dumb_counterparty_offered_output {
($amt: expr, $features: expr) => {
{
let secp_ctx = Secp256k1::new();
let dumb_scalar = SecretKey::from_slice(&<Vec<u8>>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar);
let hash = PaymentHash([1; 32]);
let preimage = PaymentPreimage([2;32]);
let htlc = HTLCOutputInCommitment { offered: false, amount_msat: $amt, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None };
let mut channel_parameters = ChannelTransactionParameters::test_dummy(0);
channel_parameters.channel_type_features = $features;
PackageSolvingData::CounterpartyOfferedHTLCOutput(
CounterpartyOfferedHTLCOutput::build(dumb_point, preimage, htlc, channel_parameters, None)
)
}
}
}
#[rustfmt::skip]
macro_rules! dumb_accepted_htlc_output {
($features: expr) => {
{
let mut channel_parameters = ChannelTransactionParameters::test_dummy(0);
channel_parameters.channel_type_features = $features;
let preimage = PaymentPreimage([2;32]);
let htlc = HTLCOutputInCommitment {
offered: false,
amount_msat: 1337000,
cltv_expiry: 420,
payment_hash: PaymentHash::from(preimage),
transaction_output_index: None,
};
let funding_outpoint = channel_parameters.funding_outpoint.unwrap();
let commitment_tx = HolderCommitmentTransaction::dummy(0, funding_outpoint, vec![htlc.clone()]);
let trusted_tx = commitment_tx.trust();
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build(
HTLCDescriptor {
channel_derivation_parameters: ChannelDerivationParameters {
value_satoshis: channel_parameters.channel_value_satoshis,
keys_id: [0; 32],
transaction_parameters: channel_parameters,
},
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: trusted_tx.per_commitment_point(),
feerate_per_kw: trusted_tx.negotiated_feerate_per_kw(),
htlc,
preimage: Some(preimage),
counterparty_sig: commitment_tx.counterparty_htlc_sigs[0].clone(),
},
0,
))
}
}
}
#[rustfmt::skip]
macro_rules! dumb_offered_htlc_output {
($cltv_expiry: expr, $features: expr) => {
{
let mut channel_parameters = ChannelTransactionParameters::test_dummy(0);
channel_parameters.channel_type_features = $features;
let htlc = HTLCOutputInCommitment {
offered: true,
amount_msat: 1337000,
cltv_expiry: $cltv_expiry,
payment_hash: PaymentHash::from(PaymentPreimage([2;32])),
transaction_output_index: None,
};
let funding_outpoint = channel_parameters.funding_outpoint.unwrap();
let commitment_tx = HolderCommitmentTransaction::dummy(0, funding_outpoint, vec![htlc.clone()]);
let trusted_tx = commitment_tx.trust();
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build(
HTLCDescriptor {
channel_derivation_parameters: ChannelDerivationParameters {
value_satoshis: channel_parameters.channel_value_satoshis,
keys_id: [0; 32],
transaction_parameters: channel_parameters,
},
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: trusted_tx.per_commitment_point(),
feerate_per_kw: trusted_tx.negotiated_feerate_per_kw(),
htlc,
preimage: None,
counterparty_sig: commitment_tx.counterparty_htlc_sigs[0].clone(),
},
0,
))
}
}
}
#[rustfmt::skip]
macro_rules! dumb_funding_output {
() => {{
let mut channel_parameters = ChannelTransactionParameters::test_dummy(0);
let funding_outpoint = channel_parameters.funding_outpoint.unwrap();
let commitment_tx = HolderCommitmentTransaction::dummy(0, funding_outpoint, Vec::new());
channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build(
commitment_tx, channel_parameters,
))
}}
}
#[test]
#[rustfmt::skip]
fn test_merge_package_untractable_funding_output() {
let funding_outp = dumb_funding_output!();
let htlc_outp = dumb_accepted_htlc_output!(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let mut untractable_package = PackageTemplate::build_package(fake_txid(1), 0, funding_outp.clone(), 0);
let mut malleable_package = PackageTemplate::build_package(fake_txid(2), 0, htlc_outp.clone(), 1100);
assert!(!untractable_package.can_merge_with(&malleable_package, 1000));
assert!(untractable_package.merge_package(malleable_package.clone(), 1000).is_err());
assert!(!malleable_package.can_merge_with(&untractable_package, 1000));
assert!(malleable_package.merge_package(untractable_package.clone(), 1000).is_err());
}
#[test]
#[rustfmt::skip]
fn test_merge_empty_package() {
let revk_outp = dumb_revk_htlc_output!();
let mut empty_package = PackageTemplate::build_package(fake_txid(1), 0, revk_outp.clone(), 0);
empty_package.inputs = vec![];
let mut package = PackageTemplate::build_package(fake_txid(1), 1, revk_outp.clone(), 1100);
assert!(empty_package.merge_package(package.clone(), 1000).is_err());
assert!(package.merge_package(empty_package.clone(), 1000).is_err());
}
#[test]
#[rustfmt::skip]
fn test_merge_package_different_signed_locktimes() {
let offered_htlc_1 = dumb_offered_htlc_output!(900, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let offered_htlc_2 = dumb_offered_htlc_output!(901, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let accepted_htlc = dumb_accepted_htlc_output!(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let mut offered_htlc_1_package = PackageTemplate::build_package(fake_txid(1), 0, offered_htlc_1.clone(), 0);
let mut offered_htlc_2_package = PackageTemplate::build_package(fake_txid(1), 1, offered_htlc_2.clone(), 0);
let mut accepted_htlc_package = PackageTemplate::build_package(fake_txid(1), 2, accepted_htlc.clone(), 1001);
assert!(!offered_htlc_2_package.can_merge_with(&offered_htlc_1_package, 1000));
assert!(offered_htlc_2_package.merge_package(offered_htlc_1_package.clone(), 1000).is_err());
assert!(!offered_htlc_1_package.can_merge_with(&offered_htlc_2_package, 1000));
assert!(offered_htlc_1_package.merge_package(offered_htlc_2_package.clone(), 1000).is_err());
assert!(!accepted_htlc_package.can_merge_with(&offered_htlc_1_package, 1000));
assert!(accepted_htlc_package.merge_package(offered_htlc_1_package.clone(), 1000).is_err());
assert!(!offered_htlc_1_package.can_merge_with(&accepted_htlc_package, 1000));
assert!(offered_htlc_1_package.merge_package(accepted_htlc_package.clone(), 1000).is_err());
}
#[test]
#[rustfmt::skip]
fn test_merge_package_different_effective_locktimes() {
let old_outp_1 = dumb_counterparty_received_output!(1_000_000, 900, ChannelTypeFeatures::only_static_remote_key());
let old_outp_2 = dumb_counterparty_received_output!(1_000_000, 901, ChannelTypeFeatures::only_static_remote_key());
let future_outp_1 = dumb_counterparty_received_output!(1_000_000, 1001, ChannelTypeFeatures::only_static_remote_key());
let future_outp_2 = dumb_counterparty_received_output!(1_000_000, 1002, ChannelTypeFeatures::only_static_remote_key());
let old_outp_1_package = PackageTemplate::build_package(fake_txid(1), 0, old_outp_1.clone(), 0);
let old_outp_2_package = PackageTemplate::build_package(fake_txid(1), 1, old_outp_2.clone(), 0);
let future_outp_1_package = PackageTemplate::build_package(fake_txid(1), 2, future_outp_1.clone(), 0);
let future_outp_2_package = PackageTemplate::build_package(fake_txid(1), 3, future_outp_2.clone(), 0);
assert!(old_outp_1_package.can_merge_with(&old_outp_2_package, 1000));
assert!(old_outp_2_package.can_merge_with(&old_outp_1_package, 1000));
assert!(old_outp_1_package.clone().merge_package(old_outp_2_package.clone(), 1000).is_ok());
assert!(old_outp_2_package.clone().merge_package(old_outp_1_package.clone(), 1000).is_ok());
assert!(!future_outp_1_package.can_merge_with(&future_outp_2_package, 1000));
assert!(!future_outp_2_package.can_merge_with(&future_outp_1_package, 1000));
assert!(future_outp_1_package.clone().merge_package(future_outp_2_package.clone(), 1000).is_err());
assert!(future_outp_2_package.clone().merge_package(future_outp_1_package.clone(), 1000).is_err());
}
#[test]
#[rustfmt::skip]
fn test_merge_package_holder_htlc_output_clusters() {
let unpinnable_1 = dumb_accepted_htlc_output!(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let unpinnable_2 = dumb_accepted_htlc_output!(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let considered_pinnable = dumb_accepted_htlc_output!(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let pinnable_1 = dumb_offered_htlc_output!(1000, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let pinnable_2 = dumb_offered_htlc_output!(1000, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let mut unpinnable_1_package = PackageTemplate::build_package(fake_txid(1), 0, unpinnable_1.clone(), 1100);
let mut unpinnable_2_package = PackageTemplate::build_package(fake_txid(1), 1, unpinnable_2.clone(), 1100);
let mut considered_pinnable_package = PackageTemplate::build_package(fake_txid(1), 2, considered_pinnable.clone(), 1001);
let mut pinnable_1_package = PackageTemplate::build_package(fake_txid(1), 3, pinnable_1.clone(), 0);
let mut pinnable_2_package = PackageTemplate::build_package(fake_txid(1), 4, pinnable_2.clone(), 0);
let unpinnable_cluster = [&mut unpinnable_1_package, &mut unpinnable_2_package];
let pinnable_cluster = [&mut pinnable_1_package, &mut pinnable_2_package];
let considered_pinnable_cluster = [&mut considered_pinnable_package];
let clusters = [unpinnable_cluster.as_slice(), pinnable_cluster.as_slice(), considered_pinnable_cluster.as_slice()];
for a in 0..clusters.len() {
for b in 0..clusters.len() {
for i in 0..clusters[a].len() {
for j in 0..clusters[b].len() {
if a != b {
assert!(!clusters[a][i].can_merge_with(clusters[b][j], 1000));
} else {
if i != j {
assert!(clusters[a][i].can_merge_with(clusters[b][j], 1000));
}
}
}
}
}
}
let mut packages = vec![
unpinnable_1_package, unpinnable_2_package, considered_pinnable_package,
pinnable_1_package, pinnable_2_package,
];
for i in (1..packages.len()).rev() {
for j in 0..i {
if packages[i].can_merge_with(&packages[j], 1000) {
let merge = packages.remove(i);
assert!(packages[j].merge_package(merge, 1000).is_ok());
}
}
}
assert_eq!(packages.len(), 3);
}
#[test]
#[should_panic]
#[rustfmt::skip]
fn test_merge_package_different_tx_trees() {
let offered_htlc = dumb_offered_htlc_output!(900, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
let mut offered_htlc_package = PackageTemplate::build_package(fake_txid(1), 0, offered_htlc.clone(), 0);
let counterparty_received_htlc = dumb_counterparty_received_output!(1_000_000, 900, ChannelTypeFeatures::only_static_remote_key());
let counterparty_received_htlc_package = PackageTemplate::build_package(fake_txid(2), 0, counterparty_received_htlc.clone(), 0);
assert!(!offered_htlc_package.can_merge_with(&counterparty_received_htlc_package, 1000));
assert!(offered_htlc_package.merge_package(counterparty_received_htlc_package.clone(), 1000).is_err());
}
#[test]
#[rustfmt::skip]
fn test_package_split_malleable() {
let revk_outp_one = dumb_revk_output!();
let revk_outp_two = dumb_revk_output!();
let revk_outp_three = dumb_revk_output!();
let mut package_one = PackageTemplate::build_package(fake_txid(1), 0, revk_outp_one, 1100);
let package_two = PackageTemplate::build_package(fake_txid(1), 1, revk_outp_two, 1100);
let package_three = PackageTemplate::build_package(fake_txid(1), 2, revk_outp_three, 1100);
assert!(package_one.merge_package(package_two, 1000).is_ok());
assert!(package_one.merge_package(package_three, 1000).is_ok());
assert_eq!(package_one.outpoints().len(), 3);
if let Some(split_package) = package_one.split_package(&BitcoinOutPoint { txid: fake_txid(1), vout: 1 }) {
assert!(split_package.is_malleable());
assert_eq!(split_package.counterparty_spendable_height, package_one.counterparty_spendable_height);
assert_eq!(split_package.feerate_previous, package_one.feerate_previous);
assert_eq!(split_package.height_timer, package_one.height_timer);
} else { panic!(); }
assert_eq!(package_one.outpoints().len(), 2);
}
#[test]
#[rustfmt::skip]
fn test_package_split_untractable() {
let htlc_outp_one = dumb_accepted_htlc_output!(ChannelTypeFeatures::only_static_remote_key());
let mut package_one = PackageTemplate::build_package(fake_txid(1), 0, htlc_outp_one, 1000);
let ret_split = package_one.split_package(&BitcoinOutPoint { txid: fake_txid(1), vout: 0 });
assert!(ret_split.is_none());
}
#[test]
fn test_package_timer() {
let revk_outp = dumb_revk_output!();
let mut package = PackageTemplate::build_package(fake_txid(1), 0, revk_outp, 1000);
assert_eq!(package.timer(), 0);
package.set_timer(101);
assert_eq!(package.timer(), 101);
}
#[test]
#[rustfmt::skip]
fn test_package_amounts() {
let counterparty_outp = dumb_counterparty_received_output!(1_000_000, 1000, ChannelTypeFeatures::only_static_remote_key());
let package = PackageTemplate::build_package(fake_txid(1), 0, counterparty_outp, 1000);
assert_eq!(package.package_amount(), 1000);
}
#[test]
#[rustfmt::skip]
fn test_package_weight() {
let weight_sans_output = (4 + 4 + 1 + 36 + 4 + 1 + 1 + 8 + 1) * WITNESS_SCALE_FACTOR as u64 + 2;
{
let revk_outp = dumb_revk_output!();
let package = PackageTemplate::build_package(fake_txid(1), 0, revk_outp, 0);
assert_eq!(package.package_weight(&ScriptBuf::new()), weight_sans_output + WEIGHT_REVOKED_OUTPUT);
}
{
for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
let counterparty_outp = dumb_counterparty_received_output!(1_000_000, 1000, channel_type_features.clone());
let package = PackageTemplate::build_package(fake_txid(1), 0, counterparty_outp, 1000);
assert_eq!(package.package_weight(&ScriptBuf::new()), weight_sans_output + weight_received_htlc(channel_type_features));
}
}
{
for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
let counterparty_outp = dumb_counterparty_offered_output!(1_000_000, channel_type_features.clone());
let package = PackageTemplate::build_package(fake_txid(1), 0, counterparty_outp, 1000);
assert_eq!(package.package_weight(&ScriptBuf::new()), weight_sans_output + weight_offered_htlc(channel_type_features));
}
}
}
struct TestFeeEstimator {
sat_per_kw: u32,
}
impl FeeEstimator for TestFeeEstimator {
fn get_est_sat_per_1000_weight(&self, _: ConfirmationTarget) -> u32 {
self.sat_per_kw
}
}
#[test]
#[rustfmt::skip]
fn test_feerate_bump() {
let sat_per_kw = FEERATE_FLOOR_SATS_PER_KW;
let test_fee_estimator = &TestFeeEstimator { sat_per_kw };
let fee_estimator = LowerBoundedFeeEstimator::new(test_fee_estimator);
let fee_rate_strategy = FeerateStrategy::ForceBump;
let confirmation_target = ConfirmationTarget::UrgentOnChainSweep;
{
let predicted_weight_units = 1000;
let input_satoshis = 505;
let logger = TestLogger::new();
let bumped_fee_rate = feerate_bump(predicted_weight_units, input_satoshis, 546, 253, &fee_rate_strategy, confirmation_target, &fee_estimator, &logger);
assert!(bumped_fee_rate.is_none());
logger.assert_log_regex("lightning::chain::package", regex::Regex::new(r"Can't bump new claiming tx, input amount 505 is too small").unwrap(), 1);
}
{
let predicted_weight_units = 1000;
let input_satoshis = 2734;
let logger = TestLogger::new();
let bumped_fee_rate = feerate_bump(predicted_weight_units, input_satoshis, 546, 2188, &fee_rate_strategy, confirmation_target, &fee_estimator, &logger);
assert!(bumped_fee_rate.is_none());
logger.assert_log_regex("lightning::chain::package", regex::Regex::new(r"Can't bump new claiming tx, output amount 0 would end up below dust threshold 546").unwrap(), 1);
}
{
let predicted_weight_units = 1000;
let input_satoshis = 506;
let logger = TestLogger::new();
let bumped_fee_rate = feerate_bump(predicted_weight_units, input_satoshis, 546, 253, &fee_rate_strategy, confirmation_target, &fee_estimator, &logger);
assert!(bumped_fee_rate.is_none());
logger.assert_log_regex("lightning::chain::package", regex::Regex::new(r"Can't bump new claiming tx, output amount 0 would end up below dust threshold 546").unwrap(), 1);
}
{
let predicted_weight_units = 1000;
let input_satoshis = 1051;
let logger = TestLogger::new();
let bumped_fee_rate = feerate_bump(predicted_weight_units, input_satoshis, 546, 253, &fee_rate_strategy, confirmation_target, &fee_estimator, &logger);
assert!(bumped_fee_rate.is_none());
logger.assert_log_regex("lightning::chain::package", regex::Regex::new(r"Can't bump new claiming tx, output amount 545 would end up below dust threshold 546").unwrap(), 1);
}
{
let predicted_weight_units = 1000;
let input_satoshis = 1052;
let logger = TestLogger::new();
let bumped_fee_rate = feerate_bump(predicted_weight_units, input_satoshis, 546, 253, &fee_rate_strategy, confirmation_target, &fee_estimator, &logger).unwrap();
assert_eq!(bumped_fee_rate, (506, 506));
logger.assert_log_regex("lightning::chain::package", regex::Regex::new(r"Naive fee bump of 63s does not meet min relay fee requirements of 253s").unwrap(), 1);
}
}
}