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.
This commit is contained in:
@@ -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))
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user