Skip to content

Commit d4bba8a

Browse files
committed
Fail interactive-tx negotiation on abort
This commit reworks all interactive transaction construction methods to mark the negotiation as failed upon a local/remote `TxAbort`, ensuring the `InteractiveTxConstructor` is consumed. Along the way, we refactor the handling of `tx_complete` such that we only have a single call into the `Channel` from the `ChannelManager`.
1 parent ea42df4 commit d4bba8a

File tree

4 files changed

+252
-228
lines changed

4 files changed

+252
-228
lines changed

lightning/src/ln/channel.rs

Lines changed: 207 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,11 @@ use crate::ln::funding::FundingTxInput;
6363
#[cfg(splicing)]
6464
use crate::ln::funding::SpliceContribution;
6565
#[cfg(splicing)]
66+
use crate::ln::interactivetxs::calculate_change_output_value;
6667
use crate::ln::interactivetxs::{
67-
calculate_change_output_value, AbortReason, InteractiveTxMessageSend,
68-
};
69-
use crate::ln::interactivetxs::{
70-
get_output_weight, InteractiveTxConstructor, InteractiveTxConstructorArgs,
71-
InteractiveTxSigningSession, SharedOwnedInput, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT,
68+
get_output_weight, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor,
69+
InteractiveTxConstructorArgs, InteractiveTxMessageSend, InteractiveTxSigningSession,
70+
SharedOwnedInput, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT,
7271
};
7372
use crate::ln::msgs;
7473
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket};
@@ -1597,14 +1596,6 @@ where
15971596
}
15981597
}
15991598

1600-
pub fn as_unfunded_v2_mut(&mut self) -> Option<&mut PendingV2Channel<SP>> {
1601-
if let ChannelPhase::UnfundedV2(channel) = &mut self.phase {
1602-
Some(channel)
1603-
} else {
1604-
None
1605-
}
1606-
}
1607-
16081599
#[rustfmt::skip]
16091600
pub fn signer_maybe_unblocked<L: Deref>(
16101601
&mut self, chain_hash: ChainHash, logger: &L,
@@ -1739,7 +1730,7 @@ where
17391730
}
17401731
}
17411732

1742-
pub fn interactive_tx_constructor_mut(&mut self) -> Option<&mut InteractiveTxConstructor> {
1733+
fn interactive_tx_constructor_mut(&mut self) -> Option<&mut InteractiveTxConstructor> {
17431734
match &mut self.phase {
17441735
ChannelPhase::UnfundedV2(chan) => chan.interactive_tx_constructor.as_mut(),
17451736
#[cfg(splicing)]
@@ -1748,6 +1739,195 @@ where
17481739
}
17491740
}
17501741

1742+
fn fail_interactive_tx_negotiation<L: Deref>(
1743+
&mut self, reason: AbortReason, logger: &L,
1744+
) -> msgs::TxAbort
1745+
where
1746+
L::Target: Logger,
1747+
{
1748+
let logger = WithChannelContext::from(logger, &self.context(), None);
1749+
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
1750+
1751+
let _interactive_tx_constructor = match &mut self.phase {
1752+
ChannelPhase::Undefined => unreachable!(),
1753+
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => None,
1754+
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1755+
pending_v2_channel.interactive_tx_constructor.take()
1756+
},
1757+
#[cfg(not(splicing))]
1758+
ChannelPhase::Funded(_) => unreachable!(),
1759+
#[cfg(splicing)]
1760+
ChannelPhase::Funded(funded_channel) => funded_channel
1761+
.pending_splice
1762+
.as_mut()
1763+
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1764+
.and_then(|funding_negotiation| {
1765+
if let FundingNegotiation::ConstructingTransaction(
1766+
_,
1767+
interactive_tx_constructor,
1768+
) = funding_negotiation
1769+
{
1770+
Some(interactive_tx_constructor)
1771+
} else {
1772+
None
1773+
}
1774+
}),
1775+
};
1776+
1777+
reason.into_tx_abort_msg(self.context().channel_id)
1778+
}
1779+
1780+
pub fn tx_add_input<L: Deref>(
1781+
&mut self, msg: &msgs::TxAddInput, logger: &L,
1782+
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1783+
where
1784+
L::Target: Logger,
1785+
{
1786+
match self.interactive_tx_constructor_mut() {
1787+
Some(interactive_tx_constructor) => interactive_tx_constructor.handle_tx_add_input(msg),
1788+
None => Err(AbortReason::InternalError(
1789+
"Received unexpected interactive transaction negotiation message",
1790+
)),
1791+
}
1792+
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1793+
}
1794+
1795+
pub fn tx_add_output<L: Deref>(
1796+
&mut self, msg: &msgs::TxAddOutput, logger: &L,
1797+
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1798+
where
1799+
L::Target: Logger,
1800+
{
1801+
match self.interactive_tx_constructor_mut() {
1802+
Some(interactive_tx_constructor) => {
1803+
interactive_tx_constructor.handle_tx_add_output(msg)
1804+
},
1805+
None => Err(AbortReason::InternalError(
1806+
"Received unexpected interactive transaction negotiation message",
1807+
)),
1808+
}
1809+
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1810+
}
1811+
1812+
pub fn tx_remove_input<L: Deref>(
1813+
&mut self, msg: &msgs::TxRemoveInput, logger: &L,
1814+
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1815+
where
1816+
L::Target: Logger,
1817+
{
1818+
match self.interactive_tx_constructor_mut() {
1819+
Some(interactive_tx_constructor) => {
1820+
interactive_tx_constructor.handle_tx_remove_input(msg)
1821+
},
1822+
None => Err(AbortReason::InternalError(
1823+
"Received unexpected interactive transaction negotiation message",
1824+
)),
1825+
}
1826+
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1827+
}
1828+
1829+
pub fn tx_remove_output<L: Deref>(
1830+
&mut self, msg: &msgs::TxRemoveOutput, logger: &L,
1831+
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1832+
where
1833+
L::Target: Logger,
1834+
{
1835+
match self.interactive_tx_constructor_mut() {
1836+
Some(interactive_tx_constructor) => {
1837+
interactive_tx_constructor.handle_tx_remove_output(msg)
1838+
},
1839+
None => Err(AbortReason::InternalError(
1840+
"Received unexpected interactive transaction negotiation message",
1841+
)),
1842+
}
1843+
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))
1844+
}
1845+
1846+
pub fn tx_complete<L: Deref>(
1847+
&mut self, msg: &msgs::TxComplete, logger: &L,
1848+
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), msgs::TxAbort>
1849+
where
1850+
L::Target: Logger,
1851+
{
1852+
let tx_complete_action = match self.interactive_tx_constructor_mut() {
1853+
Some(interactive_tx_constructor) => interactive_tx_constructor.handle_tx_complete(msg),
1854+
None => Err(AbortReason::InternalError(
1855+
"Received unexpected interactive transaction negotiation message",
1856+
)),
1857+
}
1858+
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
1859+
1860+
let (interactive_tx_msg_send, negotiation_complete) = match tx_complete_action {
1861+
HandleTxCompleteValue::SendTxMessage(interactive_tx_msg_send) => {
1862+
(Some(interactive_tx_msg_send), false)
1863+
},
1864+
HandleTxCompleteValue::SendTxComplete(
1865+
interactive_tx_msg_send,
1866+
negotiation_complete,
1867+
) => (Some(interactive_tx_msg_send), negotiation_complete),
1868+
HandleTxCompleteValue::NegotiationComplete => (None, true),
1869+
};
1870+
if !negotiation_complete {
1871+
return Ok((interactive_tx_msg_send, None));
1872+
}
1873+
1874+
let commitment_signed = self
1875+
.funding_tx_constructed(logger)
1876+
.map_err(|abort_reason| self.fail_interactive_tx_negotiation(abort_reason, logger))?;
1877+
Ok((interactive_tx_msg_send, Some(commitment_signed)))
1878+
}
1879+
1880+
pub fn tx_abort<L: Deref>(
1881+
&mut self, msg: &msgs::TxAbort, logger: &L,
1882+
) -> Result<Option<msgs::TxAbort>, ChannelError>
1883+
where
1884+
L::Target: Logger,
1885+
{
1886+
// This checks for and resets the interactive negotiation state by `take()`ing it from the channel.
1887+
// The existence of the `tx_constructor` indicates that we have not moved into the signing
1888+
// phase for this interactively constructed transaction and hence we have not exchanged
1889+
// `tx_signatures`. Either way, we never close the channel upon receiving a `tx_abort`:
1890+
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L574-L576
1891+
let should_ack = match &mut self.phase {
1892+
ChannelPhase::Undefined => unreachable!(),
1893+
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {
1894+
let err = "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel establishment";
1895+
return Err(ChannelError::Warn(err.into()));
1896+
},
1897+
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1898+
pending_v2_channel.interactive_tx_constructor.take().is_some()
1899+
},
1900+
#[cfg(not(splicing))]
1901+
ChannelPhase::Funded(_) => {
1902+
let err = "Got an unexpected tx_abort message: This is an funded channel and splicing is not supported";
1903+
return Err(ChannelError::Warn(err.into()));
1904+
},
1905+
#[cfg(splicing)]
1906+
ChannelPhase::Funded(funded_channel) => funded_channel
1907+
.pending_splice
1908+
.as_mut()
1909+
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1910+
.is_some(),
1911+
};
1912+
1913+
// NOTE: Since at this point we have not sent a `tx_abort` message for this negotiation
1914+
// previously (tx_constructor was `Some`), we need to echo back a tx_abort message according
1915+
// to the spec:
1916+
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561
1917+
// For rationale why we echo back `tx_abort`:
1918+
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580
1919+
Ok(should_ack.then(|| {
1920+
let logger = WithChannelContext::from(logger, &self.context(), None);
1921+
let reason =
1922+
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
1923+
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
1924+
msgs::TxAbort {
1925+
channel_id: msg.channel_id,
1926+
data: "Acknowledged tx_abort".to_string().into_bytes(),
1927+
}
1928+
}))
1929+
}
1930+
17511931
#[rustfmt::skip]
17521932
pub fn funding_signed<L: Deref>(
17531933
&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, signer_provider: &SP, logger: &L
@@ -1780,9 +1960,9 @@ where
17801960
result.map(|monitor| (self.as_funded_mut().expect("Channel should be funded"), monitor))
17811961
}
17821962

1783-
pub fn funding_tx_constructed<L: Deref>(
1963+
fn funding_tx_constructed<L: Deref>(
17841964
&mut self, logger: &L,
1785-
) -> Result<msgs::CommitmentSigned, msgs::TxAbort>
1965+
) -> Result<msgs::CommitmentSigned, AbortReason>
17861966
where
17871967
L::Target: Logger,
17881968
{
@@ -1837,17 +2017,15 @@ where
18372017
}
18382018
}
18392019

1840-
return Err(msgs::TxAbort {
1841-
channel_id: chan.context.channel_id(),
1842-
data: "Got a tx_complete message in an invalid state".to_owned().into_bytes(),
1843-
});
2020+
return Err(AbortReason::InternalError(
2021+
"Got a tx_complete message in an invalid state",
2022+
));
18442023
},
18452024
_ => {
18462025
debug_assert!(false);
1847-
return Err(msgs::TxAbort {
1848-
channel_id: self.context().channel_id(),
1849-
data: "Got a tx_complete message in an invalid phase".to_owned().into_bytes(),
1850-
});
2026+
return Err(AbortReason::InternalError(
2027+
"Got a tx_complete message in an invalid phase",
2028+
));
18512029
},
18522030
}
18532031
}
@@ -5855,7 +6033,7 @@ where
58556033
fn funding_tx_constructed<L: Deref>(
58566034
&mut self, funding: &mut FundingScope, signing_session: &mut InteractiveTxSigningSession,
58576035
is_splice: bool, holder_commitment_transaction_number: u64, logger: &L
5858-
) -> Result<msgs::CommitmentSigned, msgs::TxAbort>
6036+
) -> Result<msgs::CommitmentSigned, AbortReason>
58596037
where
58606038
L::Target: Logger
58616039
{
@@ -5864,21 +6042,15 @@ where
58646042
for (idx, outp) in signing_session.unsigned_tx().outputs().enumerate() {
58656043
if outp.script_pubkey() == &expected_spk && outp.value() == funding.get_value_satoshis() {
58666044
if output_index.is_some() {
5867-
return Err(msgs::TxAbort {
5868-
channel_id: self.channel_id(),
5869-
data: "Multiple outputs matched the expected script and value".to_owned().into_bytes(),
5870-
});
6045+
return Err(AbortReason::DuplicateFundingOutput);
58716046
}
58726047
output_index = Some(idx as u16);
58736048
}
58746049
}
58756050
let outpoint = if let Some(output_index) = output_index {
58766051
OutPoint { txid: signing_session.unsigned_tx().compute_txid(), index: output_index }
58776052
} else {
5878-
return Err(msgs::TxAbort {
5879-
channel_id: self.channel_id(),
5880-
data: "No output matched the funding script_pubkey".to_owned().into_bytes(),
5881-
});
6053+
return Err(AbortReason::MissingFundingOutput);
58826054
};
58836055
funding
58846056
.channel_transaction_parameters.funding_outpoint = Some(outpoint);
@@ -5892,10 +6064,7 @@ where
58926064
self.counterparty_next_commitment_transaction_number,
58936065
);
58946066
// TODO(splicing) Forced error, as the use case is not complete
5895-
return Err(msgs::TxAbort {
5896-
channel_id: self.channel_id(),
5897-
data: "Splicing not yet supported".to_owned().into_bytes(),
5898-
});
6067+
return Err(AbortReason::InternalError("Splicing not yet supported"));
58996068
} else {
59006069
self.assert_no_commitment_advancement(holder_commitment_transaction_number, "initial commitment_signed");
59016070
}
@@ -5906,10 +6075,7 @@ where
59066075
// TODO(splicing): Support async signing
59076076
None => {
59086077
funding.channel_transaction_parameters.funding_outpoint = None;
5909-
return Err(msgs::TxAbort {
5910-
channel_id: self.channel_id(),
5911-
data: "Failed to get signature for commitment_signed".to_owned().into_bytes(),
5912-
});
6078+
return Err(AbortReason::InternalError("Failed to compute commitment_signed signatures"));
59136079
},
59146080
};
59156081

0 commit comments

Comments
 (0)