Skip to content

Commit 59f7deb

Browse files
committed
Use NegotiationError to include contributed inputs and outputs
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 introduces a NegotiationError type to achieve this, which includes the inputs and outputs along with an AbortReason.
1 parent 46e1e03 commit 59f7deb

File tree

2 files changed

+185
-67
lines changed

2 files changed

+185
-67
lines changed

lightning/src/ln/channel.rs

Lines changed: 97 additions & 47 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};
@@ -1678,12 +1679,13 @@ where
16781679
}
16791680

16801681
fn fail_interactive_tx_negotiation<L: Deref>(
1681-
&mut self, reason: AbortReason, logger: &L,
1682+
&mut self, error: NegotiationError, logger: &L,
16821683
) -> msgs::TxAbort
16831684
where
16841685
L::Target: Logger,
16851686
{
16861687
let logger = WithChannelContext::from(logger, &self.context(), None);
1688+
let NegotiationError { reason, .. } = error;
16871689
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
16881690

16891691
let _interactive_tx_constructor = match &mut self.phase {
@@ -1720,11 +1722,15 @@ where
17201722
{
17211723
match self.interactive_tx_constructor_mut() {
17221724
Some(interactive_tx_constructor) => interactive_tx_constructor.handle_tx_add_input(msg),
1723-
None => Err(AbortReason::InternalError(
1724-
"Received unexpected interactive transaction negotiation message",
1725-
)),
1725+
None => Err(NegotiationError {
1726+
reason: AbortReason::InternalError(
1727+
"Received unexpected interactive transaction negotiation message",
1728+
),
1729+
contributed_inputs: Vec::new(),
1730+
contributed_outputs: Vec::new(),
1731+
}),
17261732
}
1727-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1733+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17281734
}
17291735

17301736
pub fn tx_add_output<L: Deref>(
@@ -1737,11 +1743,15 @@ where
17371743
Some(interactive_tx_constructor) => {
17381744
interactive_tx_constructor.handle_tx_add_output(msg)
17391745
},
1740-
None => Err(AbortReason::InternalError(
1741-
"Received unexpected interactive transaction negotiation message",
1742-
)),
1746+
None => Err(NegotiationError {
1747+
reason: AbortReason::InternalError(
1748+
"Received unexpected interactive transaction negotiation message",
1749+
),
1750+
contributed_inputs: Vec::new(),
1751+
contributed_outputs: Vec::new(),
1752+
}),
17431753
}
1744-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1754+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17451755
}
17461756

17471757
pub fn tx_remove_input<L: Deref>(
@@ -1754,11 +1764,15 @@ where
17541764
Some(interactive_tx_constructor) => {
17551765
interactive_tx_constructor.handle_tx_remove_input(msg)
17561766
},
1757-
None => Err(AbortReason::InternalError(
1758-
"Received unexpected interactive transaction negotiation message",
1759-
)),
1767+
None => Err(NegotiationError {
1768+
reason: AbortReason::InternalError(
1769+
"Received unexpected interactive transaction negotiation message",
1770+
),
1771+
contributed_inputs: Vec::new(),
1772+
contributed_outputs: Vec::new(),
1773+
}),
17601774
}
1761-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1775+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17621776
}
17631777

17641778
pub fn tx_remove_output<L: Deref>(
@@ -1771,11 +1785,15 @@ where
17711785
Some(interactive_tx_constructor) => {
17721786
interactive_tx_constructor.handle_tx_remove_output(msg)
17731787
},
1774-
None => Err(AbortReason::InternalError(
1775-
"Received unexpected interactive transaction negotiation message",
1776-
)),
1788+
None => Err(NegotiationError {
1789+
reason: AbortReason::InternalError(
1790+
"Received unexpected interactive transaction negotiation message",
1791+
),
1792+
contributed_inputs: Vec::new(),
1793+
contributed_outputs: Vec::new(),
1794+
}),
17771795
}
1778-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1796+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17791797
}
17801798

17811799
pub fn tx_complete<L: Deref>(
@@ -1786,11 +1804,15 @@ where
17861804
{
17871805
let tx_complete_action = match self.interactive_tx_constructor_mut() {
17881806
Some(interactive_tx_constructor) => interactive_tx_constructor.handle_tx_complete(msg),
1789-
None => Err(AbortReason::InternalError(
1790-
"Received unexpected interactive transaction negotiation message",
1791-
)),
1807+
None => Err(NegotiationError {
1808+
reason: AbortReason::InternalError(
1809+
"Received unexpected interactive transaction negotiation message",
1810+
),
1811+
contributed_inputs: Vec::new(),
1812+
contributed_outputs: Vec::new(),
1813+
}),
17921814
}
1793-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
1815+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))?;
17941816

17951817
let (interactive_tx_msg_send, negotiation_complete) = match tx_complete_action {
17961818
HandleTxCompleteValue::SendTxMessage(interactive_tx_msg_send) => {
@@ -1808,7 +1830,7 @@ where
18081830

18091831
let commitment_signed = self
18101832
.funding_tx_constructed(logger)
1811-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
1833+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))?;
18121834
Ok((interactive_tx_msg_send, Some(commitment_signed)))
18131835
}
18141836

@@ -1891,7 +1913,7 @@ where
18911913

18921914
fn funding_tx_constructed<L: Deref>(
18931915
&mut self, logger: &L,
1894-
) -> Result<msgs::CommitmentSigned, AbortReason>
1916+
) -> Result<msgs::CommitmentSigned, NegotiationError>
18951917
where
18961918
L::Target: Logger,
18971919
{
@@ -1949,15 +1971,23 @@ where
19491971
}
19501972
}
19511973

1952-
return Err(AbortReason::InternalError(
1953-
"Got a tx_complete message in an invalid state",
1954-
));
1974+
return Err(NegotiationError {
1975+
reason: AbortReason::InternalError(
1976+
"Got a tx_complete message in an invalid state",
1977+
),
1978+
contributed_inputs: Vec::new(),
1979+
contributed_outputs: Vec::new(),
1980+
});
19551981
},
19561982
_ => {
19571983
debug_assert!(false);
1958-
return Err(AbortReason::InternalError(
1959-
"Got a tx_complete message in an invalid phase",
1960-
));
1984+
return Err(NegotiationError {
1985+
reason: AbortReason::InternalError(
1986+
"Got a tx_complete message in an invalid phase",
1987+
),
1988+
contributed_inputs: Vec::new(),
1989+
contributed_outputs: Vec::new(),
1990+
});
19611991
},
19621992
}
19631993
}
@@ -6053,7 +6083,7 @@ where
60536083
fn funding_tx_constructed<L: Deref>(
60546084
&mut self, funding: &mut FundingScope, signing_session: InteractiveTxSigningSession,
60556085
is_splice: bool, holder_commitment_transaction_number: u64, logger: &L
6056-
) -> Result<msgs::CommitmentSigned, AbortReason>
6086+
) -> Result<msgs::CommitmentSigned, NegotiationError>
60576087
where
60586088
L::Target: Logger
60596089
{
@@ -6062,15 +6092,17 @@ where
60626092
for (idx, outp) in signing_session.unsigned_tx().tx().output.iter().enumerate() {
60636093
if outp.script_pubkey == expected_spk && outp.value.to_sat() == funding.get_value_satoshis() {
60646094
if output_index.is_some() {
6065-
return Err(AbortReason::DuplicateFundingOutput);
6095+
let reason = AbortReason::DuplicateFundingOutput;
6096+
return Err(signing_session.into_negotiation_error(reason));
60666097
}
60676098
output_index = Some(idx as u16);
60686099
}
60696100
}
60706101
let outpoint = if let Some(output_index) = output_index {
60716102
OutPoint { txid: signing_session.unsigned_tx().compute_txid(), index: output_index }
60726103
} else {
6073-
return Err(AbortReason::MissingFundingOutput);
6104+
let reason = AbortReason::MissingFundingOutput;
6105+
return Err(signing_session.into_negotiation_error(reason));
60746106
};
60756107
funding
60766108
.channel_transaction_parameters.funding_outpoint = Some(outpoint);
@@ -6092,7 +6124,9 @@ where
60926124
// TODO(splicing): Support async signing
60936125
None => {
60946126
funding.channel_transaction_parameters.funding_outpoint = None;
6095-
return Err(AbortReason::InternalError("Failed to compute commitment_signed signatures"));
6127+
let signing_session = self.interactive_tx_signing_session.take().unwrap();
6128+
let reason = AbortReason::InternalError("Failed to compute commitment_signed signatures");
6129+
return Err(signing_session.into_negotiation_error(reason));
60966130
},
60976131
};
60986132

@@ -6508,9 +6542,9 @@ impl FundingNegotiationContext {
65086542
/// Prepare and start interactive transaction negotiation.
65096543
/// If error occurs, it is caused by our side, not the counterparty.
65106544
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
6511-
self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6545+
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
65126546
entropy_source: &ES, holder_node_id: PublicKey,
6513-
) -> Result<InteractiveTxConstructor, AbortReason>
6547+
) -> Result<InteractiveTxConstructor, NegotiationError>
65146548
where
65156549
SP::Target: SignerProvider,
65166550
ES::Target: EntropySource,
@@ -6536,25 +6570,32 @@ impl FundingNegotiationContext {
65366570

65376571
// Optionally add change output
65386572
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6539-
calculate_change_output_value(
6573+
match calculate_change_output_value(
65406574
&self,
65416575
self.shared_funding_input.is_some(),
65426576
&shared_funding_output.script_pubkey,
65436577
context.holder_dust_limit_satoshis,
6544-
)?
6578+
) {
6579+
Ok(change_value_opt) => change_value_opt,
6580+
Err(reason) => {
6581+
return Err(self.into_negotiation_error(reason));
6582+
},
6583+
}
65456584
} else {
65466585
None
65476586
};
65486587

6549-
let mut funding_outputs = self.our_funding_outputs;
6550-
65516588
if let Some(change_value) = change_value_opt {
65526589
let change_script = if let Some(script) = self.change_script {
65536590
script
65546591
} else {
6555-
signer_provider
6556-
.get_destination_script(context.channel_keys_id)
6557-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6592+
match signer_provider.get_destination_script(context.channel_keys_id) {
6593+
Ok(script) => script,
6594+
Err(_) => {
6595+
let reason = AbortReason::InternalError("Error getting change script");
6596+
return Err(self.into_negotiation_error(reason));
6597+
},
6598+
}
65586599
};
65596600
let mut change_output =
65606601
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
@@ -6565,7 +6606,7 @@ impl FundingNegotiationContext {
65656606
// Check dust limit again
65666607
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
65676608
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6568-
funding_outputs.push(change_output);
6609+
self.our_funding_outputs.push(change_output);
65696610
}
65706611
}
65716612

@@ -6583,10 +6624,19 @@ impl FundingNegotiationContext {
65836624
shared_funding_output,
65846625
funding.value_to_self_msat / 1000,
65856626
),
6586-
outputs_to_contribute: funding_outputs,
6627+
outputs_to_contribute: self.our_funding_outputs,
65876628
};
65886629
InteractiveTxConstructor::new(constructor_args)
65896630
}
6631+
6632+
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
6633+
let contributed_inputs =
6634+
self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect();
6635+
6636+
let contributed_outputs = self.our_funding_outputs;
6637+
6638+
NegotiationError { reason, contributed_inputs, contributed_outputs }
6639+
}
65906640
}
65916641

65926642
// Holder designates channel data owned for the benefit of the user client.
@@ -13660,8 +13710,8 @@ where
1366013710
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1366113711
}
1366213712
).map_err(|err| {
13663-
let reason = ClosureReason::ProcessingError { err: err.to_string() };
13664-
ChannelError::Close((err.to_string(), reason))
13713+
let reason = ClosureReason::ProcessingError { err: err.reason.to_string() };
13714+
ChannelError::Close((err.reason.to_string(), reason))
1366513715
})?);
1366613716

1366713717
let unfunded_context = UnfundedChannelContext {

0 commit comments

Comments
 (0)