Skip to content

Commit 18d3bd7

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 18d3bd7

File tree

2 files changed

+202
-100
lines changed

2 files changed

+202
-100
lines changed

lightning/src/ln/channel.rs

Lines changed: 93 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,77 @@ 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+
Some((funding, interactive_tx_constructor))
1937+
} else {
1938+
// Replace the taken state for later error handling
1939+
pending_splice.funding_negotiation = Some(funding_negotiation);
1940+
None
1941+
}
1942+
})
1943+
.ok_or_else(|| AbortReason::InternalError("Got a tx_complete message in an invalid state"))
1944+
.and_then(|(mut funding, interactive_tx_constructor)| {
1945+
match chan.context.funding_tx_constructed(
19341946
&mut funding,
1935-
signing_session,
1947+
funding_outpoint,
19361948
true,
19371949
chan.holder_commitment_point.next_transaction_number(),
19381950
&&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-
}
1951+
) {
1952+
Ok(commitment_signed) => {
1953+
// Advance the state
1954+
pending_splice.funding_negotiation =
1955+
Some(FundingNegotiation::AwaitingSignatures { funding });
1956+
Ok((interactive_tx_constructor, commitment_signed))
1957+
},
1958+
Err(e) => {
1959+
// Restore the taken state for later error handling
1960+
pending_splice.funding_negotiation =
1961+
Some(FundingNegotiation::ConstructingTransaction { funding, interactive_tx_constructor });
1962+
Err(e)
1963+
},
1964+
}
1965+
})?
1966+
} else {
1967+
return Err(AbortReason::InternalError(
1968+
"Got a tx_complete message in an invalid state",
1969+
));
19501970
}
1951-
1952-
return Err(AbortReason::InternalError(
1953-
"Got a tx_complete message in an invalid state",
1954-
));
19551971
},
19561972
_ => {
19571973
debug_assert!(false);
19581974
return Err(AbortReason::InternalError(
19591975
"Got a tx_complete message in an invalid phase",
19601976
));
19611977
},
1962-
}
1978+
};
1979+
1980+
let signing_session = interactive_tx_constructor.into_signing_session();
1981+
self.context_mut().interactive_tx_signing_session = Some(signing_session);
1982+
Ok(commitment_signed)
19631983
}
19641984

19651985
pub fn force_shutdown(&mut self, closure_reason: ClosureReason) -> ShutdownResult {
@@ -6051,30 +6071,13 @@ where
60516071

60526072
#[rustfmt::skip]
60536073
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
6074+
&mut self, funding: &mut FundingScope, funding_outpoint: OutPoint, is_splice: bool,
6075+
holder_commitment_transaction_number: u64, logger: &L,
60566076
) -> Result<msgs::CommitmentSigned, AbortReason>
60576077
where
60586078
L::Target: Logger
60596079
{
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);
6080+
funding.channel_transaction_parameters.funding_outpoint = Some(funding_outpoint);
60786081

60796082
if is_splice {
60806083
debug_assert_eq!(
@@ -6091,7 +6094,6 @@ where
60916094
Some(commitment_signed) => commitment_signed,
60926095
// TODO(splicing): Support async signing
60936096
None => {
6094-
funding.channel_transaction_parameters.funding_outpoint = None;
60956097
return Err(AbortReason::InternalError("Failed to compute commitment_signed signatures"));
60966098
},
60976099
};
@@ -6167,8 +6169,6 @@ where
61676169
SP::Target: SignerProvider,
61686170
L::Target: Logger,
61696171
{
6170-
debug_assert!(self.interactive_tx_signing_session.is_some());
6171-
61726172
let signatures = self.get_initial_counterparty_commitment_signatures(funding, logger);
61736173
if let Some((signature, htlc_signatures)) = signatures {
61746174
log_info!(
@@ -6508,9 +6508,9 @@ impl FundingNegotiationContext {
65086508
/// Prepare and start interactive transaction negotiation.
65096509
/// If error occurs, it is caused by our side, not the counterparty.
65106510
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
6511-
self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6511+
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
65126512
entropy_source: &ES, holder_node_id: PublicKey,
6513-
) -> Result<InteractiveTxConstructor, AbortReason>
6513+
) -> Result<InteractiveTxConstructor, NegotiationError>
65146514
where
65156515
SP::Target: SignerProvider,
65166516
ES::Target: EntropySource,
@@ -6536,25 +6536,32 @@ impl FundingNegotiationContext {
65366536

65376537
// Optionally add change output
65386538
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6539-
calculate_change_output_value(
6539+
match calculate_change_output_value(
65406540
&self,
65416541
self.shared_funding_input.is_some(),
65426542
&shared_funding_output.script_pubkey,
65436543
context.holder_dust_limit_satoshis,
6544-
)?
6544+
) {
6545+
Ok(change_value_opt) => change_value_opt,
6546+
Err(reason) => {
6547+
return Err(self.into_negotiation_error(reason));
6548+
},
6549+
}
65456550
} else {
65466551
None
65476552
};
65486553

6549-
let mut funding_outputs = self.our_funding_outputs;
6550-
65516554
if let Some(change_value) = change_value_opt {
65526555
let change_script = if let Some(script) = self.change_script {
65536556
script
65546557
} else {
6555-
signer_provider
6556-
.get_destination_script(context.channel_keys_id)
6557-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6558+
match signer_provider.get_destination_script(context.channel_keys_id) {
6559+
Ok(script) => script,
6560+
Err(_) => {
6561+
let reason = AbortReason::InternalError("Error getting change script");
6562+
return Err(self.into_negotiation_error(reason));
6563+
},
6564+
}
65586565
};
65596566
let mut change_output =
65606567
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
@@ -6565,7 +6572,7 @@ impl FundingNegotiationContext {
65656572
// Check dust limit again
65666573
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
65676574
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6568-
funding_outputs.push(change_output);
6575+
self.our_funding_outputs.push(change_output);
65696576
}
65706577
}
65716578

@@ -6583,10 +6590,19 @@ impl FundingNegotiationContext {
65836590
shared_funding_output,
65846591
funding.value_to_self_msat / 1000,
65856592
),
6586-
outputs_to_contribute: funding_outputs,
6593+
outputs_to_contribute: self.our_funding_outputs,
65876594
};
65886595
InteractiveTxConstructor::new(constructor_args)
65896596
}
6597+
6598+
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
6599+
let contributed_inputs =
6600+
self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect();
6601+
6602+
let contributed_outputs = self.our_funding_outputs;
6603+
6604+
NegotiationError { reason, contributed_inputs, contributed_outputs }
6605+
}
65906606
}
65916607

65926608
// Holder designates channel data owned for the benefit of the user client.
@@ -13660,8 +13676,8 @@ where
1366013676
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1366113677
}
1366213678
).map_err(|err| {
13663-
let reason = ClosureReason::ProcessingError { err: err.to_string() };
13664-
ChannelError::Close((err.to_string(), reason))
13679+
let reason = ClosureReason::ProcessingError { err: err.reason.to_string() };
13680+
ChannelError::Close((err.reason.to_string(), reason))
1366513681
})?);
1366613682

1366713683
let unfunded_context = UnfundedChannelContext {

0 commit comments

Comments
 (0)