From ca93af2b8a1dcf3f13ceeed4f0e30a807f62bff3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 23 Jun 2023 19:51:57 +0000 Subject: [PATCH] ln: some clean-up for option_scid_alias - qt chan details dlg: show both local and remote aliases - lnchannel: more descriptive names, add clarification in doctstrings, and also save the "local_scid_alias" in the wallet file (to remember if we sent it) - lnpeer: - resend channel_ready msg after reestablish, to upgrade old existing channels to having local_scid_alias - forwarding bugfix, to follow BOLT-04: > - if it returns a `channel_update`: > - MUST set `short_channel_id` to the `short_channel_id` used by the incoming onion. --- electrum/gui/qt/channel_details.py | 9 +++---- electrum/lnchannel.py | 38 ++++++++++++++++++++++-------- electrum/lnpeer.py | 18 ++++++++------ electrum/lnworker.py | 4 ++-- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 970ce9ed9..39d3ebde4 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -5,7 +5,7 @@ import PyQt5.QtWidgets as QtWidgets import PyQt5.QtCore as QtCore from PyQt5.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout -from electrum.util import EventListener +from electrum.util import EventListener, ShortID from electrum.i18n import _ from electrum.util import format_time from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction @@ -181,9 +181,10 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener): channel_id_e = ShowQRLineEdit(chan.channel_id.hex(), self.window.config, title=_("Channel ID")) form.addRow(QLabel(_('Channel ID') + ':'), channel_id_e) form.addRow(QLabel(_('Short Channel ID') + ':'), QLabel(str(chan.short_channel_id))) - alias = chan.get_remote_alias() - if alias: - form.addRow(QLabel(_('Alias') + ':'), QLabel('0x'+alias.hex())) + if local_scid_alias := chan.get_local_scid_alias(): + form.addRow(QLabel('Local SCID Alias:'), QLabel(str(ShortID(local_scid_alias)))) + if remote_scid_alias := chan.get_remote_scid_alias(): + form.addRow(QLabel('Remote SCID Alias:'), QLabel(str(ShortID(remote_scid_alias)))) form.addRow(QLabel(_('State') + ':'), SelectableLabel(chan.get_state_for_GUI())) self.capacity = self.format_sat(chan.get_capacity()) form.addRow(QLabel(_('Capacity') + ':'), SelectableLabel(self.capacity)) diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 1825addc9..f84b79044 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -532,7 +532,7 @@ class ChannelBackup(AbstractChannel): def is_backup(self): return True - def get_remote_alias(self) -> Optional[bytes]: + def get_remote_scid_alias(self) -> Optional[bytes]: return None def create_sweeptxs_for_their_ctx(self, ctx): @@ -640,15 +640,27 @@ class Channel(AbstractChannel): self.should_request_force_close = False self.unconfirmed_closing_txid = None # not a state, only for GUI - def get_local_alias(self) -> bytes: - # deterministic, same secrecy level as wallet master pubkey - wallet_fingerprint = bytes(self.lnworker.wallet.get_fingerprint(), "utf8") - return sha256(wallet_fingerprint + self.channel_id)[0:8] + def get_local_scid_alias(self, *, create_new_if_needed: bool = False) -> Optional[bytes]: + """Get scid_alias to be used for *outgoing* HTLCs. + (called local as we choose the value) + """ + if alias := self.storage.get('local_scid_alias'): + return bytes.fromhex(alias) + elif create_new_if_needed: + # deterministic, same secrecy level as wallet master pubkey + wallet_fingerprint = bytes(self.lnworker.wallet.get_fingerprint(), "utf8") + alias = sha256(wallet_fingerprint + self.channel_id)[0:8] + self.storage['local_scid_alias'] = alias.hex() + return alias + return None - def save_remote_alias(self, alias: bytes): + def save_remote_scid_alias(self, alias: bytes): self.storage['alias'] = alias.hex() - def get_remote_alias(self) -> Optional[bytes]: + def get_remote_scid_alias(self) -> Optional[bytes]: + """Get scid_alias to be used for *incoming* HTLCs. + (called remote as the remote chooses the value) + """ alias = self.storage.get('alias') return bytes.fromhex(alias) if alias else None @@ -697,6 +709,7 @@ class Channel(AbstractChannel): This message contains info we need to populate private route hints when creating invoices. """ + assert payload['short_channel_id'] in [self.short_channel_id, self.get_local_scid_alias()] from .channel_db import ChannelDB ChannelDB.verify_channel_update(payload, start_node=self.node_id) raw = payload['raw'] @@ -719,11 +732,16 @@ 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 get_outgoing_gossip_channel_update(self) -> bytes: - if self._outgoing_channel_update is not None: + 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 + """ + if self._outgoing_channel_update is not None and scid is None: return self._outgoing_channel_update if not self.lnworker: raise Exception('lnworker not set for channel!') + if scid is None: + scid = self.short_channel_id sorted_node_ids = list(sorted([self.node_id, self.get_local_pubkey()])) channel_flags = b'\x00' if sorted_node_ids[0] == self.get_local_pubkey() else b'\x01' now = int(time.time()) @@ -731,7 +749,7 @@ class Channel(AbstractChannel): chan_upd = encode_msg( "channel_update", - short_channel_id=self.short_channel_id, + short_channel_id=scid, channel_flags=channel_flags, message_flags=b'\x01', cltv_expiry_delta=self.forwarding_cltv_expiry_delta, diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 488bb13da..0b3b2561e 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -385,7 +385,7 @@ class Peer(Logger): if not self.channels: return for chan in self.channels.values(): - if payload['short_channel_id'] in [chan.short_channel_id, chan.get_local_alias()]: + if payload['short_channel_id'] in [chan.short_channel_id, chan.get_local_scid_alias()]: chan.set_remote_update(payload) self.logger.info(f"saved remote channel_update gossip msg for chan {chan.get_id_for_log()}") break @@ -1272,7 +1272,10 @@ class Peer(Logger): resend_revoke_and_ack() chan.peer_state = PeerState.GOOD + chan_just_became_ready = False if chan.is_funded() and their_next_local_ctn == next_local_ctn == 1: + chan_just_became_ready = True + if chan_just_became_ready or self.features.supports(LnFeatures.OPTION_SCID_ALIAS_OPT): self.send_channel_ready(chan) # checks done if chan.is_funded() and chan.config[LOCAL].funding_locked_received: @@ -1289,11 +1292,11 @@ class Peer(Logger): get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big')) channel_ready_tlvs = {} - if self.their_features.supports(LnFeatures.OPTION_SCID_ALIAS_OPT): + if self.features.supports(LnFeatures.OPTION_SCID_ALIAS_OPT): # LND requires that we send an alias if the option has been negotiated in INIT. # otherwise, the channel will not be marked as active. # This does not apply if the channel was previously marked active without an alias. - channel_ready_tlvs['short_channel_id'] = {'alias':chan.get_local_alias()} + channel_ready_tlvs['short_channel_id'] = {'alias': chan.get_local_scid_alias(create_new_if_needed=True)} # note: if 'channel_ready' was not yet received, we might send it multiple times self.send_message( @@ -1309,7 +1312,7 @@ class Peer(Logger): # save remote alias for use in invoices scid_alias = payload.get('channel_ready_tlvs', {}).get('short_channel_id', {}).get('alias') if scid_alias: - chan.save_remote_alias(scid_alias) + chan.save_remote_scid_alias(scid_alias) if not chan.config[LOCAL].funding_locked_received: their_next_point = payload["second_per_commitment_point"] @@ -1593,15 +1596,16 @@ class Peer(Logger): if chain.is_tip_stale(): raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') try: - next_chan_scid = processed_onion.hop_data.payload["short_channel_id"]["short_channel_id"] # type: bytes + _next_chan_scid = processed_onion.hop_data.payload["short_channel_id"]["short_channel_id"] # type: bytes + next_chan_scid = ShortChannelID(_next_chan_scid) except Exception: raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid) local_height = chain.height() if next_chan is None: - log_fail_reason(f"cannot find next_chan {next_chan_scid.hex()}") + log_fail_reason(f"cannot find next_chan {next_chan_scid}") raise OnionRoutingFailure(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'') - outgoing_chan_upd = next_chan.get_outgoing_gossip_channel_update()[2:] + outgoing_chan_upd = next_chan.get_outgoing_gossip_channel_update(scid=next_chan_scid)[2:] outgoing_chan_upd_len = len(outgoing_chan_upd).to_bytes(2, byteorder="big") outgoing_chan_upd_message = outgoing_chan_upd_len + outgoing_chan_upd if not next_chan.can_send_update_add_htlc(): diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 011cb831a..89ca65136 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -1729,7 +1729,7 @@ class LNWallet(LNWorker): my_sending_channels: List[Channel], full_path: Optional[LNPaymentPath]) -> LNPaymentRoute: - my_sending_aliases = set(chan.get_local_alias() for chan in my_sending_channels) + my_sending_aliases = set(chan.get_local_scid_alias() for chan in my_sending_channels) my_sending_channels = {chan.short_channel_id: chan for chan in my_sending_channels if chan.short_channel_id is not None} # Collect all private edges from route hints. @@ -2049,7 +2049,7 @@ class LNWallet(LNWorker): scid_to_my_channels = {chan.short_channel_id: chan for chan in channels if chan.short_channel_id is not None} for chan in channels: - alias_or_scid = chan.get_remote_alias() or chan.short_channel_id + alias_or_scid = chan.get_remote_scid_alias() or chan.short_channel_id assert isinstance(alias_or_scid, bytes), alias_or_scid channel_info = get_mychannel_info(chan.short_channel_id, scid_to_my_channels) # note: as a fallback, if we don't have a channel update for the