Skip to content

Commit 81495c6

Browse files
authored
Merge pull request #4123 from jkczyz/2025-09-negotiation-error
Refactor interactive-tx construction and uses
2 parents cfe2a1e + 0956efd commit 81495c6

File tree

2 files changed

+324
-189
lines changed

2 files changed

+324
-189
lines changed

lightning/src/ln/channel.rs

Lines changed: 101 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ use crate::ln::funding::{FundingTxInput, SpliceContribution};
6060
use crate::ln::interactivetxs::{
6161
calculate_change_output_value, get_output_weight, AbortReason, HandleTxCompleteValue,
6262
InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend,
63-
InteractiveTxSigningSession, SharedOwnedInput, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT,
63+
InteractiveTxSigningSession, NegotiationError, SharedOwnedInput, SharedOwnedOutput,
64+
TX_COMMON_FIELDS_WEIGHT,
6465
};
6566
use crate::ln::msgs;
6667
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket};
@@ -1787,20 +1788,22 @@ where
17871788

17881789
let (interactive_tx_msg_send, negotiation_complete) = match tx_complete_action {
17891790
HandleTxCompleteValue::SendTxMessage(interactive_tx_msg_send) => {
1790-
(Some(interactive_tx_msg_send), false)
1791+
(Some(interactive_tx_msg_send), None)
17911792
},
1792-
HandleTxCompleteValue::SendTxComplete(
1793+
HandleTxCompleteValue::NegotiationComplete(
17931794
interactive_tx_msg_send,
1794-
negotiation_complete,
1795-
) => (Some(interactive_tx_msg_send), negotiation_complete),
1796-
HandleTxCompleteValue::NegotiationComplete => (None, true),
1795+
funding_outpoint,
1796+
) => (interactive_tx_msg_send, Some(funding_outpoint)),
17971797
};
1798-
if !negotiation_complete {
1798+
1799+
let funding_outpoint = if let Some(funding_outpoint) = negotiation_complete {
1800+
funding_outpoint
1801+
} else {
17991802
return Ok((interactive_tx_msg_send, None));
1800-
}
1803+
};
18011804

18021805
let commitment_signed = self
1803-
.funding_tx_constructed(logger)
1806+
.funding_tx_constructed(funding_outpoint, logger)
18041807
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
18051808
Ok((interactive_tx_msg_send, Some(commitment_signed)))
18061809
}
@@ -1891,13 +1894,13 @@ where
18911894
}
18921895

18931896
fn funding_tx_constructed<L: Deref>(
1894-
&mut self, logger: &L,
1897+
&mut self, funding_outpoint: OutPoint, logger: &L,
18951898
) -> Result<msgs::CommitmentSigned, AbortReason>
18961899
where
18971900
L::Target: Logger,
18981901
{
18991902
let logger = WithChannelContext::from(logger, self.context(), None);
1900-
match &mut self.phase {
1903+
let (interactive_tx_constructor, commitment_signed) = match &mut self.phase {
19011904
ChannelPhase::UnfundedV2(chan) => {
19021905
debug_assert_eq!(
19031906
chan.context.channel_state,
@@ -1907,60 +1910,85 @@ where
19071910
),
19081911
);
19091912

1910-
let signing_session = chan
1913+
let interactive_tx_constructor = chan
19111914
.interactive_tx_constructor
19121915
.take()
1913-
.expect("PendingV2Channel::interactive_tx_constructor should be set")
1914-
.into_signing_session();
1916+
.expect("PendingV2Channel::interactive_tx_constructor should be set");
19151917
let commitment_signed = chan.context.funding_tx_constructed(
19161918
&mut chan.funding,
1917-
signing_session,
1919+
funding_outpoint,
19181920
false,
19191921
chan.unfunded_context.transaction_number(),
19201922
&&logger,
19211923
)?;
19221924

1923-
return Ok(commitment_signed);
1925+
(interactive_tx_constructor, commitment_signed)
19241926
},
19251927
ChannelPhase::Funded(chan) => {
19261928
if let Some(pending_splice) = chan.pending_splice.as_mut() {
1927-
if let Some(funding_negotiation) = pending_splice.funding_negotiation.take() {
1928-
if let FundingNegotiation::ConstructingTransaction {
1929-
mut funding,
1930-
interactive_tx_constructor,
1931-
} = funding_negotiation
1932-
{
1933-
let signing_session = interactive_tx_constructor.into_signing_session();
1934-
let commitment_signed = chan.context.funding_tx_constructed(
1929+
pending_splice
1930+
.funding_negotiation
1931+
.take()
1932+
.and_then(|funding_negotiation| {
1933+
if let FundingNegotiation::ConstructingTransaction {
1934+
funding,
1935+
interactive_tx_constructor,
1936+
} = funding_negotiation
1937+
{
1938+
Some((funding, interactive_tx_constructor))
1939+
} else {
1940+
// Replace the taken state for later error handling
1941+
pending_splice.funding_negotiation = Some(funding_negotiation);
1942+
None
1943+
}
1944+
})
1945+
.ok_or_else(|| {
1946+
AbortReason::InternalError(
1947+
"Got a tx_complete message in an invalid state",
1948+
)
1949+
})
1950+
.and_then(|(mut funding, interactive_tx_constructor)| {
1951+
match chan.context.funding_tx_constructed(
19351952
&mut funding,
1936-
signing_session,
1953+
funding_outpoint,
19371954
true,
19381955
chan.holder_commitment_point.next_transaction_number(),
19391956
&&logger,
1940-
)?;
1941-
1942-
pending_splice.funding_negotiation =
1943-
Some(FundingNegotiation::AwaitingSignatures { funding });
1944-
1945-
return Ok(commitment_signed);
1946-
} else {
1947-
// Replace the taken state
1948-
pending_splice.funding_negotiation = Some(funding_negotiation);
1949-
}
1950-
}
1957+
) {
1958+
Ok(commitment_signed) => {
1959+
// Advance the state
1960+
pending_splice.funding_negotiation =
1961+
Some(FundingNegotiation::AwaitingSignatures { funding });
1962+
Ok((interactive_tx_constructor, commitment_signed))
1963+
},
1964+
Err(e) => {
1965+
// Restore the taken state for later error handling
1966+
pending_splice.funding_negotiation =
1967+
Some(FundingNegotiation::ConstructingTransaction {
1968+
funding,
1969+
interactive_tx_constructor,
1970+
});
1971+
Err(e)
1972+
},
1973+
}
1974+
})?
1975+
} else {
1976+
return Err(AbortReason::InternalError(
1977+
"Got a tx_complete message in an invalid state",
1978+
));
19511979
}
1952-
1953-
return Err(AbortReason::InternalError(
1954-
"Got a tx_complete message in an invalid state",
1955-
));
19561980
},
19571981
_ => {
19581982
debug_assert!(false);
19591983
return Err(AbortReason::InternalError(
19601984
"Got a tx_complete message in an invalid phase",
19611985
));
19621986
},
1963-
}
1987+
};
1988+
1989+
let signing_session = interactive_tx_constructor.into_signing_session();
1990+
self.context_mut().interactive_tx_signing_session = Some(signing_session);
1991+
Ok(commitment_signed)
19641992
}
19651993

19661994
pub fn force_shutdown(&mut self, closure_reason: ClosureReason) -> ShutdownResult {
@@ -6061,30 +6089,13 @@ where
60616089

60626090
#[rustfmt::skip]
60636091
fn funding_tx_constructed<L: Deref>(
6064-
&mut self, funding: &mut FundingScope, signing_session: InteractiveTxSigningSession,
6065-
is_splice: bool, holder_commitment_transaction_number: u64, logger: &L
6092+
&mut self, funding: &mut FundingScope, funding_outpoint: OutPoint, is_splice: bool,
6093+
holder_commitment_transaction_number: u64, logger: &L,
60666094
) -> Result<msgs::CommitmentSigned, AbortReason>
60676095
where
60686096
L::Target: Logger
60696097
{
6070-
let mut output_index = None;
6071-
let expected_spk = funding.get_funding_redeemscript().to_p2wsh();
6072-
for (idx, outp) in signing_session.unsigned_tx().tx().output.iter().enumerate() {
6073-
if outp.script_pubkey == expected_spk && outp.value.to_sat() == funding.get_value_satoshis() {
6074-
if output_index.is_some() {
6075-
return Err(AbortReason::DuplicateFundingOutput);
6076-
}
6077-
output_index = Some(idx as u16);
6078-
}
6079-
}
6080-
let outpoint = if let Some(output_index) = output_index {
6081-
OutPoint { txid: signing_session.unsigned_tx().compute_txid(), index: output_index }
6082-
} else {
6083-
return Err(AbortReason::MissingFundingOutput);
6084-
};
6085-
funding
6086-
.channel_transaction_parameters.funding_outpoint = Some(outpoint);
6087-
self.interactive_tx_signing_session = Some(signing_session);
6098+
funding.channel_transaction_parameters.funding_outpoint = Some(funding_outpoint);
60886099

60896100
if is_splice {
60906101
debug_assert_eq!(
@@ -6101,7 +6112,6 @@ where
61016112
Some(commitment_signed) => commitment_signed,
61026113
// TODO(splicing): Support async signing
61036114
None => {
6104-
funding.channel_transaction_parameters.funding_outpoint = None;
61056115
return Err(AbortReason::InternalError("Failed to compute commitment_signed signatures"));
61066116
},
61076117
};
@@ -6177,8 +6187,6 @@ where
61776187
SP::Target: SignerProvider,
61786188
L::Target: Logger,
61796189
{
6180-
debug_assert!(self.interactive_tx_signing_session.is_some());
6181-
61826190
let signatures = self.get_initial_counterparty_commitment_signatures(funding, logger);
61836191
if let Some((signature, htlc_signatures)) = signatures {
61846192
log_info!(
@@ -6518,9 +6526,9 @@ impl FundingNegotiationContext {
65186526
/// Prepare and start interactive transaction negotiation.
65196527
/// If error occurs, it is caused by our side, not the counterparty.
65206528
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
6521-
self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6529+
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
65226530
entropy_source: &ES, holder_node_id: PublicKey,
6523-
) -> Result<InteractiveTxConstructor, AbortReason>
6531+
) -> Result<InteractiveTxConstructor, NegotiationError>
65246532
where
65256533
SP::Target: SignerProvider,
65266534
ES::Target: EntropySource,
@@ -6546,25 +6554,32 @@ impl FundingNegotiationContext {
65466554

65476555
// Optionally add change output
65486556
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6549-
calculate_change_output_value(
6557+
match calculate_change_output_value(
65506558
&self,
65516559
self.shared_funding_input.is_some(),
65526560
&shared_funding_output.script_pubkey,
65536561
context.holder_dust_limit_satoshis,
6554-
)?
6562+
) {
6563+
Ok(change_value_opt) => change_value_opt,
6564+
Err(reason) => {
6565+
return Err(self.into_negotiation_error(reason));
6566+
},
6567+
}
65556568
} else {
65566569
None
65576570
};
65586571

6559-
let mut funding_outputs = self.our_funding_outputs;
6560-
65616572
if let Some(change_value) = change_value_opt {
65626573
let change_script = if let Some(script) = self.change_script {
65636574
script
65646575
} else {
6565-
signer_provider
6566-
.get_destination_script(context.channel_keys_id)
6567-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6576+
match signer_provider.get_destination_script(context.channel_keys_id) {
6577+
Ok(script) => script,
6578+
Err(_) => {
6579+
let reason = AbortReason::InternalError("Error getting change script");
6580+
return Err(self.into_negotiation_error(reason));
6581+
},
6582+
}
65686583
};
65696584
let mut change_output =
65706585
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
@@ -6575,7 +6590,7 @@ impl FundingNegotiationContext {
65756590
// Check dust limit again
65766591
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
65776592
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6578-
funding_outputs.push(change_output);
6593+
self.our_funding_outputs.push(change_output);
65796594
}
65806595
}
65816596

@@ -6593,10 +6608,19 @@ impl FundingNegotiationContext {
65936608
shared_funding_output,
65946609
funding.value_to_self_msat / 1000,
65956610
),
6596-
outputs_to_contribute: funding_outputs,
6611+
outputs_to_contribute: self.our_funding_outputs,
65976612
};
65986613
InteractiveTxConstructor::new(constructor_args)
65996614
}
6615+
6616+
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
6617+
let contributed_inputs =
6618+
self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect();
6619+
6620+
let contributed_outputs = self.our_funding_outputs;
6621+
6622+
NegotiationError { reason, contributed_inputs, contributed_outputs }
6623+
}
66006624
}
66016625

66026626
// Holder designates channel data owned for the benefit of the user client.
@@ -13737,8 +13761,8 @@ where
1373713761
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1373813762
}
1373913763
).map_err(|err| {
13740-
let reason = ClosureReason::ProcessingError { err: err.to_string() };
13741-
ChannelError::Close((err.to_string(), reason))
13764+
let reason = ClosureReason::ProcessingError { err: err.reason.to_string() };
13765+
ChannelError::Close((err.reason.to_string(), reason))
1374213766
})?);
1374313767

1374413768
let unfunded_context = UnfundedChannelContext {

0 commit comments

Comments
 (0)