diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index adb428ffb..99d081f73 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -179,7 +179,7 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener): return self.window.show_transaction(tx) - def get_common_form(self, chan): + def get_common_form(self, chan: AbstractChannel): form = QtWidgets.QFormLayout(None) remote_id_e = ShowQRLineEdit(chan.node_id.hex(), self.window.config, title=_("Remote Node ID")) form.addRow(QLabel(_('Remote Node') + ':'), remote_id_e) @@ -191,6 +191,10 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener): if remote_scid_alias := chan.get_remote_scid_alias(): form.addRow(QLabel('Remote SCID Alias:'), SelectableLabel(str(ShortID(remote_scid_alias)))) form.addRow(QLabel(_('State') + ':'), SelectableLabel(chan.get_state_for_GUI())) + if remote_peer_sent_error := chan.get_remote_peer_sent_error(): + err_label = WWLabel(remote_peer_sent_error) # note: text is already truncated to reasonable len + err_label.setTextFormat(QtCore.Qt.TextFormat.PlainText) + form.addRow(WWLabel(_('Remote peer sent error [DO NOT TRUST]') + ':'), err_label) self.capacity = self.format_sat(chan.get_capacity()) form.addRow(QLabel(_('Capacity') + ':'), SelectableLabel(self.capacity)) if not chan.is_backup(): diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 9336be484..276ed51a4 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -37,7 +37,7 @@ import electrum_ecc as ecc from electrum_ecc import ECPubkey from . import constants, util -from .util import bfh, chunks, TxMinedInfo +from .util import bfh, chunks, TxMinedInfo, error_text_bytes_to_safe_str from .invoices import PR_PAID from .bitcoin import redeem_script_to_address from .crypto import sha256, sha256d @@ -303,6 +303,9 @@ class AbstractChannel(Logger, ABC): def get_remote_scid_alias(self) -> Optional[bytes]: return None + def get_remote_peer_sent_error(self) -> Optional[str]: + return None + def get_ctx_sweep_info(self, ctx: Transaction) -> Tuple[bool, Dict[str, SweepInfo]]: our_sweep_info = self.create_sweeptxs_for_our_ctx(ctx) their_sweep_info = self.create_sweeptxs_for_their_ctx(ctx) @@ -892,6 +895,21 @@ class Channel(AbstractChannel): net_addr = NetAddress.from_string(net_addr_str) yield LNPeerAddr(host=str(net_addr.host), port=net_addr.port, pubkey=self.node_id) + def save_remote_peer_sent_error(self, original_error: bytes): + # We save the original arbitrary text(/bytes) error, as received. + # The length is only implicitly limited by the BOLT-08 max msg size. + # Receiving an error usually results in the channel getting closed, so + # there is likely no need to store multiple errors. We only store one, and overwrite. + self.storage['remote_peer_sent_error'] = original_error.hex() + + def get_remote_peer_sent_error(self) -> Optional[str]: + original_error = self.storage.get('remote_peer_sent_error') + if not original_error: + return None + err_bytes = bytes.fromhex(original_error) + safe_str = error_text_bytes_to_safe_str(err_bytes) # note: truncates + return safe_str + def get_outgoing_gossip_channel_update(self, *, scid: ShortChannelID = None) -> bytes: """ scid: to be put into the channel_update message instead of the real scid, as this might be an scid alias diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 011408def..1483f4ba1 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -282,9 +282,10 @@ class Peer(Logger, EventListener): self.logger.info(f"remote peer sent error [DO NOT TRUST THIS MESSAGE]: " f"{error_text_bytes_to_safe_str(err_bytes, max_len=None)}. chan_id={chan_id.hex()}. " f"{is_known_chan_id=}") - if chan_id in self.channels: + if chan := self.channels.get(chan_id): self.schedule_force_closing(chan_id) self.ordered_message_queues[chan_id].put_nowait((None, {'error': err_bytes})) + chan.save_remote_peer_sent_error(err_bytes) elif chan_id in self.temp_id_to_id: chan_id = self.temp_id_to_id[chan_id] or chan_id self.ordered_message_queues[chan_id].put_nowait((None, {'error': err_bytes}))