URI: 
       tlnverifier.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tlnverifier.py (8570B)
       ---
            1 # -*- coding: utf-8 -*-
            2 #
            3 # Electrum - lightweight Bitcoin client
            4 # Copyright (C) 2018 The Electrum developers
            5 #
            6 # Permission is hereby granted, free of charge, to any person
            7 # obtaining a copy of this software and associated documentation files
            8 # (the "Software"), to deal in the Software without restriction,
            9 # including without limitation the rights to use, copy, modify, merge,
           10 # publish, distribute, sublicense, and/or sell copies of the Software,
           11 # and to permit persons to whom the Software is furnished to do so,
           12 # subject to the following conditions:
           13 #
           14 # The above copyright notice and this permission notice shall be
           15 # included in all copies or substantial portions of the Software.
           16 #
           17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
           21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
           22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
           23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           24 # SOFTWARE.
           25 
           26 import asyncio
           27 import threading
           28 from typing import TYPE_CHECKING, Dict, Set
           29 
           30 import aiorpcx
           31 
           32 from . import bitcoin
           33 from . import ecc
           34 from . import constants
           35 from .util import bh2u, bfh, NetworkJobOnDefaultServer
           36 from .lnutil import funding_output_script_from_keys, ShortChannelID
           37 from .verifier import verify_tx_is_in_block, MerkleVerificationFailure
           38 from .transaction import Transaction
           39 from .interface import GracefulDisconnect
           40 from .crypto import sha256d
           41 from .lnmsg import decode_msg, encode_msg
           42 
           43 if TYPE_CHECKING:
           44     from .network import Network
           45     from .lnrouter import ChannelDB
           46 
           47 
           48 class LNChannelVerifier(NetworkJobOnDefaultServer):
           49     """ Verify channel announcements for the Channel DB """
           50 
           51     # FIXME the initial routing sync is bandwidth-heavy, and the electrum server
           52     # will start throttling us, making it even slower. one option would be to
           53     # spread it over multiple servers.
           54 
           55     def __init__(self, network: 'Network', channel_db: 'ChannelDB'):
           56         self.channel_db = channel_db
           57         self.lock = threading.Lock()
           58         self.unverified_channel_info = {}  # type: Dict[ShortChannelID, dict]  # scid -> msg_dict
           59         # channel announcements that seem to be invalid:
           60         self.blacklist = set()  # type: Set[ShortChannelID]
           61         NetworkJobOnDefaultServer.__init__(self, network)
           62 
           63     def _reset(self):
           64         super()._reset()
           65         self.started_verifying_channel = set()  # type: Set[ShortChannelID]
           66 
           67     # TODO make async; and rm self.lock completely
           68     def add_new_channel_info(self, short_channel_id: ShortChannelID, msg: dict) -> bool:
           69         if short_channel_id in self.unverified_channel_info:
           70             return False
           71         if short_channel_id in self.blacklist:
           72             return False
           73         with self.lock:
           74             self.unverified_channel_info[short_channel_id] = msg
           75             return True
           76 
           77     async def _run_tasks(self, *, taskgroup):
           78         await super()._run_tasks(taskgroup=taskgroup)
           79         async with taskgroup as group:
           80             await group.spawn(self.main)
           81 
           82     async def main(self):
           83         while True:
           84             await self._verify_some_channels()
           85             await asyncio.sleep(0.1)
           86 
           87     async def _verify_some_channels(self):
           88         blockchain = self.network.blockchain()
           89         local_height = blockchain.height()
           90 
           91         with self.lock:
           92             unverified_channel_info = list(self.unverified_channel_info)
           93 
           94         for short_channel_id in unverified_channel_info:
           95             if short_channel_id in self.started_verifying_channel:
           96                 continue
           97             block_height = short_channel_id.block_height
           98             # only resolve short_channel_id if headers are available.
           99             if block_height <= 0 or block_height > local_height:
          100                 continue
          101             header = blockchain.read_header(block_height)
          102             if header is None:
          103                 if block_height < constants.net.max_checkpoint():
          104                     await self.taskgroup.spawn(self.network.request_chunk(block_height, None, can_return_early=True))
          105                 continue
          106             self.started_verifying_channel.add(short_channel_id)
          107             await self.taskgroup.spawn(self.verify_channel(block_height, short_channel_id))
          108             #self.logger.info(f'requested short_channel_id {bh2u(short_channel_id)}')
          109 
          110     async def verify_channel(self, block_height: int, short_channel_id: ShortChannelID):
          111         # we are verifying channel announcements as they are from untrusted ln peers.
          112         # we use electrum servers to do this. however we don't trust electrum servers either...
          113         try:
          114             async with self._network_request_semaphore:
          115                 result = await self.network.get_txid_from_txpos(
          116                     block_height, short_channel_id.txpos, True)
          117         except aiorpcx.jsonrpc.RPCError:
          118             # the electrum server is complaining about the txpos for given block.
          119             # it is not clear what to do now, but let's believe the server.
          120             self._blacklist_short_channel_id(short_channel_id)
          121             return
          122         tx_hash = result['tx_hash']
          123         merkle_branch = result['merkle']
          124         # we need to wait if header sync/reorg is still ongoing, hence lock:
          125         async with self.network.bhi_lock:
          126             header = self.network.blockchain().read_header(block_height)
          127         try:
          128             verify_tx_is_in_block(tx_hash, merkle_branch, short_channel_id.txpos, header, block_height)
          129         except MerkleVerificationFailure as e:
          130             # the electrum server sent an incorrect proof. blame is on server, not the ln peer
          131             raise GracefulDisconnect(e) from e
          132         try:
          133             async with self._network_request_semaphore:
          134                 raw_tx = await self.network.get_transaction(tx_hash)
          135         except aiorpcx.jsonrpc.RPCError as e:
          136             # the electrum server can't find the tx; but it was the
          137             # one who told us about the txid!! blame is on server
          138             raise GracefulDisconnect(e) from e
          139         tx = Transaction(raw_tx)
          140         try:
          141             tx.deserialize()
          142         except Exception:
          143             # either bug in client, or electrum server is evil.
          144             # if we connect to a diff server at some point, let's try again.
          145             self.logger.warning(f"cannot deserialize transaction, skipping {tx_hash}")
          146             return
          147         if tx_hash != tx.txid():
          148             # either bug in client, or electrum server is evil.
          149             # if we connect to a diff server at some point, let's try again.
          150             self.logger.info(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})")
          151             return
          152         # check funding output
          153         chan_ann_msg = self.unverified_channel_info[short_channel_id]
          154         redeem_script = funding_output_script_from_keys(chan_ann_msg['bitcoin_key_1'], chan_ann_msg['bitcoin_key_2'])
          155         expected_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
          156         try:
          157             actual_output = tx.outputs()[short_channel_id.output_index]
          158         except IndexError:
          159             self._blacklist_short_channel_id(short_channel_id)
          160             return
          161         if expected_address != actual_output.address:
          162             # FIXME what now? best would be to ban the originating ln peer.
          163             self.logger.info(f"funding output script mismatch for {short_channel_id}")
          164             self._remove_channel_from_unverified_db(short_channel_id)
          165             return
          166         # put channel into channel DB
          167         self.channel_db.add_verified_channel_info(chan_ann_msg, capacity_sat=actual_output.value)
          168         self._remove_channel_from_unverified_db(short_channel_id)
          169 
          170     def _remove_channel_from_unverified_db(self, short_channel_id: ShortChannelID):
          171         with self.lock:
          172             self.unverified_channel_info.pop(short_channel_id, None)
          173         self.started_verifying_channel.discard(short_channel_id)
          174 
          175     def _blacklist_short_channel_id(self, short_channel_id: ShortChannelID) -> None:
          176         self.blacklist.add(short_channel_id)
          177         with self.lock:
          178             self.unverified_channel_info.pop(short_channel_id, None)
          179 
          180 
          181 def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool:
          182     msg_bytes = chan_upd['raw']
          183     pre_hash = msg_bytes[2+64:]
          184     h = sha256d(pre_hash)
          185     sig = chan_upd['signature']
          186     if not ecc.verify_signature(node_id, sig, h):
          187         return False
          188     return True