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