From d0be5fcfc875f1d3e171d4ca3f62979f587ed586 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 May 2025 16:54:56 +0000 Subject: [PATCH] lnchannel: persist error sent by remote peer into db If a force-close happens due to e.g. a feerate disagreement or an invalid signature, etc, and the remote peer sends us an error, it can be useful if users can provide us with this error. If the user does not have logging enabled when the error is sent, without this persistence it will likely get lost. --- electrum/gui/qt/channel_details.py | 6 +++++- electrum/lnchannel.py | 20 +++++++++++++++++++- electrum/lnpeer.py | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) 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}))