mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
persist channel db on disk. verify channel gossip sigs.
This commit is contained in:
parent
80bbd9edc3
commit
6742654681
8 changed files with 396 additions and 54 deletions
|
@ -26,6 +26,8 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
from . import bitcoin
|
||||
|
||||
|
||||
def read_json(filename, default):
|
||||
path = os.path.join(os.path.dirname(__file__), filename)
|
||||
|
@ -43,6 +45,10 @@ class AbstractNet:
|
|||
def max_checkpoint(cls) -> int:
|
||||
return max(0, len(cls.CHECKPOINTS) * 2016 - 1)
|
||||
|
||||
@classmethod
|
||||
def rev_genesis_bytes(cls) -> bytes:
|
||||
return bytes.fromhex(bitcoin.rev_hex(cls.GENESIS))
|
||||
|
||||
|
||||
class BitcoinMainnet(AbstractNet):
|
||||
|
||||
|
|
|
@ -307,7 +307,7 @@ def msg_magic(message: bytes) -> bytes:
|
|||
return b"\x18Bitcoin Signed Message:\n" + length + message
|
||||
|
||||
|
||||
def verify_signature(pubkey, sig, h):
|
||||
def verify_signature(pubkey: bytes, sig: bytes, h: bytes) -> bool:
|
||||
try:
|
||||
ECPubkey(pubkey).verify_message_hash(sig, h)
|
||||
except:
|
||||
|
|
168
electrum/lnchanannverifier.py
Normal file
168
electrum/lnchanannverifier.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Electrum - lightweight Bitcoin client
|
||||
# Copyright (C) 2018 The Electrum developers
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import threading
|
||||
|
||||
from . import lnbase
|
||||
from . import bitcoin
|
||||
from . import ecc
|
||||
from .util import ThreadJob, bh2u, bfh
|
||||
from .lnutil import invert_short_channel_id, funding_output_script_from_keys
|
||||
from .verifier import verify_tx_is_in_block, MerkleVerificationFailure
|
||||
from .transaction import Transaction
|
||||
|
||||
|
||||
class LNChanAnnVerifier(ThreadJob):
|
||||
""" Verify channel announcements for the Channel DB """
|
||||
|
||||
def __init__(self, network, channel_db):
|
||||
self.network = network
|
||||
self.channel_db = channel_db
|
||||
self.lock = threading.Lock()
|
||||
|
||||
# items only removed when whole verification succeeds for them.
|
||||
# fixme: if it fails, it will never succeed
|
||||
self.started_verifying_channel = set() # short_channel_id
|
||||
|
||||
self.unverified_channel_info = {} # short_channel_id -> channel_info
|
||||
|
||||
def add_new_channel_info(self, channel_info):
|
||||
short_channel_id = channel_info.channel_id
|
||||
if short_channel_id in self.unverified_channel_info:
|
||||
return
|
||||
if not verify_sigs_for_channel_announcement(channel_info.msg_payload):
|
||||
return
|
||||
with self.lock:
|
||||
self.unverified_channel_info[short_channel_id] = channel_info
|
||||
|
||||
def get_pending_channel_info(self, short_channel_id):
|
||||
return self.unverified_channel_info.get(short_channel_id, None)
|
||||
|
||||
def run(self):
|
||||
interface = self.network.interface
|
||||
if not interface:
|
||||
return
|
||||
|
||||
blockchain = interface.blockchain
|
||||
if not blockchain:
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
unverified_channel_info = list(self.unverified_channel_info)
|
||||
|
||||
for short_channel_id in unverified_channel_info:
|
||||
if short_channel_id in self.started_verifying_channel:
|
||||
continue
|
||||
block_height, tx_pos, output_idx = invert_short_channel_id(short_channel_id)
|
||||
# only resolve short_channel_id if headers are available.
|
||||
header = blockchain.read_header(block_height)
|
||||
if header is None:
|
||||
index = block_height // 2016
|
||||
if index < len(blockchain.checkpoints):
|
||||
self.network.request_chunk(interface, index)
|
||||
continue
|
||||
callback = lambda resp, short_channel_id=short_channel_id: self.on_txid_and_merkle(resp, short_channel_id)
|
||||
self.network.get_txid_from_txpos(block_height, tx_pos, True,
|
||||
callback=callback)
|
||||
#self.print_error('requested short_channel_id', bh2u(short_channel_id))
|
||||
with self.lock:
|
||||
self.started_verifying_channel.add(short_channel_id)
|
||||
|
||||
def on_txid_and_merkle(self, response, short_channel_id):
|
||||
if response.get('error'):
|
||||
self.print_error('received an error:', response)
|
||||
return
|
||||
result = response['result']
|
||||
tx_hash = result['tx_hash']
|
||||
merkle_branch = result['merkle']
|
||||
block_height, tx_pos, output_idx = invert_short_channel_id(short_channel_id)
|
||||
header = self.network.blockchain().read_header(block_height)
|
||||
try:
|
||||
verify_tx_is_in_block(tx_hash, merkle_branch, tx_pos, header, block_height)
|
||||
except MerkleVerificationFailure as e:
|
||||
self.print_error(str(e))
|
||||
return
|
||||
callback = lambda resp, short_channel_id=short_channel_id: self.on_tx_response(resp, short_channel_id)
|
||||
self.network.get_transaction(tx_hash, callback=callback)
|
||||
|
||||
def on_tx_response(self, response, short_channel_id):
|
||||
if response.get('error'):
|
||||
self.print_error('received an error:', response)
|
||||
return
|
||||
params = response['params']
|
||||
result = response['result']
|
||||
tx_hash = params[0]
|
||||
tx = Transaction(result)
|
||||
try:
|
||||
tx.deserialize()
|
||||
except Exception:
|
||||
self.print_msg("cannot deserialize transaction, skipping", tx_hash)
|
||||
return
|
||||
if tx_hash != tx.txid():
|
||||
self.print_error("received tx does not match expected txid ({} != {})"
|
||||
.format(tx_hash, tx.txid()))
|
||||
return
|
||||
# check funding output
|
||||
channel_info = self.unverified_channel_info[short_channel_id]
|
||||
chan_ann = channel_info.msg_payload
|
||||
redeem_script = funding_output_script_from_keys(chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2'])
|
||||
expected_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
|
||||
output_idx = invert_short_channel_id(short_channel_id)[2]
|
||||
try:
|
||||
actual_output = tx.outputs()[output_idx]
|
||||
except IndexError:
|
||||
return
|
||||
if expected_address != actual_output[1]:
|
||||
return
|
||||
# put channel into channel DB
|
||||
channel_info.set_capacity(actual_output[2])
|
||||
self.channel_db.add_verified_channel_info(short_channel_id, channel_info)
|
||||
# remove channel from unverified
|
||||
with self.lock:
|
||||
self.unverified_channel_info.pop(short_channel_id, None)
|
||||
try: self.started_verifying_channel.remove(short_channel_id)
|
||||
except KeyError: pass
|
||||
|
||||
|
||||
def verify_sigs_for_channel_announcement(chan_ann: dict) -> bool:
|
||||
msg_bytes = lnbase.gen_msg('channel_announcement', **chan_ann)
|
||||
pre_hash = msg_bytes[2+256:]
|
||||
h = bitcoin.Hash(pre_hash)
|
||||
pubkeys = [chan_ann['node_id_1'], chan_ann['node_id_2'], chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']]
|
||||
sigs = [chan_ann['node_signature_1'], chan_ann['node_signature_2'], chan_ann['bitcoin_signature_1'], chan_ann['bitcoin_signature_2']]
|
||||
for pubkey, sig in zip(pubkeys, sigs):
|
||||
if not ecc.verify_signature(pubkey, sig, h):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool:
|
||||
msg_bytes = lnbase.gen_msg('channel_update', **chan_upd)
|
||||
pre_hash = msg_bytes[2+64:]
|
||||
h = bitcoin.Hash(pre_hash)
|
||||
sig = chan_upd['signature']
|
||||
if not ecc.verify_signature(node_id, sig, h):
|
||||
return False
|
||||
return True
|
|
@ -30,8 +30,11 @@ import sys
|
|||
import binascii
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
from collections import namedtuple, defaultdict
|
||||
from typing import Sequence, Union, Tuple
|
||||
from typing import Sequence, Union, Tuple, Optional
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
@ -39,9 +42,12 @@ from cryptography.hazmat.backends import default_backend
|
|||
from . import bitcoin
|
||||
from . import ecc
|
||||
from . import crypto
|
||||
from . import constants
|
||||
from .crypto import sha256
|
||||
from .util import PrintError, bh2u, profiler, xor_bytes
|
||||
from .util import PrintError, bh2u, profiler, xor_bytes, get_headers_dir, bfh
|
||||
from .lnutil import get_ecdh
|
||||
from .storage import JsonDB
|
||||
from .lnchanannverifier import LNChanAnnVerifier, verify_sig_for_channel_update
|
||||
|
||||
|
||||
class ChannelInfo(PrintError):
|
||||
|
@ -54,23 +60,71 @@ class ChannelInfo(PrintError):
|
|||
assert type(self.node_id_2) is bytes
|
||||
assert list(sorted([self.node_id_1, self.node_id_2])) == [self.node_id_1, self.node_id_2]
|
||||
|
||||
self.features_len = channel_announcement_payload['len']
|
||||
self.features = channel_announcement_payload['features']
|
||||
self.bitcoin_key_1 = channel_announcement_payload['bitcoin_key_1']
|
||||
self.bitcoin_key_2 = channel_announcement_payload['bitcoin_key_2']
|
||||
|
||||
# this field does not get persisted
|
||||
self.msg_payload = channel_announcement_payload
|
||||
|
||||
self.capacity_sat = None
|
||||
self.policy_node1 = None
|
||||
self.policy_node2 = None
|
||||
|
||||
def to_json(self) -> dict:
|
||||
d = {}
|
||||
d['short_channel_id'] = bh2u(self.channel_id)
|
||||
d['node_id_1'] = bh2u(self.node_id_1)
|
||||
d['node_id_2'] = bh2u(self.node_id_2)
|
||||
d['len'] = bh2u(self.features_len)
|
||||
d['features'] = bh2u(self.features)
|
||||
d['bitcoin_key_1'] = bh2u(self.bitcoin_key_1)
|
||||
d['bitcoin_key_2'] = bh2u(self.bitcoin_key_2)
|
||||
d['policy_node1'] = self.policy_node1
|
||||
d['policy_node2'] = self.policy_node2
|
||||
d['capacity_sat'] = self.capacity_sat
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, d: dict):
|
||||
d2 = {}
|
||||
d2['short_channel_id'] = bfh(d['short_channel_id'])
|
||||
d2['node_id_1'] = bfh(d['node_id_1'])
|
||||
d2['node_id_2'] = bfh(d['node_id_2'])
|
||||
d2['len'] = bfh(d['len'])
|
||||
d2['features'] = bfh(d['features'])
|
||||
d2['bitcoin_key_1'] = bfh(d['bitcoin_key_1'])
|
||||
d2['bitcoin_key_2'] = bfh(d['bitcoin_key_2'])
|
||||
ci = ChannelInfo(d2)
|
||||
ci.capacity_sat = d['capacity_sat']
|
||||
ci.policy_node1 = ChannelInfoDirectedPolicy.from_json(d['policy_node1'])
|
||||
ci.policy_node2 = ChannelInfoDirectedPolicy.from_json(d['policy_node2'])
|
||||
return ci
|
||||
|
||||
def set_capacity(self, capacity):
|
||||
# TODO call this after looking up UTXO for funding txn on chain
|
||||
self.capacity_sat = capacity
|
||||
|
||||
def on_channel_update(self, msg_payload):
|
||||
assert self.channel_id == msg_payload['short_channel_id']
|
||||
flags = int.from_bytes(msg_payload['flags'], 'big')
|
||||
direction = flags & 1
|
||||
new_policy = ChannelInfoDirectedPolicy(msg_payload)
|
||||
if direction == 0:
|
||||
self.policy_node1 = ChannelInfoDirectedPolicy(msg_payload)
|
||||
old_policy = self.policy_node1
|
||||
node_id = self.node_id_1
|
||||
else:
|
||||
self.policy_node2 = ChannelInfoDirectedPolicy(msg_payload)
|
||||
#self.print_error('channel update', binascii.hexlify(self.channel_id).decode("ascii"), flags)
|
||||
old_policy = self.policy_node2
|
||||
node_id = self.node_id_2
|
||||
if old_policy and old_policy.timestamp >= new_policy.timestamp:
|
||||
return # ignore
|
||||
if not verify_sig_for_channel_update(msg_payload, node_id):
|
||||
return # ignore
|
||||
# save new policy
|
||||
if direction == 0:
|
||||
self.policy_node1 = new_policy
|
||||
else:
|
||||
self.policy_node2 = new_policy
|
||||
|
||||
def get_policy_for_node(self, node_id):
|
||||
if node_id == self.node_id_1:
|
||||
|
@ -84,51 +138,121 @@ class ChannelInfo(PrintError):
|
|||
class ChannelInfoDirectedPolicy:
|
||||
|
||||
def __init__(self, channel_update_payload):
|
||||
self.cltv_expiry_delta = channel_update_payload['cltv_expiry_delta']
|
||||
self.htlc_minimum_msat = channel_update_payload['htlc_minimum_msat']
|
||||
self.fee_base_msat = channel_update_payload['fee_base_msat']
|
||||
self.fee_proportional_millionths = channel_update_payload['fee_proportional_millionths']
|
||||
self.cltv_expiry_delta = int.from_bytes(self.cltv_expiry_delta, "big")
|
||||
self.htlc_minimum_msat = int.from_bytes(self.htlc_minimum_msat, "big")
|
||||
self.fee_base_msat = int.from_bytes(self.fee_base_msat, "big")
|
||||
self.fee_proportional_millionths = int.from_bytes(self.fee_proportional_millionths, "big")
|
||||
cltv_expiry_delta = channel_update_payload['cltv_expiry_delta']
|
||||
htlc_minimum_msat = channel_update_payload['htlc_minimum_msat']
|
||||
fee_base_msat = channel_update_payload['fee_base_msat']
|
||||
fee_proportional_millionths = channel_update_payload['fee_proportional_millionths']
|
||||
flags = channel_update_payload['flags']
|
||||
timestamp = channel_update_payload['timestamp']
|
||||
|
||||
self.cltv_expiry_delta = int.from_bytes(cltv_expiry_delta, "big")
|
||||
self.htlc_minimum_msat = int.from_bytes(htlc_minimum_msat, "big")
|
||||
self.fee_base_msat = int.from_bytes(fee_base_msat, "big")
|
||||
self.fee_proportional_millionths = int.from_bytes(fee_proportional_millionths, "big")
|
||||
self.flags = int.from_bytes(flags, "big")
|
||||
self.timestamp = int.from_bytes(timestamp, "big")
|
||||
|
||||
def to_json(self) -> dict:
|
||||
d = {}
|
||||
d['cltv_expiry_delta'] = self.cltv_expiry_delta
|
||||
d['htlc_minimum_msat'] = self.htlc_minimum_msat
|
||||
d['fee_base_msat'] = self.fee_base_msat
|
||||
d['fee_proportional_millionths'] = self.fee_proportional_millionths
|
||||
d['flags'] = self.flags
|
||||
d['timestamp'] = self.timestamp
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, d: dict):
|
||||
if d is None: return None
|
||||
d2 = {}
|
||||
d2['cltv_expiry_delta'] = d['cltv_expiry_delta'].to_bytes(2, "big")
|
||||
d2['htlc_minimum_msat'] = d['htlc_minimum_msat'].to_bytes(8, "big")
|
||||
d2['fee_base_msat'] = d['fee_base_msat'].to_bytes(4, "big")
|
||||
d2['fee_proportional_millionths'] = d['fee_proportional_millionths'].to_bytes(4, "big")
|
||||
d2['flags'] = d['flags'].to_bytes(2, "big")
|
||||
d2['timestamp'] = d['timestamp'].to_bytes(4, "big")
|
||||
return ChannelInfoDirectedPolicy(d2)
|
||||
|
||||
|
||||
class ChannelDB(PrintError):
|
||||
class ChannelDB(JsonDB):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, network):
|
||||
self.network = network
|
||||
|
||||
path = os.path.join(get_headers_dir(network.config), 'channel_db')
|
||||
JsonDB.__init__(self, path)
|
||||
|
||||
self.lock = threading.Lock()
|
||||
self._id_to_channel_info = {}
|
||||
self._channels_for_node = defaultdict(set) # node -> set(short_channel_id)
|
||||
|
||||
self.ca_verifier = LNChanAnnVerifier(network, self)
|
||||
self.network.add_jobs([self.ca_verifier])
|
||||
|
||||
self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
if os.path.exists(self.path):
|
||||
with open(self.path, "r", encoding='utf-8') as f:
|
||||
raw = f.read()
|
||||
self.data = json.loads(raw)
|
||||
channel_infos = self.get('channel_infos', {})
|
||||
for short_channel_id, channel_info_d in channel_infos.items():
|
||||
channel_info = ChannelInfo.from_json(channel_info_d)
|
||||
short_channel_id = bfh(short_channel_id)
|
||||
self.add_verified_channel_info(short_channel_id, channel_info)
|
||||
|
||||
def save_data(self):
|
||||
with self.lock:
|
||||
channel_infos = {}
|
||||
for short_channel_id, channel_info in self._id_to_channel_info.items():
|
||||
channel_infos[bh2u(short_channel_id)] = channel_info
|
||||
self.put('channel_infos', channel_infos)
|
||||
self.write()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._id_to_channel_info)
|
||||
|
||||
def get_channel_info(self, channel_id):
|
||||
def get_channel_info(self, channel_id) -> Optional[ChannelInfo]:
|
||||
return self._id_to_channel_info.get(channel_id, None)
|
||||
|
||||
def get_channels_for_node(self, node_id):
|
||||
"""Returns the set of channels that have node_id as one of the endpoints."""
|
||||
return self._channels_for_node[node_id]
|
||||
|
||||
def add_verified_channel_info(self, short_channel_id: bytes, channel_info: ChannelInfo):
|
||||
with self.lock:
|
||||
self._id_to_channel_info[short_channel_id] = channel_info
|
||||
self._channels_for_node[channel_info.node_id_1].add(short_channel_id)
|
||||
self._channels_for_node[channel_info.node_id_2].add(short_channel_id)
|
||||
|
||||
def on_channel_announcement(self, msg_payload):
|
||||
short_channel_id = msg_payload['short_channel_id']
|
||||
#self.print_error('channel announcement', binascii.hexlify(short_channel_id).decode("ascii"))
|
||||
channel_info = ChannelInfo(msg_payload)
|
||||
if short_channel_id in self._id_to_channel_info:
|
||||
self.print_error("IGNORING CHANNEL ANNOUNCEMENT, WE ALREADY KNOW THIS CHANNEL")
|
||||
return
|
||||
self._id_to_channel_info[short_channel_id] = channel_info
|
||||
self._channels_for_node[channel_info.node_id_1].add(short_channel_id)
|
||||
self._channels_for_node[channel_info.node_id_2].add(short_channel_id)
|
||||
if constants.net.rev_genesis_bytes() != msg_payload['chain_hash']:
|
||||
return
|
||||
channel_info = ChannelInfo(msg_payload)
|
||||
self.ca_verifier.add_new_channel_info(channel_info)
|
||||
|
||||
def on_channel_update(self, msg_payload):
|
||||
short_channel_id = msg_payload['short_channel_id']
|
||||
try:
|
||||
channel_info = self._id_to_channel_info[short_channel_id]
|
||||
except KeyError:
|
||||
if constants.net.rev_genesis_bytes() != msg_payload['chain_hash']:
|
||||
return
|
||||
# try finding channel in verified db
|
||||
channel_info = self._id_to_channel_info.get(short_channel_id, None)
|
||||
if channel_info is None:
|
||||
# try finding channel in pending db
|
||||
channel_info = self.ca_verifier.get_pending_channel_info(short_channel_id)
|
||||
if channel_info is None:
|
||||
# try finding channel in verified db, again
|
||||
# (maybe this is redundant but this should prevent a race..)
|
||||
channel_info = self._id_to_channel_info.get(short_channel_id, None)
|
||||
if channel_info is None:
|
||||
self.print_error("could not find", short_channel_id)
|
||||
else:
|
||||
channel_info.on_channel_update(msg_payload)
|
||||
return
|
||||
channel_info.on_channel_update(msg_payload)
|
||||
|
||||
def remove_channel(self, short_channel_id):
|
||||
try:
|
||||
|
|
|
@ -344,8 +344,11 @@ def sign_and_get_sig_string(tx, local_config, remote_config):
|
|||
sig_64 = sig_string_from_der_sig(sig[:-1])
|
||||
return sig_64
|
||||
|
||||
def funding_output_script(local_config, remote_config):
|
||||
pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)])
|
||||
def funding_output_script(local_config, remote_config) -> str:
|
||||
return funding_output_script_from_keys(local_config.multisig_key.pubkey, remote_config.multisig_key.pubkey)
|
||||
|
||||
def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> str:
|
||||
pubkeys = sorted([bh2u(pubkey1), bh2u(pubkey2)])
|
||||
return transaction.multisig_script(pubkeys, 2)
|
||||
|
||||
def calc_short_channel_id(block_height: int, tx_pos_in_block: int, output_index: int) -> bytes:
|
||||
|
@ -354,6 +357,12 @@ def calc_short_channel_id(block_height: int, tx_pos_in_block: int, output_index:
|
|||
oi = output_index.to_bytes(2, byteorder='big')
|
||||
return bh + tpos + oi
|
||||
|
||||
def invert_short_channel_id(short_channel_id: bytes) -> (int, int, int):
|
||||
bh = int.from_bytes(short_channel_id[:3], byteorder='big')
|
||||
tpos = int.from_bytes(short_channel_id[3:6], byteorder='big')
|
||||
oi = int.from_bytes(short_channel_id[6:8], byteorder='big')
|
||||
return bh, tpos, oi
|
||||
|
||||
def get_obscured_ctn(ctn, local, remote):
|
||||
mask = int.from_bytes(sha256(local + remote)[-6:], 'big')
|
||||
return ctn ^ mask
|
||||
|
|
|
@ -235,7 +235,7 @@ class Network(PrintError):
|
|||
|
||||
# lightning network
|
||||
self.lightning_nodes = {}
|
||||
self.channel_db = lnrouter.ChannelDB()
|
||||
self.channel_db = lnrouter.ChannelDB(self)
|
||||
self.path_finder = lnrouter.LNPathFinder(self.channel_db)
|
||||
self.lnwatcher = lnwatcher.LNWatcher(self)
|
||||
|
||||
|
@ -863,6 +863,7 @@ class Network(PrintError):
|
|||
def stop(self):
|
||||
assert self._loop_thread != threading.current_thread(), 'must not be called from network thread'
|
||||
fut = asyncio.run_coroutine_threadsafe(self._stop(full_shutdown=True), self.asyncio_loop)
|
||||
self.channel_db.save_data()
|
||||
try:
|
||||
fut.result(timeout=2)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError): pass
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import unittest
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from electrum.util import bh2u, bfh
|
||||
from electrum.lnbase import Peer
|
||||
from electrum.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop
|
||||
from electrum import bitcoin, lnrouter
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum import lnchanannverifier
|
||||
|
||||
class Test_LNRouter(unittest.TestCase):
|
||||
from . import TestCaseForTestnet
|
||||
|
||||
class Test_LNRouter(TestCaseForTestnet):
|
||||
|
||||
#@staticmethod
|
||||
#def parse_witness_list(witness_bytes):
|
||||
|
@ -21,12 +27,26 @@ class Test_LNRouter(unittest.TestCase):
|
|||
# assert witness_bytes == b"", witness_bytes
|
||||
# return res
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.electrum_path = tempfile.mkdtemp()
|
||||
cls.config = SimpleConfig({'electrum_path': cls.electrum_path})
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super().tearDownClass()
|
||||
shutil.rmtree(cls.electrum_path)
|
||||
|
||||
def test_find_path_for_payment(self):
|
||||
class fake_network:
|
||||
channel_db = lnrouter.ChannelDB()
|
||||
config = self.config
|
||||
trigger_callback = lambda x: None
|
||||
add_jobs = lambda *args: None
|
||||
fake_network.channel_db = lnrouter.ChannelDB(fake_network())
|
||||
def no_verify_add_new_channel_info(channel_info):
|
||||
fake_network.channel_db.add_verified_channel_info(channel_info.channel_id, channel_info)
|
||||
fake_network.channel_db.ca_verifier.add_new_channel_info = no_verify_add_new_channel_info
|
||||
class fake_ln_worker:
|
||||
path_finder = lnrouter.LNPathFinder(fake_network.channel_db)
|
||||
privkey = bitcoin.sha256('privkeyseed')
|
||||
|
@ -34,27 +54,39 @@ class Test_LNRouter(unittest.TestCase):
|
|||
channels = []
|
||||
invoices = {}
|
||||
channels_for_peer = lambda x: []
|
||||
p = Peer(fake_ln_worker, '', 0, 'a')
|
||||
p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'c', 'short_channel_id': bfh('0000000000000001')})
|
||||
p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000002')})
|
||||
p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'b', 'short_channel_id': bfh('0000000000000003')})
|
||||
p.on_channel_announcement({'node_id_1': b'c', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000004')})
|
||||
p.on_channel_announcement({'node_id_1': b'd', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000005')})
|
||||
p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000006')})
|
||||
p = Peer(fake_ln_worker, '', 0, b'\x02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
|
||||
p.on_channel_announcement({'node_id_1': b'\x02bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'node_id_2': b'\x02cccccccccccccccccccccccccccccccc',
|
||||
'short_channel_id': bfh('0000000000000001'), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_announcement({'node_id_1': b'\x02bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'node_id_2': b'\x02eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
||||
'short_channel_id': bfh('0000000000000002'), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_announcement({'node_id_1': b'\x02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'node_id_2': b'\x02bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'short_channel_id': bfh('0000000000000003'), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_announcement({'node_id_1': b'\x02cccccccccccccccccccccccccccccccc',
|
||||
'node_id_2': b'\x02dddddddddddddddddddddddddddddddd',
|
||||
'short_channel_id': bfh('0000000000000004'), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_announcement({'node_id_1': b'\x02dddddddddddddddddddddddddddddddd',
|
||||
'node_id_2': b'\x02eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
||||
'short_channel_id': bfh('0000000000000005'), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_announcement({'node_id_1': b'\x02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'node_id_2': b'\x02dddddddddddddddddddddddddddddddd',
|
||||
'short_channel_id': bfh('0000000000000006'), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
o = lambda i: i.to_bytes(8, "big")
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999)})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
|
||||
self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'a', b'e', 100000))
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150), 'chain_hash': bfh('43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000')})
|
||||
self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'\x02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', b'\x02eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 100000))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -175,6 +175,8 @@ class MyEncoder(json.JSONEncoder):
|
|||
return obj.isoformat(' ')[:-3]
|
||||
if isinstance(obj, set):
|
||||
return list(obj)
|
||||
if hasattr(obj, 'to_json') and callable(obj.to_json):
|
||||
return obj.to_json()
|
||||
return super(MyEncoder, self).default(obj)
|
||||
|
||||
class PrintError(object):
|
||||
|
|
Loading…
Add table
Reference in a new issue