From ddb01f5355217face27f90a2ad781f30631bb9d1 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 23 Feb 2026 15:49:30 +0100 Subject: [PATCH] lnpeer: don't save our own channel update as remote upd I noticed CLN is sending our own channel update to us on reestablishment, we then assume it to be the remote nodes update and try to verify the signature against their pubkey which fails and throws `InvalidGossipMsg`. This adds a check preventing us from trying to save our own channel updates as remote update. --- electrum/lnpeer.py | 7 +++++++ tests/test_lnpeer.py | 28 +++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index ea8779a00..5e83affdb 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -55,6 +55,7 @@ from .interface import GracefulDisconnect from .json_db import StoredDict from .invoices import PR_PAID from .fee_policy import FEE_LN_ETA_TARGET, FEERATE_PER_KW_MIN_RELAY_LIGHTNING +from .channel_db import FLAG_DIRECTION if TYPE_CHECKING: from .lnworker import LNGossip, LNWallet @@ -470,6 +471,12 @@ class Peer(Logger, EventListener): return for chan in self.channels.values(): if payload['short_channel_id'] in [chan.short_channel_id, chan.get_local_scid_alias()]: + # originator: node_id_1 if the least-significant bit of flags is 0 or node_id_2 otherwise + flags = int.from_bytes(payload['channel_flags'], byteorder='big', signed=False) + originator = sorted(self.node_ids)[flags & FLAG_DIRECTION] + if originator == self.lnworker.node_keypair.pubkey: + self.logger.debug(f"peer sent us our own channel update for chan {chan.get_id_for_log()}") + return chan.set_remote_update(payload) self.logger.info(f"saved remote channel_update gossip msg for chan {chan.get_id_for_log()}") break diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index 06ce9fe54..6ba3f750a 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -38,7 +38,7 @@ from electrum.crypto import privkey_to_pubkey from electrum.lnutil import Keypair, PaymentFailure, LnFeatures, HTLCOwner, PaymentFeeBudget, RECEIVED from electrum.lnchannel import ChannelState, PeerState, Channel from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent -from electrum.channel_db import ChannelDB +from electrum.channel_db import ChannelDB, InvalidGossipMsg from electrum.lnworker import LNWallet, NoPathFound, SentHtlcInfo, PaySession, LNPeerManager from electrum.lnmsg import encode_msg, decode_msg from electrum import lnmsg @@ -607,6 +607,32 @@ class TestPeerUtils(TestPeer): Peer.decode_short_ids(encoded_unsupported) self.assertIn("unexpected first byte", str(ctx.exception)) + async def test_maybe_save_remote_update(self): + graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan']) + alice_bob_peer, bob_alice_peer = graph.peers[('alice', 'bob')], graph.peers[('bob', 'alice')] + alice_bob_chan, bob_alice_chan = graph.channels[('alice', 'bob')], graph.channels[('bob', 'alice')] + + # prepare channel update from alice + alice_to_bob_chan_update = alice_bob_chan.get_outgoing_gossip_channel_update() + raw = alice_to_bob_chan_update + payload = decode_msg(alice_to_bob_chan_update)[1] + payload['raw'] = raw + + # bob should accept the update and save it + self.assertIsNone(bob_alice_chan.storage.get('remote_update')) + bob_alice_peer.maybe_save_remote_update(payload) + self.assertEqual(bob_alice_chan.storage.get('remote_update'), raw.hex()) + + # alice shouldn't save her own channel update as remote update + self.assertIsNone(alice_bob_chan.storage.get('remote_update')) + alice_bob_peer.maybe_save_remote_update(payload) + self.assertIsNone(alice_bob_chan.storage.get('remote_update')) + + ChannelDB.verify_channel_update(payload, start_node=bob_alice_peer.pubkey) + # trying to verify the sig against the wrong pubkey should fail obviously + with self.assertRaises(InvalidGossipMsg): + ChannelDB.verify_channel_update(payload, start_node=alice_bob_peer.pubkey) + class TestPeerDirect(TestPeer):