diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 85df4844a..ba8b8750a 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -53,7 +53,7 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr, fee_for_htlc_output, offered_htlc_trim_threshold_sat, received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, - ChannelType) + ChannelType, LNProtocolWarning) from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo from .lnhtlc import HTLCManager @@ -981,7 +981,9 @@ class Channel(AbstractChannel): preimage_hex = pending_local_commitment.serialize_preimage(0) pre_hash = sha256d(bfh(preimage_hex)) if not ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, sig, pre_hash): - raise Exception(f'failed verifying signature of our updated commitment transaction: {bh2u(sig)} preimage is {preimage_hex}') + raise LNProtocolWarning( + f'failed verifying signature of our updated commitment transaction: ' + f'{bh2u(sig)} preimage is {preimage_hex}, rawtx: {pending_local_commitment.serialize()}') htlc_sigs_string = b''.join(htlc_sigs) @@ -993,7 +995,7 @@ class Channel(AbstractChannel): subject=LOCAL, ctn=next_local_ctn) if len(htlc_to_ctx_output_idx_map) != len(htlc_sigs): - raise Exception(f'htlc sigs failure. recv {len(htlc_sigs)} sigs, expected {len(htlc_to_ctx_output_idx_map)}') + raise LNProtocolWarning(f'htlc sigs failure. recv {len(htlc_sigs)} sigs, expected {len(htlc_to_ctx_output_idx_map)}') for (direction, htlc), (ctx_output_idx, htlc_relative_idx) in htlc_to_ctx_output_idx_map.items(): htlc_sig = htlc_sigs[htlc_relative_idx] self._verify_htlc_sig(htlc=htlc, @@ -1021,7 +1023,7 @@ class Channel(AbstractChannel): pre_hash = sha256d(bfh(htlc_tx.serialize_preimage(0))) remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp) if not ecc.verify_signature(remote_htlc_pubkey, htlc_sig, pre_hash): - raise Exception(f'failed verifying HTLC signatures: {htlc} {htlc_direction}') + raise LNProtocolWarning(f'failed verifying HTLC signatures: {htlc} {htlc_direction}, rawtx: {htlc_tx.serialize()}') def get_remote_htlc_sig_for_htlc(self, *, htlc_relative_idx: int) -> bytes: data = self.config[LOCAL].current_htlc_signatures diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 307a1f7ff..b9432fda8 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -42,7 +42,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf LightningPeerConnectionClosed, HandshakeFailed, RemoteMisbehaving, ShortChannelID, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, - UpfrontShutdownScriptViolation, ChannelType) + ChannelType, LNProtocolWarning) from .lnutil import FeeUpdate, channel_id_from_funding_tx from .lntransport import LNTransport, LNTransportBase from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType @@ -861,7 +861,10 @@ class Peer(Logger): payload = await self.wait_for_message('funding_signed', channel_id) self.logger.info('received funding_signed') remote_sig = payload['signature'] - chan.receive_new_commitment(remote_sig, []) + try: + chan.receive_new_commitment(remote_sig, []) + except LNProtocolWarning as e: + await self.send_warning(channel_id, message=str(e), close_connection=True) chan.open_with_first_pcp(remote_per_commitment_point, remote_sig) chan.set_state(ChannelState.OPENING) self.lnworker.add_new_channel(chan) @@ -1020,7 +1023,10 @@ class Peer(Logger): if isinstance(self.transport, LNTransport): chan.add_or_update_peer_addr(self.transport.peer_addr) remote_sig = funding_created['signature'] - chan.receive_new_commitment(remote_sig, []) + try: + chan.receive_new_commitment(remote_sig, []) + except LNProtocolWarning as e: + await self.send_warning(channel_id, message=str(e), close_connection=True) sig_64, _ = chan.sign_next_commitment() self.send_message('funding_signed', channel_id=channel_id, @@ -1868,12 +1874,18 @@ class Peer(Logger): return txid async def on_shutdown(self, chan: Channel, payload): + # TODO: A receiving node: if it hasn't received a funding_signed (if it is a + # funder) or a funding_created (if it is a fundee): + # SHOULD send an error and fail the channel. their_scriptpubkey = payload['scriptpubkey'] their_upfront_scriptpubkey = chan.config[REMOTE].upfront_shutdown_script # BOLT-02 check if they use the upfront shutdown script they advertized - if their_upfront_scriptpubkey: + if self.is_upfront_shutdown_script() and their_upfront_scriptpubkey: if not (their_scriptpubkey == their_upfront_scriptpubkey): - raise UpfrontShutdownScriptViolation("remote didn't use upfront shutdown script it commited to in channel opening") + await self.send_warning( + chan.channel_id, + "remote didn't use upfront shutdown script it commited to in channel opening", + close_connection=True) else: # BOLT-02 restrict the scriptpubkey to some templates: if self.is_shutdown_anysegwit() and match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT): @@ -1881,7 +1893,10 @@ class Peer(Logger): elif match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0): pass else: - raise Exception(f'scriptpubkey in received shutdown message does not conform to any template: {their_scriptpubkey.hex()}') + await self.send_warning( + chan.channel_id, + f'scriptpubkey in received shutdown message does not conform to any template: {their_scriptpubkey.hex()}', + close_connection=True) chan_id = chan.channel_id if chan_id in self.shutdown_received: diff --git a/electrum/lnutil.py b/electrum/lnutil.py index bba793321..79a1f42f3 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -351,7 +351,6 @@ class UnableToDeriveSecret(LightningError): pass class HandshakeFailed(LightningError): pass class ConnStringFormatError(LightningError): pass class RemoteMisbehaving(LightningError): pass -class UpfrontShutdownScriptViolation(RemoteMisbehaving): pass class NotFoundChanAnnouncementForUpdate(Exception): pass class InvalidGossipMsg(Exception): diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py index 04f3e3f09..b731f9949 100644 --- a/electrum/tests/test_lnpeer.py +++ b/electrum/tests/test_lnpeer.py @@ -22,9 +22,8 @@ from electrum import simple_config, lnutil from electrum.lnaddr import lnencode, LnAddr, lndecode from electrum.bitcoin import COIN, sha256 from electrum.util import bh2u, create_and_start_event_loop, NetworkRetryManager, bfh, OldTaskGroup -from electrum.lnpeer import Peer, UpfrontShutdownScriptViolation +from electrum.lnpeer import Peer from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey -from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner from electrum.lnchannel import ChannelState, PeerState, Channel from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent @@ -1201,7 +1200,7 @@ class TestPeer(TestCaseForTestnet): gath = asyncio.gather(*coros) await gath - with self.assertRaises(UpfrontShutdownScriptViolation): + with self.assertRaises(GracefulDisconnect): run(test()) # bob sends the same upfront_shutdown_script has he announced