Skip to content

Commit 6e8a1c1

Browse files
committed
Perform interactive tx negotiation during splicing
1 parent b0d96ba commit 6e8a1c1

File tree

4 files changed

+373
-126
lines changed

4 files changed

+373
-126
lines changed

Diff for: lightning/src/ln/channel.rs

+200-84
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::ln::types::ChannelId;
3232
use crate::types::payment::{PaymentPreimage, PaymentHash};
3333
use crate::types::features::{ChannelTypeFeatures, InitFeatures};
3434
use crate::ln::interactivetxs::{
35-
get_output_weight, calculate_change_output_value, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor,
35+
estimate_input_weight, get_output_weight, calculate_change_output_value, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor,
3636
InteractiveTxConstructorArgs, InteractiveTxMessageSend, InteractiveTxSigningSession, InteractiveTxMessageSendResult,
3737
OutputOwned, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT,
3838
};
@@ -1142,7 +1142,7 @@ pub(super) enum Channel<SP: Deref> where SP::Target: SignerProvider {
11421142
Funded(FundedChannel<SP>),
11431143
#[cfg(splicing)]
11441144
/// Used during splicing, channel is funded but a new funding is being renegotiated.
1145-
RefundingV2(FundedChannel<SP>),
1145+
RefundingV2(SplicingChannel<SP>),
11461146
}
11471147

11481148
impl<SP: Deref> Channel<SP> where
@@ -1156,7 +1156,7 @@ impl<SP: Deref> Channel<SP> where
11561156
Channel::UnfundedInboundV1(chan) => &chan.context,
11571157
Channel::UnfundedV2(chan) => &chan.context,
11581158
#[cfg(splicing)]
1159-
Channel::RefundingV2(chan) => &chan.context,
1159+
Channel::RefundingV2(chan) => &chan.pre_funded.context,
11601160
}
11611161
}
11621162

@@ -1167,7 +1167,7 @@ impl<SP: Deref> Channel<SP> where
11671167
Channel::UnfundedInboundV1(ref mut chan) => &mut chan.context,
11681168
Channel::UnfundedV2(ref mut chan) => &mut chan.context,
11691169
#[cfg(splicing)]
1170-
Channel::RefundingV2(ref mut chan) => &mut chan.context,
1170+
Channel::RefundingV2(ref mut chan) => &mut chan.pre_funded.context,
11711171
}
11721172
}
11731173

@@ -1183,22 +1183,29 @@ impl<SP: Deref> Channel<SP> where
11831183
}
11841184

11851185
pub fn is_funded(&self) -> bool {
1186-
matches!(self, Channel::Funded(_))
1186+
match self {
1187+
Channel::Funded(_) => true,
1188+
#[cfg(splicing)]
1189+
Channel::RefundingV2(_) => true,
1190+
_ => false,
1191+
}
11871192
}
11881193

11891194
pub fn as_funded(&self) -> Option<&FundedChannel<SP>> {
1190-
if let Channel::Funded(channel) = self {
1191-
Some(channel)
1192-
} else {
1193-
None
1195+
match self {
1196+
Channel::Funded(channel) => Some(channel),
1197+
#[cfg(splicing)]
1198+
Channel::RefundingV2(channel) => Some(&channel.pre_funded),
1199+
_ => None,
11941200
}
11951201
}
11961202

11971203
pub fn as_funded_mut(&mut self) -> Option<&mut FundedChannel<SP>> {
1198-
if let Channel::Funded(channel) = self {
1199-
Some(channel)
1200-
} else {
1201-
None
1204+
match self {
1205+
Channel::Funded(channel) => Some(channel),
1206+
#[cfg(splicing)]
1207+
Channel::RefundingV2(channel) => Some(&mut channel.pre_funded),
1208+
_ => None,
12021209
}
12031210
}
12041211

@@ -1239,10 +1246,11 @@ impl<SP: Deref> Channel<SP> where
12391246
}
12401247

12411248
pub fn as_unfunded_v2_mut(&mut self) -> Option<&mut PendingV2Channel<SP>> {
1242-
if let Channel::UnfundedV2(channel) = self {
1243-
Some(channel)
1244-
} else {
1245-
None
1249+
match self {
1250+
Channel::UnfundedV2(channel) => Some(channel),
1251+
#[cfg(splicing)]
1252+
Channel::RefundingV2(channel) => Some(&mut channel.post_pending),
1253+
_ => None,
12461254
}
12471255
}
12481256

@@ -1294,7 +1302,7 @@ impl<SP: Deref> Channel<SP> where
12941302
},
12951303
Channel::UnfundedV2(_) => None,
12961304
#[cfg(splicing)]
1297-
Channel::RefundingV2(chan) => Some(chan.signer_maybe_unblocked(logger)),
1305+
Channel::RefundingV2(chan) => Some(chan.pre_funded.signer_maybe_unblocked(logger)),
12981306
}
12991307
}
13001308

@@ -1423,6 +1431,55 @@ where
14231431
}
14241432
}
14251433

1434+
/// Struct holding together various state dureing splicing negotiation
1435+
#[cfg(splicing)]
1436+
pub(super) struct SplicingChannel<SP: Deref> where SP::Target: SignerProvider {
1437+
pub pre_funded: FundedChannel<SP>,
1438+
pub post_pending: PendingV2Channel<SP>,
1439+
pub post_funded: Option<FundedChannel<SP>>,
1440+
}
1441+
1442+
#[cfg(splicing)]
1443+
impl<SP: Deref> SplicingChannel<SP> where SP::Target: SignerProvider {
1444+
pub(super) fn new(pre_funded: FundedChannel<SP>, post_pending: PendingV2Channel<SP>) -> Self {
1445+
Self {
1446+
pre_funded,
1447+
post_pending,
1448+
post_funded: None,
1449+
}
1450+
}
1451+
1452+
pub fn splice_init<ES: Deref, L: Deref>(
1453+
&mut self, msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L,
1454+
) -> Result<msgs::SpliceAck,ChannelError> where ES::Target: EntropySource, L::Target: Logger {
1455+
self.post_pending.splice_init(msg, signer_provider, entropy_source, holder_node_id, logger)
1456+
}
1457+
1458+
pub fn splice_ack<ES: Deref, L: Deref>(
1459+
&mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L,
1460+
) -> Result<Option<InteractiveTxMessageSend>, ChannelError> where ES::Target: EntropySource, L::Target: Logger {
1461+
self.post_pending.splice_ack(msg, our_funding_contribution, signer_provider, entropy_source, holder_node_id, logger)
1462+
}
1463+
1464+
pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult {
1465+
self.post_pending.tx_add_input(msg)
1466+
}
1467+
1468+
pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult {
1469+
self.post_pending.tx_add_output(msg)
1470+
}
1471+
1472+
pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult {
1473+
self.post_pending.tx_complete(msg)
1474+
}
1475+
1476+
pub fn funding_tx_constructed<L: Deref>(
1477+
&mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L
1478+
) -> Result<(msgs::CommitmentSigned, Option<Event>), ChannelError> where L::Target: Logger {
1479+
self.post_pending.funding_tx_constructed(signing_session, logger)
1480+
}
1481+
}
1482+
14261483
/// Contains all state common to unfunded inbound/outbound channels.
14271484
pub(super) struct UnfundedChannelContext {
14281485
/// A counter tracking how many ticks have elapsed since this unfunded channel was
@@ -1455,7 +1512,7 @@ impl UnfundedChannelContext {
14551512
/// Info about a pending splice, used in the pre-splice channel
14561513
#[cfg(splicing)]
14571514
#[derive(Clone)]
1458-
struct PendingSplicePre {
1515+
pub(super) struct PendingSplicePre {
14591516
pub our_funding_contribution: i64,
14601517
pub funding_feerate_perkw: u32,
14611518
pub locktime: u32,
@@ -4869,6 +4926,54 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
48694926
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
48704927
}
48714928

4929+
pub(super) fn maybe_add_funding_change_output<SP: Deref>(signer_provider: &SP, is_initiator: bool,
4930+
our_funding_satoshis: u64, funding_inputs_prev_outputs: &Vec<TxOut>,
4931+
funding_outputs: &mut Vec<OutputOwned>, funding_feerate_sat_per_1000_weight: u32,
4932+
total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32],
4933+
) -> Result<Option<TxOut>, ChannelError> where
4934+
SP::Target: SignerProvider,
4935+
{
4936+
let our_funding_inputs_weight = funding_inputs_prev_outputs.iter().fold(0u64, |weight, prev_output| {
4937+
weight.saturating_add(estimate_input_weight(prev_output).to_wu())
4938+
});
4939+
let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, out| {
4940+
weight.saturating_add(get_output_weight(&out.tx_out().script_pubkey).to_wu())
4941+
});
4942+
let our_contributed_weight = our_funding_outputs_weight.saturating_add(our_funding_inputs_weight);
4943+
let mut fees_sats = fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight);
4944+
4945+
// If we are the initiator, we must pay for weight of all common fields in the funding transaction.
4946+
if is_initiator {
4947+
let common_fees = fee_for_weight(funding_feerate_sat_per_1000_weight, TX_COMMON_FIELDS_WEIGHT);
4948+
fees_sats = fees_sats.saturating_add(common_fees);
4949+
}
4950+
4951+
let remaining_value = total_input_satoshis
4952+
.saturating_sub(our_funding_satoshis)
4953+
.saturating_sub(fees_sats);
4954+
4955+
if remaining_value < holder_dust_limit_satoshis {
4956+
Ok(None)
4957+
} else {
4958+
let change_script = signer_provider.get_destination_script(channel_keys_id).map_err(
4959+
|_| ChannelError::Close((
4960+
"Failed to get change script as new destination script".to_owned(),
4961+
ClosureReason::ProcessingError { err: "Failed to get change script as new destination script".to_owned() }
4962+
))
4963+
)?;
4964+
let mut change_output = TxOut {
4965+
value: Amount::from_sat(remaining_value),
4966+
script_pubkey: change_script,
4967+
};
4968+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
4969+
4970+
let change_output_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, change_output_weight);
4971+
change_output.value = Amount::from_sat(remaining_value.saturating_sub(change_output_fee));
4972+
funding_outputs.push(OutputOwned::Single(change_output.clone()));
4973+
Ok(Some(change_output))
4974+
}
4975+
}
4976+
48724977
pub(super) fn calculate_our_funding_satoshis(
48734978
is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)],
48744979
total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32,
@@ -4941,7 +5046,7 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
49415046
holder_commitment_point: HolderCommitmentPoint,
49425047
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
49435048
#[cfg(splicing)]
4944-
pending_splice_pre: Option<PendingSplicePre>,
5049+
pub pending_splice_pre: Option<PendingSplicePre>,
49455050
/// Info about an in-progress, pending splice (if any), on the post-splice channel
49465051
#[cfg(splicing)]
49475052
pending_splice_post: Option<PendingSplicePost>,
@@ -8632,11 +8737,11 @@ impl<SP: Deref> FundedChannel<SP> where
86328737
Ok(msg)
86338738
}
86348739

8635-
/// Handle splice_init
8740+
/// Checks during handling splice_init
86368741
#[cfg(splicing)]
8637-
pub fn splice_init<ES: Deref, L: Deref>(
8638-
&mut self, msg: &msgs::SpliceInit, _signer_provider: &SP, _entropy_source: &ES, _holder_node_id: PublicKey, logger: &L,
8639-
) -> Result<msgs::SpliceAck, ChannelError> where ES::Target: EntropySource, L::Target: Logger {
8742+
pub fn splice_init_checks<ES: Deref>(
8743+
&mut self, msg: &msgs::SpliceInit, _signer_provider: &SP, _entropy_source: &ES, _holder_node_id: PublicKey,
8744+
) -> Result<(), ChannelError> where ES::Target: EntropySource {
86408745
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
86418746
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
86428747
let our_funding_contribution_satoshis = 0i64;
@@ -8677,52 +8782,7 @@ impl<SP: Deref> FundedChannel<SP> where
86778782
// Early check for reserve requirement, assuming maximum balance of full channel value
86788783
// This will also be checked later at tx_complete
86798784
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8680-
8681-
// TODO(splicing): Store msg.funding_pubkey
8682-
8683-
// Apply start of splice change in the state
8684-
self.context.splice_start(false, logger);
8685-
8686-
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
8687-
8688-
// TODO(splicing): start interactive funding negotiation
8689-
// let _msg = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id)
8690-
// .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?;
8691-
8692-
Ok(splice_ack_msg)
8693-
}
8694-
8695-
/// Handle splice_ack
8696-
#[cfg(splicing)]
8697-
pub fn splice_ack<ES: Deref, L: Deref>(
8698-
&mut self, msg: &msgs::SpliceAck, _signer_provider: &SP, _entropy_source: &ES, _holder_node_id: PublicKey, logger: &L,
8699-
) -> Result<Option<InteractiveTxMessageSend>, ChannelError> where ES::Target: EntropySource, L::Target: Logger {
8700-
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8701-
8702-
// check if splice is pending
8703-
let pending_splice = if let Some(pending_splice) = &self.pending_splice_pre {
8704-
pending_splice
8705-
} else {
8706-
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
8707-
};
8708-
8709-
let our_funding_contribution = pending_splice.our_funding_contribution;
8710-
8711-
let pre_channel_value = self.context.get_value_satoshis();
8712-
let post_channel_value = PendingSplicePre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8713-
let post_balance = PendingSplicePre::add_checked(self.context.value_to_self_msat, our_funding_contribution);
8714-
// Early check for reserve requirement, assuming maximum balance of full channel value
8715-
// This will also be checked later at tx_complete
8716-
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8717-
8718-
// Apply start of splice change in the state
8719-
self.context.splice_start(true, logger);
8720-
8721-
// TODO(splicing): start interactive funding negotiation
8722-
// let tx_msg_opt = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id)
8723-
// .map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?;
8724-
// Ok(tx_msg_opt)
8725-
Ok(None)
8785+
Ok(())
87268786
}
87278787

87288788
// Send stuff to our remote peers:
@@ -9965,6 +10025,56 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
996510025
}
996610026
}
996710027

10028+
pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result<FundedChannel<SP>, ChannelError>{
10029+
let holder_commitment_point = self.unfunded_context.holder_commitment_point.ok_or(ChannelError::close(
10030+
format!("Expected to have holder commitment points available upon finishing interactive tx construction for channel {}",
10031+
self.context.channel_id())))?;
10032+
let channel = FundedChannel {
10033+
context: self.context,
10034+
interactive_tx_signing_session: Some(signing_session),
10035+
holder_commitment_point,
10036+
#[cfg(splicing)]
10037+
pending_splice_pre: None,
10038+
#[cfg(splicing)]
10039+
pending_splice_post: self.pending_splice_post,
10040+
};
10041+
10042+
Ok(channel)
10043+
}
10044+
10045+
/// Handle splice_ack
10046+
#[cfg(splicing)]
10047+
pub fn splice_ack<ES: Deref, L: Deref>(
10048+
&mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L,
10049+
) -> Result<Option<InteractiveTxMessageSend>, ChannelError> where ES::Target: EntropySource, L::Target: Logger {
10050+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
10051+
10052+
// check if splice is pending
10053+
let pending_splice = if let Some(pending_splice) = &self.pending_splice_post {
10054+
pending_splice
10055+
} else {
10056+
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
10057+
};
10058+
10059+
let pre_channel_value = self.context.get_value_satoshis();
10060+
let post_channel_value = PendingSplicePre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
10061+
let post_balance = PendingSplicePre::add_checked(self.context.value_to_self_msat, our_funding_contribution);
10062+
// Early check for reserve requirement, assuming maximum balance of full channel value
10063+
// This will also be checked later at tx_complete
10064+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
10065+
10066+
// We need the current funding tx as an extra input
10067+
let prev_funding_input = pending_splice.get_input_of_previous_funding()?;
10068+
10069+
// Apply start of splice change in the state
10070+
self.context.splice_start(true, logger);
10071+
10072+
// Start interactive funding negotiation, with the previous funding transaction as an extra shared input
10073+
let tx_msg_opt = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id, Some(prev_funding_input))
10074+
.map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?;
10075+
Ok(tx_msg_opt)
10076+
}
10077+
996810078
/// Creates a new dual-funded channel from a remote side's request for one.
996910079
/// Assumes chain_hash has already been checked and corresponds with what we expect!
997010080
#[allow(dead_code)] // TODO(dual_funding): Remove once V2 channels is enabled.
@@ -10149,21 +10259,27 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
1014910259
self.generate_accept_channel_v2_message()
1015010260
}
1015110261

10152-
pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result<FundedChannel<SP>, ChannelError>{
10153-
let holder_commitment_point = self.unfunded_context.holder_commitment_point.ok_or(ChannelError::close(
10154-
format!("Expected to have holder commitment points available upon finishing interactive tx construction for channel {}",
10155-
self.context.channel_id())))?;
10156-
let channel = FundedChannel {
10157-
context: self.context,
10158-
interactive_tx_signing_session: Some(signing_session),
10159-
holder_commitment_point,
10160-
#[cfg(splicing)]
10161-
pending_splice_pre: None,
10162-
#[cfg(splicing)]
10163-
pending_splice_post: self.pending_splice_post,
10164-
};
10262+
/// Handle splice_init
10263+
/// See also [`splice_init_checks`]
10264+
#[cfg(splicing)]
10265+
pub fn splice_init<ES: Deref, L: Deref>(
10266+
&mut self, _msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L,
10267+
) -> Result<msgs::SpliceAck,ChannelError> where ES::Target: EntropySource, L::Target: Logger {
10268+
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
10269+
let our_funding_contribution_satoshis = 0i64;
1016510270

10166-
Ok(channel)
10271+
// TODO(splicing): Store msg.funding_pubkey
10272+
10273+
// Apply start of splice change in the state
10274+
self.context.splice_start(false, logger);
10275+
10276+
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
10277+
10278+
// Start interactive funding negotiation. No extra input, as we are not the splice initiator
10279+
let _msg = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id, None)
10280+
.map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?;
10281+
10282+
Ok(splice_ack_msg)
1016710283
}
1016810284
}
1016910285

0 commit comments

Comments
 (0)