Skip to content

Commit 7b7b404

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 625228e commit 7b7b404

File tree

2 files changed

+261
-93
lines changed

2 files changed

+261
-93
lines changed

lightning/src/ln/channel.rs

Lines changed: 103 additions & 48 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};
@@ -1712,12 +1713,13 @@ where
17121713
}
17131714

17141715
fn fail_interactive_tx_negotiation<L: Deref>(
1715-
&mut self, reason: AbortReason, logger: &L,
1716+
&mut self, error: NegotiationError, logger: &L,
17161717
) -> msgs::TxAbort
17171718
where
17181719
L::Target: Logger,
17191720
{
17201721
let logger = WithChannelContext::from(logger, &self.context(), None);
1722+
let NegotiationError { reason, .. } = error;
17211723
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
17221724

17231725
let _interactive_tx_constructor = match &mut self.phase {
@@ -1754,11 +1756,15 @@ where
17541756
{
17551757
match self.interactive_tx_constructor_mut() {
17561758
Some(interactive_tx_constructor) => interactive_tx_constructor.handle_tx_add_input(msg),
1757-
None => Err(AbortReason::InternalError(
1758-
"Received unexpected interactive transaction negotiation message",
1759-
)),
1759+
None => Err(NegotiationError {
1760+
reason: AbortReason::InternalError(
1761+
"Received unexpected interactive transaction negotiation message",
1762+
),
1763+
contributed_inputs: Vec::new(),
1764+
contributed_outputs: Vec::new(),
1765+
}),
17601766
}
1761-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1767+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17621768
}
17631769

17641770
pub fn tx_add_output<L: Deref>(
@@ -1771,11 +1777,15 @@ where
17711777
Some(interactive_tx_constructor) => {
17721778
interactive_tx_constructor.handle_tx_add_output(msg)
17731779
},
1774-
None => Err(AbortReason::InternalError(
1775-
"Received unexpected interactive transaction negotiation message",
1776-
)),
1780+
None => Err(NegotiationError {
1781+
reason: AbortReason::InternalError(
1782+
"Received unexpected interactive transaction negotiation message",
1783+
),
1784+
contributed_inputs: Vec::new(),
1785+
contributed_outputs: Vec::new(),
1786+
}),
17771787
}
1778-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1788+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17791789
}
17801790

17811791
pub fn tx_remove_input<L: Deref>(
@@ -1788,11 +1798,15 @@ where
17881798
Some(interactive_tx_constructor) => {
17891799
interactive_tx_constructor.handle_tx_remove_input(msg)
17901800
},
1791-
None => Err(AbortReason::InternalError(
1792-
"Received unexpected interactive transaction negotiation message",
1793-
)),
1801+
None => Err(NegotiationError {
1802+
reason: AbortReason::InternalError(
1803+
"Received unexpected interactive transaction negotiation message",
1804+
),
1805+
contributed_inputs: Vec::new(),
1806+
contributed_outputs: Vec::new(),
1807+
}),
17941808
}
1795-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1809+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
17961810
}
17971811

17981812
pub fn tx_remove_output<L: Deref>(
@@ -1805,11 +1819,15 @@ where
18051819
Some(interactive_tx_constructor) => {
18061820
interactive_tx_constructor.handle_tx_remove_output(msg)
18071821
},
1808-
None => Err(AbortReason::InternalError(
1809-
"Received unexpected interactive transaction negotiation message",
1810-
)),
1822+
None => Err(NegotiationError {
1823+
reason: AbortReason::InternalError(
1824+
"Received unexpected interactive transaction negotiation message",
1825+
),
1826+
contributed_inputs: Vec::new(),
1827+
contributed_outputs: Vec::new(),
1828+
}),
18111829
}
1812-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1830+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))
18131831
}
18141832

18151833
pub fn tx_complete<L: Deref>(
@@ -1820,11 +1838,15 @@ where
18201838
{
18211839
let tx_complete_action = match self.interactive_tx_constructor_mut() {
18221840
Some(interactive_tx_constructor) => interactive_tx_constructor.handle_tx_complete(msg),
1823-
None => Err(AbortReason::InternalError(
1824-
"Received unexpected interactive transaction negotiation message",
1825-
)),
1841+
None => Err(NegotiationError {
1842+
reason: AbortReason::InternalError(
1843+
"Received unexpected interactive transaction negotiation message",
1844+
),
1845+
contributed_inputs: Vec::new(),
1846+
contributed_outputs: Vec::new(),
1847+
}),
18261848
}
1827-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
1849+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))?;
18281850

18291851
let (interactive_tx_msg_send, negotiation_complete) = match tx_complete_action {
18301852
HandleTxCompleteValue::SendTxMessage(interactive_tx_msg_send) => {
@@ -1842,7 +1864,7 @@ where
18421864

18431865
let commitment_signed = self
18441866
.funding_tx_constructed(logger)
1845-
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
1867+
.map_err(|e| self.fail_interactive_tx_negotiation(e, logger))?;
18461868
Ok((interactive_tx_msg_send, Some(commitment_signed)))
18471869
}
18481870

@@ -1925,7 +1947,7 @@ where
19251947

19261948
fn funding_tx_constructed<L: Deref>(
19271949
&mut self, logger: &L,
1928-
) -> Result<msgs::CommitmentSigned, AbortReason>
1950+
) -> Result<msgs::CommitmentSigned, NegotiationError>
19291951
where
19301952
L::Target: Logger,
19311953
{
@@ -1983,15 +2005,23 @@ where
19832005
}
19842006
}
19852007

1986-
return Err(AbortReason::InternalError(
1987-
"Got a tx_complete message in an invalid state",
1988-
));
2008+
return Err(NegotiationError {
2009+
reason: AbortReason::InternalError(
2010+
"Got a tx_complete message in an invalid state",
2011+
),
2012+
contributed_inputs: Vec::new(),
2013+
contributed_outputs: Vec::new(),
2014+
});
19892015
},
19902016
_ => {
19912017
debug_assert!(false);
1992-
return Err(AbortReason::InternalError(
1993-
"Got a tx_complete message in an invalid phase",
1994-
));
2018+
return Err(NegotiationError {
2019+
reason: AbortReason::InternalError(
2020+
"Got a tx_complete message in an invalid phase",
2021+
),
2022+
contributed_inputs: Vec::new(),
2023+
contributed_outputs: Vec::new(),
2024+
});
19952025
},
19962026
}
19972027
}
@@ -6102,7 +6132,7 @@ where
61026132
fn funding_tx_constructed<L: Deref>(
61036133
&mut self, funding: &mut FundingScope, signing_session: InteractiveTxSigningSession,
61046134
is_splice: bool, holder_commitment_transaction_number: u64, logger: &L
6105-
) -> Result<msgs::CommitmentSigned, AbortReason>
6135+
) -> Result<msgs::CommitmentSigned, NegotiationError>
61066136
where
61076137
L::Target: Logger
61086138
{
@@ -6111,15 +6141,17 @@ where
61116141
for (idx, outp) in signing_session.unsigned_tx().tx().output.iter().enumerate() {
61126142
if outp.script_pubkey == expected_spk && outp.value.to_sat() == funding.get_value_satoshis() {
61136143
if output_index.is_some() {
6114-
return Err(AbortReason::DuplicateFundingOutput);
6144+
let reason = AbortReason::DuplicateFundingOutput;
6145+
return Err(signing_session.into_negotiation_error(reason));
61156146
}
61166147
output_index = Some(idx as u16);
61176148
}
61186149
}
61196150
let outpoint = if let Some(output_index) = output_index {
61206151
OutPoint { txid: signing_session.unsigned_tx().compute_txid(), index: output_index }
61216152
} else {
6122-
return Err(AbortReason::MissingFundingOutput);
6153+
let reason = AbortReason::MissingFundingOutput;
6154+
return Err(signing_session.into_negotiation_error(reason));
61236155
};
61246156
funding
61256157
.channel_transaction_parameters.funding_outpoint = Some(outpoint);
@@ -6131,7 +6163,9 @@ where
61316163
self.counterparty_next_commitment_transaction_number,
61326164
);
61336165
// TODO(splicing) Forced error, as the use case is not complete
6134-
return Err(AbortReason::InternalError("Splicing not yet supported"));
6166+
let signing_session = self.interactive_tx_signing_session.take().unwrap();
6167+
let reason = AbortReason::InternalError("Splicing not yet supported");
6168+
return Err(signing_session.into_negotiation_error(reason));
61356169
} else {
61366170
self.assert_no_commitment_advancement(holder_commitment_transaction_number, "initial commitment_signed");
61376171
self.channel_state = ChannelState::FundingNegotiated(FundingNegotiatedFlags::new());
@@ -6143,7 +6177,9 @@ where
61436177
// TODO(splicing): Support async signing
61446178
None => {
61456179
funding.channel_transaction_parameters.funding_outpoint = None;
6146-
return Err(AbortReason::InternalError("Failed to compute commitment_signed signatures"));
6180+
let signing_session = self.interactive_tx_signing_session.take().unwrap();
6181+
let reason = AbortReason::InternalError("Failed to compute commitment_signed signatures");
6182+
return Err(signing_session.into_negotiation_error(reason));
61476183
},
61486184
};
61496185

@@ -6556,9 +6592,9 @@ impl FundingNegotiationContext {
65566592
/// Prepare and start interactive transaction negotiation.
65576593
/// If error occurs, it is caused by our side, not the counterparty.
65586594
fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
6559-
self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6595+
mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
65606596
entropy_source: &ES, holder_node_id: PublicKey,
6561-
) -> Result<InteractiveTxConstructor, AbortReason>
6597+
) -> Result<InteractiveTxConstructor, NegotiationError>
65626598
where
65636599
SP::Target: SignerProvider,
65646600
ES::Target: EntropySource,
@@ -6584,25 +6620,32 @@ impl FundingNegotiationContext {
65846620

65856621
// Optionally add change output
65866622
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6587-
calculate_change_output_value(
6623+
match calculate_change_output_value(
65886624
&self,
65896625
self.shared_funding_input.is_some(),
65906626
&shared_funding_output.script_pubkey,
65916627
context.holder_dust_limit_satoshis,
6592-
)?
6628+
) {
6629+
Ok(change_value_opt) => change_value_opt,
6630+
Err(reason) => {
6631+
return Err(self.into_negotiation_error(reason));
6632+
},
6633+
}
65936634
} else {
65946635
None
65956636
};
65966637

6597-
let mut funding_outputs = self.our_funding_outputs;
6598-
65996638
if let Some(change_value) = change_value_opt {
66006639
let change_script = if let Some(script) = self.change_script {
66016640
script
66026641
} else {
6603-
signer_provider
6604-
.get_destination_script(context.channel_keys_id)
6605-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6642+
match signer_provider.get_destination_script(context.channel_keys_id) {
6643+
Ok(script) => script,
6644+
Err(_) => {
6645+
let reason = AbortReason::InternalError("Error getting change script");
6646+
return Err(self.into_negotiation_error(reason));
6647+
},
6648+
}
66066649
};
66076650
let mut change_output =
66086651
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
@@ -6613,7 +6656,7 @@ impl FundingNegotiationContext {
66136656
// Check dust limit again
66146657
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
66156658
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6616-
funding_outputs.push(change_output);
6659+
self.our_funding_outputs.push(change_output);
66176660
}
66186661
}
66196662

@@ -6631,10 +6674,22 @@ impl FundingNegotiationContext {
66316674
shared_funding_output,
66326675
funding.value_to_self_msat / 1000,
66336676
),
6634-
outputs_to_contribute: funding_outputs,
6677+
outputs_to_contribute: self.our_funding_outputs,
66356678
};
66366679
InteractiveTxConstructor::new(constructor_args)
66376680
}
6681+
6682+
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
6683+
let contributed_inputs = self
6684+
.our_funding_inputs
6685+
.into_iter()
6686+
.map(|input| input.utxo.outpoint)
6687+
.collect();
6688+
6689+
let contributed_outputs = self.our_funding_outputs;
6690+
6691+
NegotiationError { reason, contributed_inputs, contributed_outputs }
6692+
}
66386693
}
66396694

66406695
// Holder designates channel data owned for the benefit of the user client.
@@ -13721,8 +13776,8 @@ where
1372113776
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1372213777
}
1372313778
).map_err(|err| {
13724-
let reason = ClosureReason::ProcessingError { err: err.to_string() };
13725-
ChannelError::Close((err.to_string(), reason))
13779+
let reason = ClosureReason::ProcessingError { err: err.reason.to_string() };
13780+
ChannelError::Close((err.reason.to_string(), reason))
1372613781
})?);
1372713782

1372813783
let unfunded_context = UnfundedChannelContext {

0 commit comments

Comments
 (0)