Skip to content

Commit 0956efd

Browse files
committed
Consume InteractiveTxConstructor after error checks
InteractiveTxConstructor contains the users contributed inputs. When an interactive tx sessions is aborted, the user will need to be notified with an event indicating which inputs and outputs were contributed. This allows them to re-use inputs that are no longer in use. This commit ensures the InteractiveTxConstructor is only consumed after all error checking. That way, in the case of a failure, we're able to produce an event from its input data.
1 parent 0ecbe62 commit 0956efd

File tree

2 files changed

+207
-100
lines changed

2 files changed

+207
-100
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};
@@ -1794,20 +1795,22 @@ where
17941795

17951796
let (interactive_tx_msg_send, negotiation_complete) = match tx_complete_action {
17961797
HandleTxCompleteValue::SendTxMessage(interactive_tx_msg_send) => {
1797-
(Some(interactive_tx_msg_send), false)
1798+
(Some(interactive_tx_msg_send), None)
17981799
},
1799-
HandleTxCompleteValue::SendTxComplete(
1800+
HandleTxCompleteValue::NegotiationComplete(
18001801
interactive_tx_msg_send,
1801-
negotiation_complete,
1802-
) => (Some(interactive_tx_msg_send), negotiation_complete),
1803-
HandleTxCompleteValue::NegotiationComplete => (None, true),
1802+
funding_outpoint,
1803+
) => (interactive_tx_msg_send, Some(funding_outpoint)),
18041804
};
1805-
if !negotiation_complete {
1805+
1806+
let funding_outpoint = if let Some(funding_outpoint) = negotiation_complete {
1807+
funding_outpoint
1808+
} else {
18061809
return Ok((interactive_tx_msg_send, None));
1807-
}
1810+
};
18081811

18091812
let commitment_signed = self
1810-
.funding_tx_constructed(logger)
1813+
.funding_tx_constructed(funding_outpoint, logger)
18111814
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
18121815
Ok((interactive_tx_msg_send, Some(commitment_signed)))
18131816
}
@@ -1890,13 +1893,13 @@ where
18901893
}
18911894

18921895
fn funding_tx_constructed<L: Deref>(
1893-
&mut self, logger: &L,
1896+
&mut self, funding_outpoint: OutPoint, logger: &L,
18941897
) -> Result<msgs::CommitmentSigned, AbortReason>
18951898
where
18961899
L::Target: Logger,
18971900
{
18981901
let logger = WithChannelContext::from(logger, self.context(), None);
1899-
match &mut self.phase {
1902+
let (interactive_tx_constructor, commitment_signed) = match &mut self.phase {
19001903
ChannelPhase::UnfundedV2(chan) => {
19011904
debug_assert_eq!(
19021905
chan.context.channel_state,
@@ -1906,60 +1909,85 @@ where
19061909
),
19071910
);
19081911

1909-
let signing_session = chan
1912+
let interactive_tx_constructor = chan
19101913
.interactive_tx_constructor
19111914
.take()
1912-
.expect("PendingV2Channel::interactive_tx_constructor should be set")
1913-
.into_signing_session();
1915+
.expect("PendingV2Channel::interactive_tx_constructor should be set");
19141916
let commitment_signed = chan.context.funding_tx_constructed(
19151917
&mut chan.funding,
1916-
signing_session,
1918+
funding_outpoint,
19171919
false,
19181920
chan.unfunded_context.transaction_number(),
19191921
&&logger,
19201922
)?;
19211923

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

19651993
pub fn force_shutdown(&mut self, closure_reason: ClosureReason) -> ShutdownResult {
@@ -6051,30 +6079,13 @@ where
60516079

60526080
#[rustfmt::skip]
60536081
fn funding_tx_constructed<L: Deref>(
6054-
&mut self, funding: &mut FundingScope, signing_session: InteractiveTxSigningSession,
6055-
is_splice: bool, holder_commitment_transaction_number: u64, logger: &L
6082+
&mut self, funding: &mut FundingScope, funding_outpoint: OutPoint, is_splice: bool,
6083+
holder_commitment_transaction_number: u64, logger: &L,
60566084
) -> Result<msgs::CommitmentSigned, AbortReason>
60576085
where
60586086
L::Target: Logger
60596087
{
6060-
let mut output_index = None;
6061-
let expected_spk = funding.get_funding_redeemscript().to_p2wsh();
6062-
for (idx, outp) in signing_session.unsigned_tx().tx().output.iter().enumerate() {
6063-
if outp.script_pubkey == expected_spk && outp.value.to_sat() == funding.get_value_satoshis() {
6064-
if output_index.is_some() {
6065-
return Err(AbortReason::DuplicateFundingOutput);
6066-
}
6067-
output_index = Some(idx as u16);
6068-
}
6069-
}
6070-
let outpoint = if let Some(output_index) = output_index {
6071-
OutPoint { txid: signing_session.unsigned_tx().compute_txid(), index: output_index }
6072-
} else {
6073-
return Err(AbortReason::MissingFundingOutput);
6074-
};
6075-
funding
6076-
.channel_transaction_parameters.funding_outpoint = Some(outpoint);
6077-
self.interactive_tx_signing_session = Some(signing_session);
6088+
funding.channel_transaction_parameters.funding_outpoint = Some(funding_outpoint);
60786089

60796090
if is_splice {
60806091
debug_assert_eq!(
@@ -6091,7 +6102,6 @@ where
60916102
Some(commitment_signed) => commitment_signed,
60926103
// TODO(splicing): Support async signing
60936104
None => {
6094-
funding.channel_transaction_parameters.funding_outpoint = None;
60956105
return Err(AbortReason::InternalError("Failed to compute commitment_signed signatures"));
60966106
},
60976107
};
@@ -6167,8 +6177,6 @@ where
61676177
SP::Target: SignerProvider,
61686178
L::Target: Logger,
61696179
{
6170-
debug_assert!(self.interactive_tx_signing_session.is_some());
6171-
61726180
let signatures = self.get_initial_counterparty_commitment_signatures(funding, logger);
61736181
if let Some((signature, htlc_signatures)) = signatures {
61746182
log_info!(
@@ -6508,9 +6516,9 @@ impl FundingNegotiationContext {
65086516
/// Prepare and start interactive transaction negotiation.
65096517
/// If error occurs, it is caused by our side, not the counterparty.
65106518
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
6511-
self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6519+
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
65126520
entropy_source: &ES, holder_node_id: PublicKey,
6513-
) -> Result<InteractiveTxConstructor, AbortReason>
6521+
) -> Result<InteractiveTxConstructor, NegotiationError>
65146522
where
65156523
SP::Target: SignerProvider,
65166524
ES::Target: EntropySource,
@@ -6536,25 +6544,32 @@ impl FundingNegotiationContext {
65366544

65376545
// Optionally add change output
65386546
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6539-
calculate_change_output_value(
6547+
match calculate_change_output_value(
65406548
&self,
65416549
self.shared_funding_input.is_some(),
65426550
&shared_funding_output.script_pubkey,
65436551
context.holder_dust_limit_satoshis,
6544-
)?
6552+
) {
6553+
Ok(change_value_opt) => change_value_opt,
6554+
Err(reason) => {
6555+
return Err(self.into_negotiation_error(reason));
6556+
},
6557+
}
65456558
} else {
65466559
None
65476560
};
65486561

6549-
let mut funding_outputs = self.our_funding_outputs;
6550-
65516562
if let Some(change_value) = change_value_opt {
65526563
let change_script = if let Some(script) = self.change_script {
65536564
script
65546565
} else {
6555-
signer_provider
6556-
.get_destination_script(context.channel_keys_id)
6557-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6566+
match signer_provider.get_destination_script(context.channel_keys_id) {
6567+
Ok(script) => script,
6568+
Err(_) => {
6569+
let reason = AbortReason::InternalError("Error getting change script");
6570+
return Err(self.into_negotiation_error(reason));
6571+
},
6572+
}
65586573
};
65596574
let mut change_output =
65606575
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
@@ -6565,7 +6580,7 @@ impl FundingNegotiationContext {
65656580
// Check dust limit again
65666581
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
65676582
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6568-
funding_outputs.push(change_output);
6583+
self.our_funding_outputs.push(change_output);
65696584
}
65706585
}
65716586

@@ -6583,10 +6598,19 @@ impl FundingNegotiationContext {
65836598
shared_funding_output,
65846599
funding.value_to_self_msat / 1000,
65856600
),
6586-
outputs_to_contribute: funding_outputs,
6601+
outputs_to_contribute: self.our_funding_outputs,
65876602
};
65886603
InteractiveTxConstructor::new(constructor_args)
65896604
}
6605+
6606+
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
6607+
let contributed_inputs =
6608+
self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect();
6609+
6610+
let contributed_outputs = self.our_funding_outputs;
6611+
6612+
NegotiationError { reason, contributed_inputs, contributed_outputs }
6613+
}
65906614
}
65916615

65926616
// Holder designates channel data owned for the benefit of the user client.
@@ -13660,8 +13684,8 @@ where
1366013684
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1366113685
}
1366213686
).map_err(|err| {
13663-
let reason = ClosureReason::ProcessingError { err: err.to_string() };
13664-
ChannelError::Close((err.to_string(), reason))
13687+
let reason = ClosureReason::ProcessingError { err: err.reason.to_string() };
13688+
ChannelError::Close((err.reason.to_string(), reason))
1366513689
})?);
1366613690

1366713691
let unfunded_context = UnfundedChannelContext {

0 commit comments

Comments
 (0)