Browse Source

Merge pull request #8135 from spesmilo/scid_alias

Add support for option_scid_alias
master
ThomasV 3 years ago committed by GitHub
parent
commit
8fa549c418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      electrum/channel_db.py
  2. 5
      electrum/gui/qt/channel_details.py
  3. 12
      electrum/lnchannel.py
  4. 30
      electrum/lnmsg.py
  5. 4
      electrum/lnonion.py
  6. 39
      electrum/lnpeer.py
  7. 27
      electrum/lnutil.py
  8. 4
      electrum/lnwire/README.md
  9. 47
      electrum/lnwire/onion_wire.csv
  10. 27
      electrum/lnwire/peer_wire.csv
  11. 19
      electrum/lnworker.py
  12. 29
      electrum/tests/test_lnmsg.py
  13. 4
      electrum/tests/test_lnpeer.py

10
electrum/channel_db.py

@ -46,6 +46,7 @@ from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update
from .lnmsg import decode_msg
from . import ecc
from .crypto import sha256d
from .lnmsg import FailedToParseMsg
if TYPE_CHECKING:
from .network import Network
@ -725,6 +726,8 @@ class ChannelDB(SqlDB):
ci = ChannelInfo.from_raw_msg(msg)
except IncompatibleOrInsaneFeatures:
continue
except FailedToParseMsg:
continue
self._channels[ShortChannelID.normalize(short_channel_id)] = ci
c.execute("""SELECT * FROM node_info""")
for node_id, msg in c:
@ -732,11 +735,16 @@ class ChannelDB(SqlDB):
node_info, node_addresses = NodeInfo.from_raw_msg(msg)
except IncompatibleOrInsaneFeatures:
continue
except FailedToParseMsg:
continue
# don't load node_addresses because they dont have timestamps
self._nodes[node_id] = node_info
c.execute("""SELECT * FROM policy""")
for key, msg in c:
p = Policy.from_raw_msg(key, msg)
try:
p = Policy.from_raw_msg(key, msg)
except FailedToParseMsg:
continue
self._policies[(p.start_node, p.short_channel_id)] = p
for channel_info in self._channels.values():
self._channels_for_node[channel_info.node1_id].add(channel_info.short_channel_id)

5
electrum/gui/qt/channel_details.py

@ -180,10 +180,11 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener):
form.addRow(QLabel(_('Remote Node') + ':'), remote_id_e)
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()))
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))
if not chan.is_backup():

12
electrum/lnchannel.py

@ -614,6 +614,18 @@ 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 save_remote_alias(self, alias: bytes):
self.storage['alias'] = alias.hex()
def get_remote_alias(self) -> Optional[bytes]:
alias = self.storage.get('alias')
return bytes.fromhex(alias) if alias else None
def has_onchain_backup(self):
return self.storage.get('has_onchain_backup', False)

30
electrum/lnmsg.py

@ -129,7 +129,7 @@ def _read_field(*, fd: io.BytesIO, field_type: str, count: Union[int, str]) -> U
if len(raw) > 0 and raw[0] == 0x00:
raise FieldEncodingNotMinimal()
return int.from_bytes(raw, byteorder="big", signed=False)
elif field_type == 'varint':
elif field_type == 'bigsize':
assert count == 1, count
val = read_bigsize_int(fd)
if val is None:
@ -203,7 +203,7 @@ def _write_field(*, fd: io.BytesIO, field_type: str, count: Union[int, str],
if nbytes_written != len(value):
raise Exception(f"tried to write {len(value)} bytes, but only wrote {nbytes_written}!?")
return
elif field_type == 'varint':
elif field_type == 'bigsize':
assert count == 1, count
if isinstance(value, int):
value = write_bigsize_int(value)
@ -243,8 +243,8 @@ def _write_field(*, fd: io.BytesIO, field_type: str, count: Union[int, str],
def _read_tlv_record(*, fd: io.BytesIO) -> Tuple[int, bytes]:
if not fd: raise Exception()
tlv_type = _read_field(fd=fd, field_type="varint", count=1)
tlv_len = _read_field(fd=fd, field_type="varint", count=1)
tlv_type = _read_field(fd=fd, field_type="bigsize", count=1)
tlv_len = _read_field(fd=fd, field_type="bigsize", count=1)
tlv_val = _read_field(fd=fd, field_type="byte", count=tlv_len)
return tlv_type, tlv_val
@ -252,8 +252,8 @@ def _read_tlv_record(*, fd: io.BytesIO) -> Tuple[int, bytes]:
def _write_tlv_record(*, fd: io.BytesIO, tlv_type: int, tlv_val: bytes) -> None:
if not fd: raise Exception()
tlv_len = len(tlv_val)
_write_field(fd=fd, field_type="varint", count=1, value=tlv_type)
_write_field(fd=fd, field_type="varint", count=1, value=tlv_len)
_write_field(fd=fd, field_type="bigsize", count=1, value=tlv_type)
_write_field(fd=fd, field_type="bigsize", count=1, value=tlv_len)
_write_field(fd=fd, field_type="byte", count=tlv_len, value=tlv_val)
@ -454,10 +454,7 @@ class LNSerializer:
try:
field_value = kwargs[field_name]
except KeyError:
if len(row) > 5:
break # optional feature field not present
else:
field_value = 0 # default mandatory fields to zero
field_value = 0 # default mandatory fields to zero
#print(f">>> encode_msg. writing field: {field_name}. value={field_value!r}. field_type={field_type!r}. count={field_count!r}")
_write_field(fd=fd,
field_type=field_type,
@ -507,15 +504,10 @@ class LNSerializer:
parsed[tlv_stream_name] = d
continue
#print(f">> count={field_count}. parsed={parsed}")
try:
parsed[field_name] = _read_field(fd=fd,
field_type=field_type,
count=field_count)
except UnexpectedEndOfStream as e:
if len(row) > 5:
break # optional feature field not present
else:
raise
parsed[field_name] = _read_field(
fd=fd,
field_type=field_type,
count=field_count)
else:
raise Exception(f"unexpected row in scheme: {row!r}")
except FailedToParseMsg as e:

4
electrum/lnonion.py

@ -122,7 +122,7 @@ class OnionHopsDataSingle: # called HopData in lnd
else: # tlv
payload_fd = io.BytesIO()
OnionWireSerializer.write_tlv_stream(fd=payload_fd,
tlv_stream_name="tlv_payload",
tlv_stream_name="payload",
**self.payload)
payload_bytes = payload_fd.getvalue()
with io.BytesIO() as fd:
@ -157,7 +157,7 @@ class OnionHopsDataSingle: # called HopData in lnd
raise Exception(f"unexpected EOF")
ret = OnionHopsDataSingle(is_tlv_payload=True)
ret.payload = OnionWireSerializer.read_tlv_stream(fd=io.BytesIO(hop_payload),
tlv_stream_name="tlv_payload")
tlv_stream_name="payload")
ret.hmac = fd.read(PER_HOP_HMAC_SIZE)
assert len(ret.hmac) == PER_HOP_HMAC_SIZE
return ret

39
electrum/lnpeer.py

@ -388,7 +388,7 @@ class Peer(Logger):
if not self.channels:
return
for chan in self.channels.values():
if chan.short_channel_id == payload['short_channel_id']:
if payload['short_channel_id'] in [chan.short_channel_id, chan.get_local_alias()]:
chan.set_remote_update(payload)
self.logger.info(f"saved remote channel_update gossip msg for chan {chan.get_id_for_log()}")
break
@ -552,7 +552,7 @@ class Peer(Logger):
def on_reply_channel_range(self, payload):
first = payload['first_blocknum']
num = payload['number_of_blocks']
complete = bool(int.from_bytes(payload['complete'], 'big'))
complete = bool(int.from_bytes(payload['sync_complete'], 'big'))
encoded = payload['encoded_short_ids']
ids = self.decode_short_ids(encoded)
#self.logger.info(f"on_reply_channel_range. >>> first_block {first}, num_blocks {num}, num_ids {len(ids)}, complete {repr(payload['complete'])}")
@ -712,6 +712,8 @@ class Peer(Logger):
open_channel_tlvs = {}
assert self.their_features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT)
our_channel_type = ChannelType(ChannelType.OPTION_STATIC_REMOTEKEY)
# We do not set the option_scid_alias bit in channel_type because LND rejects it.
# Eclair accepts channel_type with that bit, but does not require it.
# if option_channel_type is negotiated: MUST set channel_type
if self.is_channel_type():
@ -1273,7 +1275,7 @@ class Peer(Logger):
chan.peer_state = PeerState.GOOD
if chan.is_funded() and their_next_local_ctn == next_local_ctn == 1:
self.send_funding_locked(chan)
self.send_channel_ready(chan)
# checks done
if chan.is_funded() and chan.config[LOCAL].funding_locked_received:
self.mark_open(chan)
@ -1282,20 +1284,37 @@ class Peer(Logger):
if chan.get_state() == ChannelState.SHUTDOWN:
await self.send_shutdown(chan)
def send_funding_locked(self, chan: Channel):
def send_channel_ready(self, chan: Channel):
channel_id = chan.channel_id
per_commitment_secret_index = RevocationStore.START_INDEX - 1
per_commitment_point_second = secret_to_pubkey(int.from_bytes(
second_per_commitment_point = secret_to_pubkey(int.from_bytes(
get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big'))
# note: if funding_locked was not yet received, we might send it multiple times
self.send_message("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second)
channel_ready_tlvs = {}
if self.their_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()}
# note: if 'channel_ready' was not yet received, we might send it multiple times
self.send_message(
"channel_ready",
channel_id=channel_id,
second_per_commitment_point=second_per_commitment_point,
channel_ready_tlvs=channel_ready_tlvs)
if chan.is_funded() and chan.config[LOCAL].funding_locked_received:
self.mark_open(chan)
def on_funding_locked(self, chan: Channel, payload):
self.logger.info(f"on_funding_locked. channel: {bh2u(chan.channel_id)}")
def on_channel_ready(self, chan: Channel, payload):
self.logger.info(f"on_channel_ready. channel: {bh2u(chan.channel_id)}")
# 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)
if not chan.config[LOCAL].funding_locked_received:
their_next_point = payload["next_per_commitment_point"]
their_next_point = payload["second_per_commitment_point"]
chan.config[REMOTE].next_per_commitment_point = their_next_point
chan.config[LOCAL].funding_locked_received = True
self.lnworker.save_channel(chan)

27
electrum/lnutil.py

@ -1116,6 +1116,12 @@ class LnFeatures(IntFlag):
_ln_feature_contexts[OPTION_CHANNEL_TYPE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_CHANNEL_TYPE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
OPTION_SCID_ALIAS_REQ = 1 << 46
OPTION_SCID_ALIAS_OPT = 1 << 47
_ln_feature_contexts[OPTION_SCID_ALIAS_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
_ln_feature_contexts[OPTION_SCID_ALIAS_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
def validate_transitive_dependencies(self) -> bool:
# for all even bit set, set corresponding odd bit:
features = self # copy
@ -1198,6 +1204,8 @@ class ChannelType(IntFlag):
OPTION_STATIC_REMOTEKEY = 1 << 12
OPTION_ANCHOR_OUTPUTS = 1 << 20
OPTION_ANCHORS_ZERO_FEE_HTLC_TX = 1 << 22
OPTION_SCID_ALIAS = 1 << 46
OPTION_ZEROCONF = 1 << 50
def discard_unknown_and_check(self):
"""Discards unknown flags and checks flag combination."""
@ -1215,13 +1223,12 @@ class ChannelType(IntFlag):
return final_channel_type
def check_combinations(self):
if self == ChannelType.OPTION_STATIC_REMOTEKEY:
pass
elif self == ChannelType.OPTION_ANCHOR_OUTPUTS | ChannelType.OPTION_STATIC_REMOTEKEY:
pass
elif self == ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX | ChannelType.OPTION_STATIC_REMOTEKEY:
pass
else:
basic_type = self & ~(ChannelType.OPTION_SCID_ALIAS | ChannelType.OPTION_ZEROCONF)
if basic_type not in [
ChannelType.OPTION_STATIC_REMOTEKEY,
ChannelType.OPTION_ANCHOR_OUTPUTS | ChannelType.OPTION_STATIC_REMOTEKEY,
ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX | ChannelType.OPTION_STATIC_REMOTEKEY
]:
raise ValueError("Channel type is not a valid flag combination.")
def complies_with_features(self, features: LnFeatures) -> bool:
@ -1240,7 +1247,10 @@ class ChannelType(IntFlag):
@property
def name_minimal(self):
return self.name.replace('OPTION_', '')
if self.name:
return self.name.replace('OPTION_', '')
else:
return str(self)
del LNFC # name is ambiguous without context
@ -1259,6 +1269,7 @@ LN_FEATURES_IMPLEMENTED = (
| LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM | LnFeatures.OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM
| LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_REQ
| LnFeatures.OPTION_CHANNEL_TYPE_OPT | LnFeatures.OPTION_CHANNEL_TYPE_REQ
| LnFeatures.OPTION_SCID_ALIAS_OPT | LnFeatures.OPTION_SCID_ALIAS_REQ
)

4
electrum/lnwire/README.md

@ -1,5 +1,7 @@
These files are generated from the BOLT repository:
These files have been generated from the BOLT repository:
```
$ python3 tools/extract-formats.py 01-*.md 02-*.md 07-*.md > peer_wire.csv
$ python3 tools/extract-formats.py 04-*.md > onion_wire.csv
```
Note: Trampoline messages were added manually to onion_wire.csv

47
electrum/lnwire/onion_wire.csv

@ -1,23 +1,25 @@
tlvtype,tlv_payload,amt_to_forward,2
tlvdata,tlv_payload,amt_to_forward,amt_to_forward,tu64,
tlvtype,tlv_payload,outgoing_cltv_value,4
tlvdata,tlv_payload,outgoing_cltv_value,outgoing_cltv_value,tu32,
tlvtype,tlv_payload,short_channel_id,6
tlvdata,tlv_payload,short_channel_id,short_channel_id,short_channel_id,
tlvtype,tlv_payload,payment_data,8
tlvdata,tlv_payload,payment_data,payment_secret,byte,32
tlvdata,tlv_payload,payment_data,total_msat,tu64,
tlvtype,tlv_payload,invoice_features,66097
tlvdata,tlv_payload,invoice_features,invoice_features,u64,
tlvtype,tlv_payload,outgoing_node_id,66098
tlvdata,tlv_payload,outgoing_node_id,outgoing_node_id,byte,33
tlvtype,tlv_payload,invoice_routing_info,66099
tlvdata,tlv_payload,invoice_routing_info,invoice_routing_info,byte,...
tlvtype,tlv_payload,trampoline_onion_packet,66100
tlvdata,tlv_payload,trampoline_onion_packet,version,byte,1
tlvdata,tlv_payload,trampoline_onion_packet,public_key,byte,33
tlvdata,tlv_payload,trampoline_onion_packet,hops_data,byte,400
tlvdata,tlv_payload,trampoline_onion_packet,hmac,byte,32
tlvtype,payload,amt_to_forward,2
tlvdata,payload,amt_to_forward,amt_to_forward,tu64,
tlvtype,payload,outgoing_cltv_value,4
tlvdata,payload,outgoing_cltv_value,outgoing_cltv_value,tu32,
tlvtype,payload,short_channel_id,6
tlvdata,payload,short_channel_id,short_channel_id,short_channel_id,
tlvtype,payload,payment_data,8
tlvdata,payload,payment_data,payment_secret,byte,32
tlvdata,payload,payment_data,total_msat,tu64,
tlvtype,payload,payment_metadata,16
tlvdata,payload,payment_metadata,payment_metadata,byte,...
tlvtype,payload,invoice_features,66097
tlvdata,payload,invoice_features,invoice_features,u64,
tlvtype,payload,outgoing_node_id,66098
tlvdata,payload,outgoing_node_id,outgoing_node_id,byte,33
tlvtype,payload,invoice_routing_info,66099
tlvdata,payload,invoice_routing_info,invoice_routing_info,byte,...
tlvtype,payload,trampoline_onion_packet,66100
tlvdata,payload,trampoline_onion_packet,version,byte,1
tlvdata,payload,trampoline_onion_packet,public_key,byte,33
tlvdata,payload,trampoline_onion_packet,hops_data,byte,400
tlvdata,payload,trampoline_onion_packet,hmac,byte,32
msgtype,invalid_realm,PERM|1
msgtype,temporary_node_failure,NODE|2
msgtype,permanent_node_failure,PERM|NODE|2
@ -57,8 +59,11 @@ msgdata,final_incorrect_cltv_expiry,cltv_expiry,u32,
msgtype,final_incorrect_htlc_amount,19
msgdata,final_incorrect_htlc_amount,incoming_htlc_amt,u64,
msgtype,channel_disabled,UPDATE|20
msgdata,channel_disabled,disabled_flags,u16,
msgdata,channel_disabled,len,u16,
msgdata,channel_disabled,channel_update,byte,len
msgtype,expiry_too_far,21
msgtype,invalid_onion_payload,PERM|22
msgdata,invalid_onion_payload,type,varint,
msgdata,invalid_onion_payload,type,bigsize,
msgdata,invalid_onion_payload,offset,u16,
msgtype,mpp_timeout,23

1 tlvtype,tlv_payload,amt_to_forward,2 tlvtype,payload,amt_to_forward,2
2 tlvdata,tlv_payload,amt_to_forward,amt_to_forward,tu64, tlvdata,payload,amt_to_forward,amt_to_forward,tu64,
3 tlvtype,tlv_payload,outgoing_cltv_value,4 tlvtype,payload,outgoing_cltv_value,4
4 tlvdata,tlv_payload,outgoing_cltv_value,outgoing_cltv_value,tu32, tlvdata,payload,outgoing_cltv_value,outgoing_cltv_value,tu32,
5 tlvtype,tlv_payload,short_channel_id,6 tlvtype,payload,short_channel_id,6
6 tlvdata,tlv_payload,short_channel_id,short_channel_id,short_channel_id, tlvdata,payload,short_channel_id,short_channel_id,short_channel_id,
7 tlvtype,tlv_payload,payment_data,8 tlvtype,payload,payment_data,8
8 tlvdata,tlv_payload,payment_data,payment_secret,byte,32 tlvdata,payload,payment_data,payment_secret,byte,32
9 tlvdata,tlv_payload,payment_data,total_msat,tu64, tlvdata,payload,payment_data,total_msat,tu64,
10 tlvtype,tlv_payload,invoice_features,66097 tlvtype,payload,payment_metadata,16
11 tlvdata,tlv_payload,invoice_features,invoice_features,u64, tlvdata,payload,payment_metadata,payment_metadata,byte,...
12 tlvtype,tlv_payload,outgoing_node_id,66098 tlvtype,payload,invoice_features,66097
13 tlvdata,tlv_payload,outgoing_node_id,outgoing_node_id,byte,33 tlvdata,payload,invoice_features,invoice_features,u64,
14 tlvtype,tlv_payload,invoice_routing_info,66099 tlvtype,payload,outgoing_node_id,66098
15 tlvdata,tlv_payload,invoice_routing_info,invoice_routing_info,byte,... tlvdata,payload,outgoing_node_id,outgoing_node_id,byte,33
16 tlvtype,tlv_payload,trampoline_onion_packet,66100 tlvtype,payload,invoice_routing_info,66099
17 tlvdata,tlv_payload,trampoline_onion_packet,version,byte,1 tlvdata,payload,invoice_routing_info,invoice_routing_info,byte,...
18 tlvdata,tlv_payload,trampoline_onion_packet,public_key,byte,33 tlvtype,payload,trampoline_onion_packet,66100
19 tlvdata,tlv_payload,trampoline_onion_packet,hops_data,byte,400 tlvdata,payload,trampoline_onion_packet,version,byte,1
20 tlvdata,tlv_payload,trampoline_onion_packet,hmac,byte,32 tlvdata,payload,trampoline_onion_packet,public_key,byte,33
21 tlvdata,payload,trampoline_onion_packet,hops_data,byte,400
22 tlvdata,payload,trampoline_onion_packet,hmac,byte,32
23 msgtype,invalid_realm,PERM|1 msgtype,invalid_realm,PERM|1
24 msgtype,temporary_node_failure,NODE|2 msgtype,temporary_node_failure,NODE|2
25 msgtype,permanent_node_failure,PERM|NODE|2 msgtype,permanent_node_failure,PERM|NODE|2
59 msgtype,final_incorrect_htlc_amount,19 msgtype,final_incorrect_htlc_amount,19
60 msgdata,final_incorrect_htlc_amount,incoming_htlc_amt,u64, msgdata,final_incorrect_htlc_amount,incoming_htlc_amt,u64,
61 msgtype,channel_disabled,UPDATE|20 msgtype,channel_disabled,UPDATE|20
62 msgdata,channel_disabled,disabled_flags,u16,
63 msgdata,channel_disabled,len,u16,
64 msgdata,channel_disabled,channel_update,byte,len
65 msgtype,expiry_too_far,21 msgtype,expiry_too_far,21
66 msgtype,invalid_onion_payload,PERM|22 msgtype,invalid_onion_payload,PERM|22
67 msgdata,invalid_onion_payload,type,varint, msgdata,invalid_onion_payload,type,bigsize,
68 msgdata,invalid_onion_payload,offset,u16, msgdata,invalid_onion_payload,offset,u16,
69 msgtype,mpp_timeout,23 msgtype,mpp_timeout,23

27
electrum/lnwire/peer_wire.csv

@ -6,6 +6,8 @@ msgdata,init,features,byte,flen
msgdata,init,tlvs,init_tlvs,
tlvtype,init_tlvs,networks,1
tlvdata,init_tlvs,networks,chains,chain_hash,...
tlvtype,init_tlvs,remote_addr,3
tlvdata,init_tlvs,remote_addr,data,byte,...
msgtype,error,17
msgdata,error,channel_id,channel_id,
msgdata,error,len,u16,
@ -87,9 +89,12 @@ msgdata,funding_created,signature,signature,
msgtype,funding_signed,35
msgdata,funding_signed,channel_id,channel_id,
msgdata,funding_signed,signature,signature,
msgtype,funding_locked,36
msgdata,funding_locked,channel_id,channel_id,
msgdata,funding_locked,next_per_commitment_point,point,
msgtype,channel_ready,36
msgdata,channel_ready,channel_id,channel_id,
msgdata,channel_ready,second_per_commitment_point,point,
msgdata,channel_ready,tlvs,channel_ready_tlvs,
tlvtype,channel_ready_tlvs,short_channel_id,1
tlvdata,channel_ready_tlvs,short_channel_id,alias,short_channel_id,
msgtype,shutdown,38
msgdata,shutdown,channel_id,channel_id,
msgdata,shutdown,len,u16,
@ -139,8 +144,8 @@ msgtype,channel_reestablish,136
msgdata,channel_reestablish,channel_id,channel_id,
msgdata,channel_reestablish,next_commitment_number,u64,
msgdata,channel_reestablish,next_revocation_number,u64,
msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32,option_data_loss_protect,option_static_remotekey
msgdata,channel_reestablish,my_current_per_commitment_point,point,,option_data_loss_protect,option_static_remotekey
msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32
msgdata,channel_reestablish,my_current_per_commitment_point,point,
msgtype,announcement_signatures,259
msgdata,announcement_signatures,channel_id,channel_id,
msgdata,announcement_signatures,short_channel_id,short_channel_id,
@ -180,35 +185,35 @@ msgdata,channel_update,cltv_expiry_delta,u16,
msgdata,channel_update,htlc_minimum_msat,u64,
msgdata,channel_update,fee_base_msat,u32,
msgdata,channel_update,fee_proportional_millionths,u32,
msgdata,channel_update,htlc_maximum_msat,u64,,option_channel_htlc_max
msgdata,channel_update,htlc_maximum_msat,u64,
msgtype,query_short_channel_ids,261,gossip_queries
msgdata,query_short_channel_ids,chain_hash,chain_hash,
msgdata,query_short_channel_ids,len,u16,
msgdata,query_short_channel_ids,encoded_short_ids,byte,len
msgdata,query_short_channel_ids,tlvs,query_short_channel_ids_tlvs,
tlvtype,query_short_channel_ids_tlvs,query_flags,1
tlvdata,query_short_channel_ids_tlvs,query_flags,encoding_type,u8,
tlvdata,query_short_channel_ids_tlvs,query_flags,encoding_type,byte,
tlvdata,query_short_channel_ids_tlvs,query_flags,encoded_query_flags,byte,...
msgtype,reply_short_channel_ids_end,262,gossip_queries
msgdata,reply_short_channel_ids_end,chain_hash,chain_hash,
msgdata,reply_short_channel_ids_end,complete,byte,
msgdata,reply_short_channel_ids_end,full_information,byte,
msgtype,query_channel_range,263,gossip_queries
msgdata,query_channel_range,chain_hash,chain_hash,
msgdata,query_channel_range,first_blocknum,u32,
msgdata,query_channel_range,number_of_blocks,u32,
msgdata,query_channel_range,tlvs,query_channel_range_tlvs,
tlvtype,query_channel_range_tlvs,query_option,1
tlvdata,query_channel_range_tlvs,query_option,query_option_flags,varint,
tlvdata,query_channel_range_tlvs,query_option,query_option_flags,bigsize,
msgtype,reply_channel_range,264,gossip_queries
msgdata,reply_channel_range,chain_hash,chain_hash,
msgdata,reply_channel_range,first_blocknum,u32,
msgdata,reply_channel_range,number_of_blocks,u32,
msgdata,reply_channel_range,complete,byte,
msgdata,reply_channel_range,sync_complete,byte,
msgdata,reply_channel_range,len,u16,
msgdata,reply_channel_range,encoded_short_ids,byte,len
msgdata,reply_channel_range,tlvs,reply_channel_range_tlvs,
tlvtype,reply_channel_range_tlvs,timestamps_tlv,1
tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoding_type,u8,
tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoding_type,byte,
tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoded_timestamps,byte,...
tlvtype,reply_channel_range_tlvs,checksums_tlv,3
tlvdata,reply_channel_range_tlvs,checksums_tlv,checksums,channel_update_checksums,...

1 msgtype,init,16
6 msgdata,init,tlvs,init_tlvs,
7 tlvtype,init_tlvs,networks,1
8 tlvdata,init_tlvs,networks,chains,chain_hash,...
9 tlvtype,init_tlvs,remote_addr,3
10 tlvdata,init_tlvs,remote_addr,data,byte,...
11 msgtype,error,17
12 msgdata,error,channel_id,channel_id,
13 msgdata,error,len,u16,
89 msgtype,funding_signed,35
90 msgdata,funding_signed,channel_id,channel_id,
91 msgdata,funding_signed,signature,signature,
92 msgtype,funding_locked,36 msgtype,channel_ready,36
93 msgdata,funding_locked,channel_id,channel_id, msgdata,channel_ready,channel_id,channel_id,
94 msgdata,funding_locked,next_per_commitment_point,point, msgdata,channel_ready,second_per_commitment_point,point,
95 msgdata,channel_ready,tlvs,channel_ready_tlvs,
96 tlvtype,channel_ready_tlvs,short_channel_id,1
97 tlvdata,channel_ready_tlvs,short_channel_id,alias,short_channel_id,
98 msgtype,shutdown,38
99 msgdata,shutdown,channel_id,channel_id,
100 msgdata,shutdown,len,u16,
144 msgdata,channel_reestablish,channel_id,channel_id,
145 msgdata,channel_reestablish,next_commitment_number,u64,
146 msgdata,channel_reestablish,next_revocation_number,u64,
147 msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32,option_data_loss_protect,option_static_remotekey msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32
148 msgdata,channel_reestablish,my_current_per_commitment_point,point,,option_data_loss_protect,option_static_remotekey msgdata,channel_reestablish,my_current_per_commitment_point,point,
149 msgtype,announcement_signatures,259
150 msgdata,announcement_signatures,channel_id,channel_id,
151 msgdata,announcement_signatures,short_channel_id,short_channel_id,
185 msgdata,channel_update,htlc_minimum_msat,u64,
186 msgdata,channel_update,fee_base_msat,u32,
187 msgdata,channel_update,fee_proportional_millionths,u32,
188 msgdata,channel_update,htlc_maximum_msat,u64,,option_channel_htlc_max msgdata,channel_update,htlc_maximum_msat,u64,
189 msgtype,query_short_channel_ids,261,gossip_queries
190 msgdata,query_short_channel_ids,chain_hash,chain_hash,
191 msgdata,query_short_channel_ids,len,u16,
192 msgdata,query_short_channel_ids,encoded_short_ids,byte,len
193 msgdata,query_short_channel_ids,tlvs,query_short_channel_ids_tlvs,
194 tlvtype,query_short_channel_ids_tlvs,query_flags,1
195 tlvdata,query_short_channel_ids_tlvs,query_flags,encoding_type,u8, tlvdata,query_short_channel_ids_tlvs,query_flags,encoding_type,byte,
196 tlvdata,query_short_channel_ids_tlvs,query_flags,encoded_query_flags,byte,...
197 msgtype,reply_short_channel_ids_end,262,gossip_queries
198 msgdata,reply_short_channel_ids_end,chain_hash,chain_hash,
199 msgdata,reply_short_channel_ids_end,complete,byte, msgdata,reply_short_channel_ids_end,full_information,byte,
200 msgtype,query_channel_range,263,gossip_queries
201 msgdata,query_channel_range,chain_hash,chain_hash,
202 msgdata,query_channel_range,first_blocknum,u32,
203 msgdata,query_channel_range,number_of_blocks,u32,
204 msgdata,query_channel_range,tlvs,query_channel_range_tlvs,
205 tlvtype,query_channel_range_tlvs,query_option,1
206 tlvdata,query_channel_range_tlvs,query_option,query_option_flags,varint, tlvdata,query_channel_range_tlvs,query_option,query_option_flags,bigsize,
207 msgtype,reply_channel_range,264,gossip_queries
208 msgdata,reply_channel_range,chain_hash,chain_hash,
209 msgdata,reply_channel_range,first_blocknum,u32,
210 msgdata,reply_channel_range,number_of_blocks,u32,
211 msgdata,reply_channel_range,complete,byte, msgdata,reply_channel_range,sync_complete,byte,
212 msgdata,reply_channel_range,len,u16,
213 msgdata,reply_channel_range,encoded_short_ids,byte,len
214 msgdata,reply_channel_range,tlvs,reply_channel_range_tlvs,
215 tlvtype,reply_channel_range_tlvs,timestamps_tlv,1
216 tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoding_type,u8, tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoding_type,byte,
217 tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoded_timestamps,byte,...
218 tlvtype,reply_channel_range_tlvs,checksums_tlv,3
219 tlvdata,reply_channel_range_tlvs,checksums_tlv,checksums,channel_update_checksums,...

19
electrum/lnworker.py

@ -187,6 +187,7 @@ LNWALLET_FEATURES = BASE_FEATURES\
| LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM\
| LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT\
| LnFeatures.OPTION_CHANNEL_TYPE_OPT\
| LnFeatures.OPTION_SCID_ALIAS_OPT\
LNGOSSIP_FEATURES = BASE_FEATURES\
| LnFeatures.GOSSIP_QUERIES_OPT\
@ -1002,7 +1003,7 @@ class LNWallet(LNWorker):
elif chan.get_state() == ChannelState.FUNDED:
peer = self._peers.get(chan.node_id)
if peer and peer.is_initialized():
peer.send_funding_locked(chan)
peer.send_channel_ready(chan)
elif chan.get_state() == ChannelState.OPEN:
peer = self._peers.get(chan.node_id)
@ -1361,6 +1362,7 @@ class LNWallet(LNWorker):
# send a single htlc
short_channel_id = route[0].short_channel_id
chan = self.get_channel_by_short_id(short_channel_id)
assert chan, ShortChannelID(short_channel_id)
peer = self._peers.get(route[0].node_id)
if not peer:
raise PaymentFailure('Dropped peer')
@ -1708,6 +1710,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_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.
@ -1719,6 +1722,10 @@ class LNWallet(LNWorker):
private_path_nodes = [edge[0] for edge in private_path][1:] + [invoice_pubkey]
private_path_rest = [edge[1:] for edge in private_path]
start_node = private_path[0][0]
# remove aliases from direct routes
if len(private_path) == 1 and private_path[0][1] in my_sending_aliases:
self.logger.info(f'create_route: skipping alias {ShortChannelID(private_path[0][1])}')
continue
for end_node, edge_rest in zip(private_path_nodes, private_path_rest):
short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta = edge_rest
short_channel_id = ShortChannelID(short_channel_id)
@ -2024,9 +2031,9 @@ 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:
chan_id = chan.short_channel_id
assert isinstance(chan_id, bytes), chan_id
channel_info = get_mychannel_info(chan_id, scid_to_my_channels)
alias_or_scid = chan.get_remote_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
# incoming direction of our private channel, we fill the invoice with garbage.
# the sender should still be able to pay us, but will incur an extra round trip
@ -2044,11 +2051,11 @@ class LNWallet(LNWorker):
missing_info = False
if missing_info:
self.logger.info(
f"Warning. Missing channel update for our channel {chan_id}; "
f"Warning. Missing channel update for our channel {chan.short_channel_id}; "
f"filling invoice with incorrect data.")
routing_hints.append(('r', [(
chan.node_id,
chan_id,
alias_or_scid,
fee_base_msat,
fee_proportional_millionths,
cltv_expiry_delta)]))

29
electrum/tests/test_lnmsg.py

@ -213,35 +213,6 @@ class TestLNMsg(TestCaseForTestnet):
),
decode_msg(bfh("01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000000d43100006f00025e6ed0830100009000000000000000c8000001f400000023000000003b9aca00")))
def test_encode_decode_msg__missing_optional_field_will_not_appear_in_decoded_dict(self):
# "channel_update": optional field "htlc_maximum_msat" missing -> does not get put into dict
self.assertEqual(bfh("01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000000d43100006f00025e6ed0830100009000000000000000c8000001f400000023"),
encode_msg(
"channel_update",
short_channel_id=ShortChannelID.from_components(54321, 111, 2),
channel_flags=b'\x00',
message_flags=b'\x01',
cltv_expiry_delta=144,
htlc_minimum_msat=200,
fee_base_msat=500,
fee_proportional_millionths=35,
chain_hash=constants.net.rev_genesis_bytes(),
timestamp=1584320643,
))
self.assertEqual(('channel_update',
{'chain_hash': b'CI\x7f\xd7\xf8&\x95q\x08\xf4\xa3\x0f\xd9\xce\xc3\xae\xbay\x97 \x84\xe9\x0e\xad\x01\xea3\t\x00\x00\x00\x00',
'channel_flags': b'\x00',
'cltv_expiry_delta': 144,
'fee_base_msat': 500,
'fee_proportional_millionths': 35,
'htlc_minimum_msat': 200,
'message_flags': b'\x01',
'short_channel_id': b'\x00\xd41\x00\x00o\x00\x02',
'signature': bytes(64),
'timestamp': 1584320643}
),
decode_msg(bfh("01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000000d43100006f00025e6ed0830100009000000000000000c8000001f400000023")))
def test_encode_decode_msg__ints_can_be_passed_as_bytes(self):
self.assertEqual(bfh("01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000000d43100006f00025e6ed0830100009000000000000000c8000001f400000023000000003b9aca00"),
encode_msg(

4
electrum/tests/test_lnpeer.py

@ -123,6 +123,9 @@ class MockWallet:
def is_mine(self, addr):
return True
def get_fingerprint(self):
return ''
class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
MPP_EXPIRY = 2 # HTLC timestamps are cast to int, so this cannot be 1
@ -152,6 +155,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
self.features |= LnFeatures.PAYMENT_SECRET_OPT
self.features |= LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
self.features |= LnFeatures.OPTION_CHANNEL_TYPE_OPT
self.features |= LnFeatures.OPTION_SCID_ALIAS_OPT
self.pending_payments = defaultdict(asyncio.Future)
for chan in chans:
chan.lnworker = self

Loading…
Cancel
Save