URI: 
       tLNPeerAddr: validate arguments - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 13d69973555fef8802cce264f7ea324e89e36684
   DIR parent edba59ef54b4489ddccdbe70b883bfec42be3d3a
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Tue, 26 Nov 2019 00:15:33 +0100
       
       LNPeerAddr: validate arguments
       
       no longer subclassing NamedTuple (as it is difficult to do validation then...)
       
       Diffstat:
         M electrum/channel_db.py              |      14 ++++++++++----
         M electrum/lntransport.py             |      23 ++++++++++++++---------
         M electrum/lnutil.py                  |      29 +++++++++++++++++++++++------
         M electrum/lnworker.py                |       7 ++++++-
       
       4 files changed, 53 insertions(+), 20 deletions(-)
       ---
   DIR diff --git a/electrum/channel_db.py b/electrum/channel_db.py
       t@@ -281,13 +281,19 @@ class ChannelDB(SqlDB):
                    return None
                addr = sorted(list(r), key=lambda x: x[2])[0]
                host, port, timestamp = addr
       -        return LNPeerAddr(host, port, node_id)
       +        try:
       +            return LNPeerAddr(host, port, node_id)
       +        except ValueError:
       +            return None
        
            def get_recent_peers(self):
                assert self.data_loaded.is_set(), "channelDB load_data did not finish yet!"
       -        r = [self.get_last_good_address(x) for x in self._addresses.keys()]
       -        r = r[-self.NUM_MAX_RECENT_PEERS:]
       -        return r
       +        # FIXME this does not reliably return "recent" peers...
       +        #       Also, the list() cast over the whole dict (thousands of elements),
       +        #       is really inefficient.
       +        r = [self.get_last_good_address(node_id)
       +             for node_id in list(self._addresses.keys())[-self.NUM_MAX_RECENT_PEERS:]]
       +        return list(reversed(r))
        
            def add_channel_announcement(self, msg_payloads, trusted=True):
                if type(msg_payloads) is dict:
   DIR diff --git a/electrum/lntransport.py b/electrum/lntransport.py
       t@@ -8,11 +8,12 @@
        import hashlib
        import asyncio
        from asyncio import StreamReader, StreamWriter
       +
        from Cryptodome.Cipher import ChaCha20_Poly1305
        
        from .crypto import sha256, hmac_oneshot
        from .lnutil import (get_ecdh, privkey_to_pubkey, LightningPeerConnectionClosed,
       -                     HandshakeFailed)
       +                     HandshakeFailed, LNPeerAddr)
        from . import ecc
        from .util import bh2u
        
       t@@ -86,7 +87,13 @@ def create_ephemeral_key() -> (bytes, bytes):
            privkey = ecc.ECPrivkey.generate_random_key()
            return privkey.get_secret_bytes(), privkey.get_public_key_bytes()
        
       +
        class LNTransportBase:
       +    reader: StreamReader
       +    writer: StreamWriter
       +
       +    def name(self) -> str:
       +        raise NotImplementedError()
        
            def send_bytes(self, msg: bytes) -> None:
                l = len(msg).to_bytes(2, 'big')
       t@@ -207,21 +214,18 @@ class LNResponderTransport(LNTransportBase):
        
        class LNTransport(LNTransportBase):
        
       -    def __init__(self, privkey: bytes, peer_addr):
       +    def __init__(self, privkey: bytes, peer_addr: LNPeerAddr):
                LNTransportBase.__init__(self)
                assert type(privkey) is bytes and len(privkey) == 32
                self.privkey = privkey
       -        self.remote_pubkey = peer_addr.pubkey
       -        self.host = peer_addr.host
       -        self.port = peer_addr.port
                self.peer_addr = peer_addr
        
            def name(self):
       -        return str(self.host) + ':' + str(self.port)
       +        return self.peer_addr.net_addr_str()
        
            async def handshake(self):
       -        self.reader, self.writer = await asyncio.open_connection(self.host, self.port)
       -        hs = HandshakeState(self.remote_pubkey)
       +        self.reader, self.writer = await asyncio.open_connection(self.peer_addr.host, self.peer_addr.port)
       +        hs = HandshakeState(self.peer_addr.pubkey)
                # Get a new ephemeral key
                epriv, epub = create_ephemeral_key()
        
       t@@ -230,7 +234,8 @@ class LNTransport(LNTransportBase):
                self.writer.write(msg)
                rspns = await self.reader.read(2**10)
                if len(rspns) != 50:
       -            raise HandshakeFailed(f"Lightning handshake act 1 response has bad length, are you sure this is the right pubkey? {bh2u(self.remote_pubkey)}")
       +            raise HandshakeFailed(f"Lightning handshake act 1 response has bad length, "
       +                                  f"are you sure this is the right pubkey? {self.peer_addr}")
                hver, alice_epub, tag = rspns[0], rspns[1:34], rspns[34:]
                if bytes([hver]) != hs.handshake_version:
                    raise HandshakeFailed("unexpected handshake version: {}".format(hver))
   DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py
       t@@ -658,14 +658,31 @@ class LnGlobalFeatures(IntFlag):
        LN_GLOBAL_FEATURES_KNOWN_SET = set(LnGlobalFeatures)
        
        
       -class LNPeerAddr(NamedTuple):
       -    host: str
       -    port: int
       -    pubkey: bytes
       +class LNPeerAddr:
       +
       +    def __init__(self, host: str, port: int, pubkey: bytes):
       +        assert isinstance(host, str), repr(host)
       +        assert isinstance(port, int), repr(port)
       +        assert isinstance(pubkey, bytes), repr(pubkey)
       +        try:
       +            net_addr = NetAddress(host, port)  # this validates host and port
       +        except Exception as e:
       +            raise ValueError(f"cannot construct LNPeerAddr: invalid host or port (host={host}, port={port})") from e
       +        # note: not validating pubkey as it would be too expensive:
       +        # if not ECPubkey.is_pubkey_bytes(pubkey): raise ValueError()
       +        self.host = host
       +        self.port = port
       +        self.pubkey = pubkey
       +        self._net_addr_str = str(net_addr)
        
            def __str__(self):
       -        host_and_port = str(NetAddress(self.host, self.port))
       -        return '{}@{}'.format(self.pubkey.hex(), host_and_port)
       +        return '{}@{}'.format(self.pubkey.hex(), self.net_addr_str())
       +
       +    def __repr__(self):
       +        return f'<LNPeerAddr host={self.host} port={self.port} pubkey={self.pubkey.hex()}>'
       +
       +    def net_addr_str(self) -> str:
       +        return self._net_addr_str
        
        
        def get_compressed_pubkey_from_bech32(bech32_pubkey: str) -> bytes:
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -221,7 +221,10 @@ class LNWorker(Logger):
                        if not addrs:
                            continue
                        host, port, timestamp = self.choose_preferred_address(list(addrs))
       -                peer = LNPeerAddr(host, port, node_id)
       +                try:
       +                    peer = LNPeerAddr(host, port, node_id)
       +                except ValueError:
       +                    continue
                        if peer in self._last_tried_peer:
                            continue
                        #self.logger.info('taking random ln peer from our channel db')
       t@@ -1265,6 +1268,8 @@ class LNWallet(LNWorker):
                self.network.trigger_callback('channels_updated', self.wallet)
                self.network.trigger_callback('wallet_updated', self.wallet)
        
       +    @ignore_exceptions
       +    @log_exceptions
            async def reestablish_peer_for_given_channel(self, chan):
                now = time.time()
                # try last good address first