Browse Source

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.
master
SomberNight 3 years ago
parent
commit
ca93af2b8a
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 9
      electrum/gui/qt/channel_details.py
  2. 38
      electrum/lnchannel.py
  3. 18
      electrum/lnpeer.py
  4. 4
      electrum/lnworker.py

9
electrum/gui/qt/channel_details.py

@ -5,7 +5,7 @@ import PyQt5.QtWidgets as QtWidgets
import PyQt5.QtCore as QtCore import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout 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.i18n import _
from electrum.util import format_time from electrum.util import format_time
from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction 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")) 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(_('Channel ID') + ':'), channel_id_e)
form.addRow(QLabel(_('Short Channel ID') + ':'), QLabel(str(chan.short_channel_id))) form.addRow(QLabel(_('Short Channel ID') + ':'), QLabel(str(chan.short_channel_id)))
alias = chan.get_remote_alias() if local_scid_alias := chan.get_local_scid_alias():
if alias: form.addRow(QLabel('Local SCID Alias:'), QLabel(str(ShortID(local_scid_alias))))
form.addRow(QLabel(_('Alias') + ':'), QLabel('0x'+alias.hex())) 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())) form.addRow(QLabel(_('State') + ':'), SelectableLabel(chan.get_state_for_GUI()))
self.capacity = self.format_sat(chan.get_capacity()) self.capacity = self.format_sat(chan.get_capacity())
form.addRow(QLabel(_('Capacity') + ':'), SelectableLabel(self.capacity)) form.addRow(QLabel(_('Capacity') + ':'), SelectableLabel(self.capacity))

38
electrum/lnchannel.py

@ -532,7 +532,7 @@ class ChannelBackup(AbstractChannel):
def is_backup(self): def is_backup(self):
return True return True
def get_remote_alias(self) -> Optional[bytes]: def get_remote_scid_alias(self) -> Optional[bytes]:
return None return None
def create_sweeptxs_for_their_ctx(self, ctx): def create_sweeptxs_for_their_ctx(self, ctx):
@ -640,15 +640,27 @@ class Channel(AbstractChannel):
self.should_request_force_close = False self.should_request_force_close = False
self.unconfirmed_closing_txid = None # not a state, only for GUI self.unconfirmed_closing_txid = None # not a state, only for GUI
def get_local_alias(self) -> bytes: def get_local_scid_alias(self, *, create_new_if_needed: bool = False) -> Optional[bytes]:
# deterministic, same secrecy level as wallet master pubkey """Get scid_alias to be used for *outgoing* HTLCs.
wallet_fingerprint = bytes(self.lnworker.wallet.get_fingerprint(), "utf8") (called local as we choose the value)
return sha256(wallet_fingerprint + self.channel_id)[0:8] """
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() 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') alias = self.storage.get('alias')
return bytes.fromhex(alias) if alias else None 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 This message contains info we need to populate private route hints when
creating invoices. creating invoices.
""" """
assert payload['short_channel_id'] in [self.short_channel_id, self.get_local_scid_alias()]
from .channel_db import ChannelDB from .channel_db import ChannelDB
ChannelDB.verify_channel_update(payload, start_node=self.node_id) ChannelDB.verify_channel_update(payload, start_node=self.node_id)
raw = payload['raw'] raw = payload['raw']
@ -719,11 +732,16 @@ class Channel(AbstractChannel):
net_addr = NetAddress.from_string(net_addr_str) net_addr = NetAddress.from_string(net_addr_str)
yield LNPeerAddr(host=str(net_addr.host), port=net_addr.port, pubkey=self.node_id) yield LNPeerAddr(host=str(net_addr.host), port=net_addr.port, pubkey=self.node_id)
def get_outgoing_gossip_channel_update(self) -> bytes: def get_outgoing_gossip_channel_update(self, *, scid: ShortChannelID = None) -> bytes:
if self._outgoing_channel_update is not None: """
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 return self._outgoing_channel_update
if not self.lnworker: if not self.lnworker:
raise Exception('lnworker not set for channel!') 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()])) 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' channel_flags = b'\x00' if sorted_node_ids[0] == self.get_local_pubkey() else b'\x01'
now = int(time.time()) now = int(time.time())
@ -731,7 +749,7 @@ class Channel(AbstractChannel):
chan_upd = encode_msg( chan_upd = encode_msg(
"channel_update", "channel_update",
short_channel_id=self.short_channel_id, short_channel_id=scid,
channel_flags=channel_flags, channel_flags=channel_flags,
message_flags=b'\x01', message_flags=b'\x01',
cltv_expiry_delta=self.forwarding_cltv_expiry_delta, cltv_expiry_delta=self.forwarding_cltv_expiry_delta,

18
electrum/lnpeer.py

@ -385,7 +385,7 @@ class Peer(Logger):
if not self.channels: if not self.channels:
return return
for chan in self.channels.values(): 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) chan.set_remote_update(payload)
self.logger.info(f"saved remote channel_update gossip msg for chan {chan.get_id_for_log()}") self.logger.info(f"saved remote channel_update gossip msg for chan {chan.get_id_for_log()}")
break break
@ -1272,7 +1272,10 @@ class Peer(Logger):
resend_revoke_and_ack() resend_revoke_and_ack()
chan.peer_state = PeerState.GOOD chan.peer_state = PeerState.GOOD
chan_just_became_ready = False
if chan.is_funded() and their_next_local_ctn == next_local_ctn == 1: 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) self.send_channel_ready(chan)
# checks done # checks done
if chan.is_funded() and chan.config[LOCAL].funding_locked_received: 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')) get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big'))
channel_ready_tlvs = {} 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. # LND requires that we send an alias if the option has been negotiated in INIT.
# otherwise, the channel will not be marked as active. # otherwise, the channel will not be marked as active.
# This does not apply if the channel was previously marked active without an alias. # 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 # note: if 'channel_ready' was not yet received, we might send it multiple times
self.send_message( self.send_message(
@ -1309,7 +1312,7 @@ class Peer(Logger):
# save remote alias for use in invoices # save remote alias for use in invoices
scid_alias = payload.get('channel_ready_tlvs', {}).get('short_channel_id', {}).get('alias') scid_alias = payload.get('channel_ready_tlvs', {}).get('short_channel_id', {}).get('alias')
if scid_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: if not chan.config[LOCAL].funding_locked_received:
their_next_point = payload["second_per_commitment_point"] their_next_point = payload["second_per_commitment_point"]
@ -1593,15 +1596,16 @@ class Peer(Logger):
if chain.is_tip_stale(): if chain.is_tip_stale():
raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'') raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
try: 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: except Exception:
raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00') raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid) next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid)
local_height = chain.height() local_height = chain.height()
if next_chan is None: 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'') 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_len = len(outgoing_chan_upd).to_bytes(2, byteorder="big")
outgoing_chan_upd_message = outgoing_chan_upd_len + outgoing_chan_upd outgoing_chan_upd_message = outgoing_chan_upd_len + outgoing_chan_upd
if not next_chan.can_send_update_add_htlc(): if not next_chan.can_send_update_add_htlc():

4
electrum/lnworker.py

@ -1729,7 +1729,7 @@ class LNWallet(LNWorker):
my_sending_channels: List[Channel], my_sending_channels: List[Channel],
full_path: Optional[LNPaymentPath]) -> LNPaymentRoute: 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 my_sending_channels = {chan.short_channel_id: chan for chan in my_sending_channels
if chan.short_channel_id is not None} if chan.short_channel_id is not None}
# Collect all private edges from route hints. # 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 scid_to_my_channels = {chan.short_channel_id: chan for chan in channels
if chan.short_channel_id is not None} if chan.short_channel_id is not None}
for chan in channels: 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 assert isinstance(alias_or_scid, bytes), alias_or_scid
channel_info = get_mychannel_info(chan.short_channel_id, scid_to_my_channels) 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 # note: as a fallback, if we don't have a channel update for the

Loading…
Cancel
Save