Skip to content
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

Extend begin_interactive_funding_tx_construction() with splicing-specific extra input (on top of #3443) #3516

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
146 changes: 135 additions & 11 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use bitcoin::amount::Amount;
use bitcoin::constants::ChainHash;
use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash};
use bitcoin::transaction::{Transaction, TxIn};
use bitcoin::transaction::{Transaction, TxIn, TxOut};
use bitcoin::sighash;
use bitcoin::sighash::EcdsaSighashType;
use bitcoin::consensus::encode;
Expand All @@ -31,9 +31,9 @@ use crate::ln::types::ChannelId;
use crate::types::payment::{PaymentPreimage, PaymentHash};
use crate::types::features::{ChannelTypeFeatures, InitFeatures};
use crate::ln::interactivetxs::{
get_output_weight, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor,
InteractiveTxConstructorArgs, InteractiveTxSigningSession, InteractiveTxMessageSendResult,
TX_COMMON_FIELDS_WEIGHT,
get_output_weight, calculate_change_output_value, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor,
InteractiveTxConstructorArgs, InteractiveTxMessageSend, InteractiveTxSigningSession, InteractiveTxMessageSendResult,
OutputOwned, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT,
};
use crate::ln::msgs;
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError};
Expand Down Expand Up @@ -1681,6 +1681,96 @@ impl<SP: Deref> InitialRemoteCommitmentReceiver<SP> for Channel<SP> where SP::Ta
}

impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled
fn begin_interactive_funding_tx_construction<ES: Deref>(
&mut self, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey,
prev_funding_input: Option<(TxIn, TransactionU16LenLimited)>,
) -> Result<Option<InteractiveTxMessageSend>, APIError>
where ES::Target: EntropySource
{
let mut funding_inputs = Vec::new();
mem::swap(&mut self.dual_funding_context.our_funding_inputs, &mut funding_inputs);

if let Some(prev_funding_input) = prev_funding_input {
funding_inputs.push(prev_funding_input);
}

let funding_inputs_prev_outputs = DualFundingChannelContext::txouts_from_input_prev_txs(&funding_inputs)
.map_err(|err| APIError::APIMisuseError { err: err.to_string() })?;

let total_input_satoshis: u64 = funding_inputs_prev_outputs.iter().map(|txout| txout.value.to_sat()).sum();
if total_input_satoshis < self.dual_funding_context.our_funding_satoshis {
return Err(APIError::APIMisuseError {
err: format!("Total value of funding inputs must be at least funding amount. It was {} sats",
total_input_satoshis) });
}

// Add output for funding tx
let mut funding_outputs = Vec::new();
let funding_output_value_satoshis = self.context.get_value_satoshis();
let funding_output_script_pubkey = self.context.get_funding_redeemscript().to_p2wsh();
let expected_remote_shared_funding_output = if self.context.is_outbound() {
let tx_out = TxOut {
value: Amount::from_sat(funding_output_value_satoshis),
script_pubkey: funding_output_script_pubkey,
};
funding_outputs.push(
if self.dual_funding_context.their_funding_satoshis.unwrap_or(0) == 0 {
OutputOwned::SharedControlFullyOwned(tx_out)
} else {
OutputOwned::Shared(SharedOwnedOutput::new(
tx_out, self.dual_funding_context.our_funding_satoshis
))
}
);
None
} else {
Some((funding_output_script_pubkey, funding_output_value_satoshis))
};

// Optionally add change output
if let Some(change_value) = calculate_change_output_value(
self.context.is_outbound(), self.dual_funding_context.our_funding_satoshis,
&funding_inputs_prev_outputs, &funding_outputs,
self.dual_funding_context.funding_feerate_sat_per_1000_weight,
self.context.holder_dust_limit_satoshis,
) {
let change_script = signer_provider.get_destination_script(self.context.channel_keys_id).map_err(
|err| APIError::APIMisuseError {
err: format!("Failed to get change script as new destination script, {:?}", err),
})?;
let mut change_output = TxOut {
value: Amount::from_sat(change_value),
script_pubkey: change_script,
};
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
let change_output_fee = fee_for_weight(self.dual_funding_context.funding_feerate_sat_per_1000_weight, change_output_weight);
change_output.value = Amount::from_sat(change_value.saturating_sub(change_output_fee));
// Note: dust check not done here, should be handled before
funding_outputs.push(OutputOwned::Single(change_output));
}

let constructor_args = InteractiveTxConstructorArgs {
entropy_source,
holder_node_id,
counterparty_node_id: self.context.counterparty_node_id,
channel_id: self.context.channel_id(),
feerate_sat_per_kw: self.dual_funding_context.funding_feerate_sat_per_1000_weight,
is_initiator: self.context.is_outbound(),
funding_tx_locktime: self.dual_funding_context.funding_tx_locktime,
inputs_to_contribute: funding_inputs,
outputs_to_contribute: funding_outputs,
expected_remote_shared_funding_output,
};
let mut tx_constructor = InteractiveTxConstructor::new(constructor_args)
.map_err(|_| APIError::APIMisuseError { err: "Incorrect shared output provided".into() })?;
let msg = tx_constructor.take_initiator_first_message();

self.interactive_tx_constructor = Some(tx_constructor);

Ok(msg)
}

pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult {
InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor {
Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err(
Expand Down Expand Up @@ -4103,7 +4193,6 @@ 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,
Expand Down Expand Up @@ -4149,6 +4238,9 @@ pub(super) fn calculate_our_funding_satoshis(
pub(super) struct DualFundingChannelContext {
/// The amount in satoshis we will be contributing to the channel.
pub our_funding_satoshis: u64,
/// The amount in satoshis our counterparty will be contributing to the channel.
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
pub their_funding_satoshis: Option<u64>,
/// The funding transaction locktime suggested by the initiator. If set by us, it is always set
/// to the current block height to align incentives against fee-sniping.
pub funding_tx_locktime: LockTime,
Expand All @@ -4160,10 +4252,39 @@ pub(super) struct DualFundingChannelContext {
/// Note that the `our_funding_satoshis` field is equal to the total value of `our_funding_inputs`
/// minus any fees paid for our contributed weight. This means that change will never be generated
/// and the maximum value possible will go towards funding the channel.
///
/// Note that this field may be emptied once the interactive negotiation has been started.
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>,
}

impl DualFundingChannelContext {
/// Obtain prev outputs for each supplied input and matching transaction.
/// Can error when there a prev tx does not have an output for the specified vout number.
/// Also checks for matching of transaction IDs.
fn txouts_from_input_prev_txs(inputs: &Vec<(TxIn, TransactionU16LenLimited)>) -> Result<Vec<&TxOut>, ChannelError> {
let mut prev_outputs: Vec<&TxOut> = Vec::with_capacity(inputs.len());
// Check that vouts exist for each TxIn in provided transactions.
for (idx, (txin, tx)) in inputs.iter().enumerate() {
let txid = tx.as_transaction().compute_txid();
if txin.previous_output.txid != txid {
return Err(ChannelError::Warn(
format!("Transaction input txid mismatch, {} vs. {}, at index {}", txin.previous_output.txid, txid, idx)
));
}
if let Some(output) = tx.as_transaction().output.get(txin.previous_output.vout as usize) {
prev_outputs.push(output);
} else {
return Err(ChannelError::Warn(
format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn, at index {}",
txid, txin.previous_output.vout, idx)
));
}
}
Ok(prev_outputs)
}
}

// Holder designates channel data owned for the benefit of the user client.
// Counterparty designates channel data owned by the another channel participant entity.
pub(super) struct Channel<SP: Deref> where SP::Target: SignerProvider {
Expand Down Expand Up @@ -8829,15 +8950,17 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
unfunded_channel_age_ticks: 0,
holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx),
};
let dual_funding_context = DualFundingChannelContext {
our_funding_satoshis: funding_satoshis,
their_funding_satoshis: None,
funding_tx_locktime,
funding_feerate_sat_per_1000_weight,
our_funding_inputs: funding_inputs,
};
let chan = Self {
context,
unfunded_context,
dual_funding_context: DualFundingChannelContext {
our_funding_satoshis: funding_satoshis,
funding_tx_locktime,
funding_feerate_sat_per_1000_weight,
our_funding_inputs: funding_inputs,
},
dual_funding_context,
interactive_tx_constructor: None,
};
Ok(chan)
Expand Down Expand Up @@ -8982,6 +9105,7 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {

let dual_funding_context = DualFundingChannelContext {
our_funding_satoshis: funding_satoshis,
their_funding_satoshis: Some(msg.common_fields.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(),
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use crate::ln::inbound_payment;
use crate::ln::types::ChannelId;
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext};
#[cfg(any(dual_funding, splicing))]
#[cfg(dual_funding)]
use crate::ln::channel::PendingV2Channel;
use crate::ln::channel_state::ChannelDetails;
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
Expand Down
Loading
Loading