Skip to content

Include base input fee in fee, in calculate_our_funding_satoshis() #3558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 62 additions & 42 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use crate::chain::transaction::{OutPoint, TransactionData};
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient};
use crate::events::{ClosureReason, Event};
use crate::events::bump_transaction::BASE_INPUT_WEIGHT;
use crate::routing::gossip::NodeId;
use crate::util::ser::{Readable, ReadableArgs, TransactionU16LenLimited, Writeable, Writer};
use crate::util::logger::{Logger, Record, WithContext};
Expand Down Expand Up @@ -4468,46 +4469,33 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
}

#[allow(dead_code)] // TODO(dual_funding): Remove once V2 channels is enabled.
pub(super) fn calculate_our_funding_satoshis(
is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)],
total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32,
holder_dust_limit_satoshis: u64,
) -> Result<u64, APIError> {
let mut total_input_satoshis = 0u64;
let mut our_contributed_weight = 0u64;
/// Estimate our part of the fee of the new funding transaction.
/// input_count: Number of contributed inputs.
/// witness_weight: The witness weight for contributed inputs.
#[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used.
fn estimate_v2_funding_transaction_fee(
is_initiator: bool, input_count: usize, witness_weight: Weight,
funding_feerate_sat_per_1000_weight: u32,
) -> u64 {
// Inputs
let mut weight = (input_count as u64) * BASE_INPUT_WEIGHT;

for (idx, input) in funding_inputs.iter().enumerate() {
if let Some(output) = input.1.as_transaction().output.get(input.0.previous_output.vout as usize) {
total_input_satoshis = total_input_satoshis.saturating_add(output.value.to_sat());
} else {
return Err(APIError::APIMisuseError {
err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
input.1.as_transaction().compute_txid(), input.0.previous_output.vout, idx) });
}
}
our_contributed_weight = our_contributed_weight.saturating_add(total_witness_weight.to_wu());
// Witnesses
weight = weight.saturating_add(witness_weight.to_wu());

// If we are the initiator, we must pay for weight of all common fields in the funding transaction.
if is_initiator {
our_contributed_weight = our_contributed_weight
weight = weight
.saturating_add(TX_COMMON_FIELDS_WEIGHT)
// The weight of a P2WSH output to be added later.
//
// The weight of the funding output, a P2WSH output
// NOTE: The witness script hash given here is irrelevant as it's a fixed size and we just want
// to calculate the contributed weight, so we use an all-zero hash.
.saturating_add(get_output_weight(&ScriptBuf::new_p2wsh(
&WScriptHash::from_raw_hash(Hash::all_zeros())
)).to_wu())
}

let funding_satoshis = total_input_satoshis
.saturating_sub(fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight));
if funding_satoshis < holder_dust_limit_satoshis {
Ok(0)
} else {
Ok(funding_satoshis)
}
fee_for_weight(funding_feerate_sat_per_1000_weight, weight)
}

/// Context for dual-funded channels.
Expand Down Expand Up @@ -9247,27 +9235,23 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {

/// Creates a new dual-funded channel from a remote side's request for one.
/// Assumes chain_hash has already been checked and corresponds with what we expect!
/// TODO(dual_funding): Allow contributions, pass intended amount and inputs
#[allow(dead_code)] // TODO(dual_funding): Remove once V2 channels is enabled.
pub fn new_inbound<ES: Deref, F: Deref, L: Deref>(
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
holder_node_id: PublicKey, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
their_features: &InitFeatures, msg: &msgs::OpenChannelV2,
funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, total_witness_weight: Weight,
user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L,
) -> Result<Self, ChannelError>
where ES::Target: EntropySource,
F::Target: FeeEstimator,
L::Target: Logger,
{
let funding_satoshis = calculate_our_funding_satoshis(
false, &funding_inputs, total_witness_weight, msg.funding_feerate_sat_per_1000_weight,
msg.common_fields.dust_limit_satoshis
).map_err(|_| ChannelError::Close(
(
"Failed to accept channel".to_string(),
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
)))?;
let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
// TODO(dual_funding): Take these as input once supported
let our_funding_satoshis = 0u64;
let our_funding_inputs = Vec::new();

let channel_value_satoshis = our_funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
Expand Down Expand Up @@ -9301,7 +9285,7 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
logger,
false,

funding_satoshis,
our_funding_satoshis,

counterparty_pubkeys,
channel_type,
Expand All @@ -9316,10 +9300,10 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
context.channel_id = channel_id;

let dual_funding_context = DualFundingChannelContext {
our_funding_satoshis: funding_satoshis,
our_funding_satoshis: our_funding_satoshis,
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
our_funding_inputs: funding_inputs.clone(),
our_funding_inputs: our_funding_inputs.clone(),
};

let interactive_tx_constructor = Some(InteractiveTxConstructor::new(
Expand All @@ -9331,7 +9315,7 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
feerate_sat_per_kw: dual_funding_context.funding_feerate_sat_per_1000_weight,
funding_tx_locktime: dual_funding_context.funding_tx_locktime,
is_initiator: false,
inputs_to_contribute: funding_inputs,
inputs_to_contribute: our_funding_inputs,
outputs_to_contribute: Vec::new(),
expected_remote_shared_funding_output: Some((context.get_funding_redeemscript().to_p2wsh(), context.channel_value_satoshis)),
}
Expand Down Expand Up @@ -12236,4 +12220,40 @@ mod tests {
assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
}

#[test]
fn test_estimate_v2_funding_transaction_fee() {
use crate::ln::channel::estimate_v2_funding_transaction_fee;
use bitcoin::Weight;

// 2 inputs with weight 300, initiator, 2000 sat/kw feerate
assert_eq!(
estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), 2000),
1668
);

// higher feerate
assert_eq!(
estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), 3000),
2502
);

// only 1 input
assert_eq!(
estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(300), 2000),
1348
);

// 0 input weight
assert_eq!(
estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(0), 2000),
748
);

// not initiator
assert_eq!(
estimate_v2_funding_transaction_fee(false, 1, Weight::from_wu(0), 2000),
320
);
}
}
17 changes: 8 additions & 9 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! imply it needs to fail HTLCs/payments/channels it manages).

use bitcoin::block::Header;
use bitcoin::transaction::{Transaction, TxIn};
use bitcoin::transaction::Transaction;
use bitcoin::constants::ChainHash;
use bitcoin::key::constants::SECRET_KEY_SIZE;
use bitcoin::network::Network;
Expand All @@ -30,7 +30,7 @@ use bitcoin::hash_types::{BlockHash, Txid};

use bitcoin::secp256k1::{SecretKey,PublicKey};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{secp256k1, Sequence, Weight};
use bitcoin::{secp256k1, Sequence};

use crate::events::FundingInfo;
use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext};
Expand Down Expand Up @@ -83,7 +83,6 @@ use crate::util::wakers::{Future, Notifier};
use crate::util::scid_utils::fake_scid;
use crate::util::string::UntrustedString;
use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter};
use crate::util::ser::TransactionU16LenLimited;
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::errors::APIError;
#[cfg(async_payments)] use {
Expand Down Expand Up @@ -7648,7 +7647,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
/// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest
/// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id
pub fn accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> {
self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id, vec![], Weight::from_wu(0))
self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id)
}

/// Accepts a request to open a channel after a [`events::Event::OpenChannelRequest`], treating
Expand All @@ -7670,13 +7669,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
/// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest
/// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id
pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> {
self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id, vec![], Weight::from_wu(0))
self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id)
}

/// TODO(dual_funding): Allow contributions, pass intended amount and inputs
fn do_accept_inbound_channel(
&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool,
user_channel_id: u128, _funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>,
_total_witness_weight: Weight,
user_channel_id: u128,
) -> Result<(), APIError> {
let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(*temporary_channel_id), None);
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
Expand Down Expand Up @@ -7726,7 +7725,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
&self.fee_estimator, &self.entropy_source, &self.signer_provider,
self.get_our_node_id(), *counterparty_node_id,
&self.channel_type_features(), &peer_state.latest_features,
&open_channel_msg, _funding_inputs, _total_witness_weight,
&open_channel_msg,
user_channel_id, &self.default_configuration, best_block_height,
&self.logger,
).map_err(|_| MsgHandleErrInternal::from_chan_no_close(
Expand Down Expand Up @@ -8012,7 +8011,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
let channel = PendingV2Channel::new_inbound(
&self.fee_estimator, &self.entropy_source, &self.signer_provider,
self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(),
&peer_state.latest_features, msg, vec![], Weight::from_wu(0), user_channel_id,
&peer_state.latest_features, msg, user_channel_id,
&self.default_configuration, best_block_height, &self.logger,
).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?;
let message_send_event = events::MessageSendEvent::SendAcceptChannelV2 {
Expand Down
32 changes: 13 additions & 19 deletions lightning/src/ln/dual_funding_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,28 @@

#[cfg(dual_funding)]
use {
crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator},
crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator},
crate::events::{Event, MessageSendEvent, MessageSendEventsProvider},
crate::ln::chan_utils::{
make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters,
CounterpartyChannelTransactionParameters,
},
crate::ln::channel::{
calculate_our_funding_satoshis, PendingV2Channel, MIN_CHAN_DUST_LIMIT_SATOSHIS,
},
crate::ln::channel::PendingV2Channel,
crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint},
crate::ln::functional_test_utils::*,
crate::ln::msgs::ChannelMessageHandler,
crate::ln::msgs::{CommitmentSigned, TxAddInput, TxAddOutput, TxComplete},
crate::ln::types::ChannelId,
crate::prelude::*,
crate::sign::{ChannelSigner as _, P2WPKH_WITNESS_WEIGHT},
crate::sign::ChannelSigner as _,
crate::util::ser::TransactionU16LenLimited,
crate::util::test_utils,
bitcoin::Weight,
};

#[cfg(dual_funding)]
// Dual-funding: V2 Channel Establishment Tests
struct V2ChannelEstablishmentTestSession {
funding_input_sats: u64,
initiator_input_value_satoshis: u64,
}

Expand All @@ -60,17 +58,7 @@ fn do_test_v2_channel_establishment(
.collect();

// Alice creates a dual-funded channel as initiator.
let funding_feerate = node_cfgs[0]
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
let funding_satoshis = calculate_our_funding_satoshis(
true,
&initiator_funding_inputs[..],
Weight::from_wu(P2WPKH_WITNESS_WEIGHT),
funding_feerate,
MIN_CHAN_DUST_LIMIT_SATOSHIS,
)
.unwrap();
let funding_satoshis = session.funding_input_sats;
let mut channel = PendingV2Channel::new_outbound(
&LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator),
&nodes[0].node.entropy_source,
Expand Down Expand Up @@ -260,12 +248,18 @@ fn do_test_v2_channel_establishment(
fn test_v2_channel_establishment() {
// Only initiator contributes, no persist pending
do_test_v2_channel_establishment(
V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 },
V2ChannelEstablishmentTestSession {
funding_input_sats: 100_000,
initiator_input_value_satoshis: 150_000,
},
false,
);
// Only initiator contributes, persist pending
do_test_v2_channel_establishment(
V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 },
V2ChannelEstablishmentTestSession {
funding_input_sats: 100_000,
initiator_input_value_satoshis: 150_000,
},
true,
);
}
Loading