2018-10-25 18:28:18 +02:00
|
|
|
import asyncio
|
|
|
|
|
import tempfile
|
2018-10-25 21:59:16 +02:00
|
|
|
from decimal import Decimal
|
|
|
|
|
import os
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
from collections import defaultdict
|
2019-05-02 18:09:11 +02:00
|
|
|
import logging
|
2019-12-11 23:07:47 +01:00
|
|
|
import concurrent
|
|
|
|
|
from concurrent import futures
|
2020-03-06 21:54:05 +01:00
|
|
|
import unittest
|
2021-03-17 09:32:23 +01:00
|
|
|
from typing import Iterable, NamedTuple, Tuple, List, Dict
|
2020-03-06 21:54:05 +01:00
|
|
|
|
2021-03-11 20:35:21 +01:00
|
|
|
from aiorpcx import TaskGroup, timeout_after, TaskTimeout
|
2018-10-25 21:59:16 +02:00
|
|
|
|
2021-07-02 18:44:39 +02:00
|
|
|
import electrum
|
|
|
|
|
import electrum.trampoline
|
2020-12-29 17:40:01 +01:00
|
|
|
from electrum import bitcoin
|
2020-02-27 13:41:40 +01:00
|
|
|
from electrum import constants
|
2018-10-25 21:59:16 +02:00
|
|
|
from electrum.network import Network
|
|
|
|
|
from electrum.ecc import ECPrivkey
|
|
|
|
|
from electrum import simple_config, lnutil
|
|
|
|
|
from electrum.lnaddr import lnencode, LnAddr, lndecode
|
|
|
|
|
from electrum.bitcoin import COIN, sha256
|
2020-12-29 17:40:01 +01:00
|
|
|
from electrum.util import bh2u, create_and_start_event_loop, NetworkRetryManager, bfh
|
|
|
|
|
from electrum.lnpeer import Peer, UpfrontShutdownScriptViolation
|
2018-10-25 21:59:16 +02:00
|
|
|
from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey
|
|
|
|
|
from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving
|
2020-03-16 22:07:00 +01:00
|
|
|
from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner
|
2020-04-13 16:02:05 +02:00
|
|
|
from electrum.lnchannel import ChannelState, PeerState, Channel
|
2020-05-06 11:00:58 +02:00
|
|
|
from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent
|
2019-06-18 13:49:31 +02:00
|
|
|
from electrum.channel_db import ChannelDB
|
2019-10-09 19:23:09 +02:00
|
|
|
from electrum.lnworker import LNWallet, NoPathFound
|
2019-02-05 17:56:01 +01:00
|
|
|
from electrum.lnmsg import encode_msg, decode_msg
|
2021-03-19 20:51:38 +01:00
|
|
|
from electrum import lnmsg
|
2020-03-06 21:54:05 +01:00
|
|
|
from electrum.logging import console_stderr_handler, Logger
|
2021-03-08 22:18:06 +01:00
|
|
|
from electrum.lnworker import PaymentInfo, RECEIVED
|
2020-05-06 11:00:58 +02:00
|
|
|
from electrum.lnonion import OnionFailureCode
|
2021-03-09 08:47:30 +01:00
|
|
|
from electrum.lnutil import derive_payment_secret_from_payment_preimage
|
2021-03-02 18:35:07 +01:00
|
|
|
from electrum.lnutil import LOCAL, REMOTE
|
2021-03-08 22:18:06 +01:00
|
|
|
from electrum.invoices import PR_PAID, PR_UNPAID
|
2018-10-25 21:59:16 +02:00
|
|
|
|
2019-02-09 10:29:33 +01:00
|
|
|
from .test_lnchannel import create_test_channels
|
2020-03-04 18:54:20 +01:00
|
|
|
from .test_bitcoin import needs_test_with_all_chacha20_implementations
|
2021-03-12 18:50:10 +01:00
|
|
|
from . import TestCaseForTestnet
|
2018-10-25 18:28:18 +02:00
|
|
|
|
2018-10-25 21:59:16 +02:00
|
|
|
def keypair():
|
|
|
|
|
priv = ECPrivkey.generate_random_key().get_secret_bytes()
|
|
|
|
|
k1 = Keypair(
|
|
|
|
|
pubkey=privkey_to_pubkey(priv),
|
|
|
|
|
privkey=priv)
|
|
|
|
|
return k1
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
|
def noop_lock():
|
|
|
|
|
yield
|
|
|
|
|
|
2018-10-25 18:28:18 +02:00
|
|
|
class MockNetwork:
|
2018-11-02 19:16:42 +01:00
|
|
|
def __init__(self, tx_queue):
|
2018-10-25 21:59:16 +02:00
|
|
|
self.callbacks = defaultdict(list)
|
2018-10-25 18:28:18 +02:00
|
|
|
self.lnwatcher = None
|
2019-03-06 06:17:52 +01:00
|
|
|
self.interface = None
|
2018-10-25 18:28:18 +02:00
|
|
|
user_config = {}
|
2019-02-09 10:29:33 +01:00
|
|
|
user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-")
|
2018-10-25 18:28:18 +02:00
|
|
|
self.config = simple_config.SimpleConfig(user_config, read_user_dir_function=lambda: user_dir)
|
|
|
|
|
self.asyncio_loop = asyncio.get_event_loop()
|
|
|
|
|
self.channel_db = ChannelDB(self)
|
2020-03-10 15:11:16 +01:00
|
|
|
self.channel_db.data_loaded.set()
|
2018-10-25 21:59:16 +02:00
|
|
|
self.path_finder = LNPathFinder(self.channel_db)
|
2018-11-02 19:16:42 +01:00
|
|
|
self.tx_queue = tx_queue
|
2020-04-13 17:04:27 +02:00
|
|
|
self._blockchain = MockBlockchain()
|
2018-10-25 21:59:16 +02:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def callback_lock(self):
|
|
|
|
|
return noop_lock()
|
|
|
|
|
|
|
|
|
|
def get_local_height(self):
|
|
|
|
|
return 0
|
2018-10-25 18:28:18 +02:00
|
|
|
|
2020-04-13 17:04:27 +02:00
|
|
|
def blockchain(self):
|
|
|
|
|
return self._blockchain
|
|
|
|
|
|
2020-03-06 12:40:42 +01:00
|
|
|
async def broadcast_transaction(self, tx):
|
2018-11-02 19:16:42 +01:00
|
|
|
if self.tx_queue:
|
|
|
|
|
await self.tx_queue.put(tx)
|
|
|
|
|
|
2020-03-06 12:40:42 +01:00
|
|
|
async def try_broadcasting(self, tx, name):
|
2020-04-08 13:18:56 +02:00
|
|
|
await self.broadcast_transaction(tx)
|
2020-03-06 12:40:42 +01:00
|
|
|
|
2020-04-13 17:04:27 +02:00
|
|
|
|
|
|
|
|
class MockBlockchain:
|
|
|
|
|
|
|
|
|
|
def height(self):
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
def is_tip_stale(self):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2018-11-07 18:00:28 +01:00
|
|
|
class MockWallet:
|
2020-05-29 11:30:08 +02:00
|
|
|
|
2019-05-08 12:41:57 +02:00
|
|
|
def set_label(self, x, y):
|
|
|
|
|
pass
|
2020-05-29 11:30:08 +02:00
|
|
|
|
2020-02-05 15:13:37 +01:00
|
|
|
def save_db(self):
|
|
|
|
|
pass
|
2020-05-29 11:30:08 +02:00
|
|
|
|
|
|
|
|
def add_transaction(self, tx):
|
|
|
|
|
pass
|
|
|
|
|
|
2020-02-12 19:23:09 +01:00
|
|
|
def is_lightning_backup(self):
|
|
|
|
|
return False
|
2018-11-07 18:00:28 +01:00
|
|
|
|
2020-09-15 15:37:47 +00:00
|
|
|
def is_mine(self, addr):
|
|
|
|
|
return True
|
|
|
|
|
|
2020-05-29 11:30:08 +02:00
|
|
|
|
2020-04-15 17:39:39 +02:00
|
|
|
class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
|
2021-03-11 20:35:21 +01:00
|
|
|
MPP_EXPIRY = 2 # HTLC timestamps are cast to int, so this cannot be 1
|
|
|
|
|
TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 0
|
2021-03-22 17:03:50 +01:00
|
|
|
INITIAL_TRAMPOLINE_FEE_LEVEL = 0
|
2021-03-10 21:00:58 +01:00
|
|
|
|
2021-03-08 22:18:06 +01:00
|
|
|
def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue, name):
|
|
|
|
|
self.name = name
|
2020-03-06 21:54:05 +01:00
|
|
|
Logger.__init__(self)
|
2020-04-15 17:39:39 +02:00
|
|
|
NetworkRetryManager.__init__(self, max_retry_delay_normal=1, init_retry_delay_normal=1)
|
2018-10-25 21:59:16 +02:00
|
|
|
self.node_keypair = local_keypair
|
2018-11-02 19:16:42 +01:00
|
|
|
self.network = MockNetwork(tx_queue)
|
2021-03-11 20:35:21 +01:00
|
|
|
self.taskgroup = TaskGroup()
|
|
|
|
|
self.lnwatcher = None
|
|
|
|
|
self.listen_server = None
|
2021-03-05 13:00:24 +01:00
|
|
|
self._channels = {chan.channel_id: chan for chan in chans}
|
2019-10-09 20:16:11 +02:00
|
|
|
self.payments = {}
|
2019-10-22 18:54:00 +02:00
|
|
|
self.logs = defaultdict(list)
|
2018-11-07 18:00:28 +01:00
|
|
|
self.wallet = MockWallet()
|
2020-03-16 22:07:00 +01:00
|
|
|
self.features = LnFeatures(0)
|
|
|
|
|
self.features |= LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT
|
2020-12-29 17:40:01 +01:00
|
|
|
self.features |= LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT
|
2021-03-02 18:35:07 +01:00
|
|
|
self.features |= LnFeatures.VAR_ONION_OPT
|
|
|
|
|
self.features |= LnFeatures.PAYMENT_SECRET_OPT
|
2021-03-05 13:00:24 +01:00
|
|
|
self.features |= LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT
|
2019-05-23 12:37:24 +02:00
|
|
|
self.pending_payments = defaultdict(asyncio.Future)
|
2020-05-06 10:44:38 +02:00
|
|
|
for chan in chans:
|
|
|
|
|
chan.lnworker = self
|
|
|
|
|
self._peers = {} # bytes -> Peer
|
2020-02-27 20:53:50 +01:00
|
|
|
# used in tests
|
2021-03-18 07:48:30 +01:00
|
|
|
self.enable_htlc_settle = True
|
|
|
|
|
self.enable_htlc_forwarding = True
|
2021-03-11 16:53:55 +01:00
|
|
|
self.received_mpp_htlcs = dict()
|
2021-02-05 17:09:47 +01:00
|
|
|
self.sent_htlcs = defaultdict(asyncio.Queue)
|
2021-03-02 10:23:30 +01:00
|
|
|
self.sent_htlcs_routes = dict()
|
2021-02-27 09:45:19 +01:00
|
|
|
self.sent_buckets = defaultdict(set)
|
2021-03-05 13:00:24 +01:00
|
|
|
self.trampoline_forwarding_failures = {}
|
2021-03-05 17:04:06 +01:00
|
|
|
self.inflight_payments = set()
|
|
|
|
|
self.preimages = {}
|
2021-03-11 19:31:22 +01:00
|
|
|
self.stopping_soon = False
|
2021-11-04 19:16:02 +01:00
|
|
|
self.downstream_htlc_to_upstream_peer_map = {}
|
2018-10-25 21:59:16 +02:00
|
|
|
|
2021-07-02 18:44:39 +02:00
|
|
|
self.logger.info(f"created LNWallet[{name}] with nodeID={local_keypair.pubkey.hex()}")
|
|
|
|
|
|
2019-08-15 13:17:16 +02:00
|
|
|
def get_invoice_status(self, key):
|
|
|
|
|
pass
|
|
|
|
|
|
2018-10-25 21:59:16 +02:00
|
|
|
@property
|
|
|
|
|
def lock(self):
|
|
|
|
|
return noop_lock()
|
|
|
|
|
|
2021-03-05 13:00:24 +01:00
|
|
|
@property
|
|
|
|
|
def channel_db(self):
|
|
|
|
|
return self.network.channel_db if self.network else None
|
|
|
|
|
|
2020-04-30 21:08:26 +02:00
|
|
|
@property
|
|
|
|
|
def channels(self):
|
|
|
|
|
return self._channels
|
|
|
|
|
|
2018-10-25 18:28:18 +02:00
|
|
|
@property
|
|
|
|
|
def peers(self):
|
2020-04-15 21:32:53 +02:00
|
|
|
return self._peers
|
|
|
|
|
|
2019-02-03 15:27:48 +01:00
|
|
|
def get_channel_by_short_id(self, short_channel_id):
|
|
|
|
|
with self.lock:
|
2020-04-30 21:08:26 +02:00
|
|
|
for chan in self._channels.values():
|
2019-02-03 15:27:48 +01:00
|
|
|
if chan.short_channel_id == short_channel_id:
|
|
|
|
|
return chan
|
|
|
|
|
|
2020-04-10 19:50:20 +02:00
|
|
|
def channel_state_changed(self, chan):
|
|
|
|
|
pass
|
|
|
|
|
|
2018-10-25 21:59:16 +02:00
|
|
|
def save_channel(self, chan):
|
2018-11-02 19:16:42 +01:00
|
|
|
print("Ignoring channel save")
|
2018-10-25 21:59:16 +02:00
|
|
|
|
2021-03-08 22:18:06 +01:00
|
|
|
def diagnostic_name(self):
|
|
|
|
|
return self.name
|
|
|
|
|
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
async def stop(self):
|
2021-03-11 20:35:21 +01:00
|
|
|
await LNWallet.stop(self)
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
if self.channel_db:
|
|
|
|
|
self.channel_db.stop()
|
|
|
|
|
await self.channel_db.stopped_event.wait()
|
|
|
|
|
|
2021-05-10 09:21:53 +02:00
|
|
|
async def create_routes_from_invoice(self, amount_msat: int, decoded_invoice: LnAddr, *, full_path=None):
|
|
|
|
|
return [r async for r in self.create_routes_for_payment(
|
|
|
|
|
amount_msat=amount_msat,
|
|
|
|
|
final_total_msat=amount_msat,
|
|
|
|
|
invoice_pubkey=decoded_invoice.pubkey.serialize(),
|
|
|
|
|
min_cltv_expiry=decoded_invoice.get_min_final_cltv_expiry(),
|
|
|
|
|
r_tags=decoded_invoice.get_routing_info('r'),
|
|
|
|
|
invoice_features=decoded_invoice.get_features(),
|
2021-12-17 15:21:21 +01:00
|
|
|
trampoline_fee_levels=defaultdict(int),
|
2021-05-10 09:21:53 +02:00
|
|
|
use_two_trampolines=False,
|
|
|
|
|
payment_hash=decoded_invoice.paymenthash,
|
|
|
|
|
payment_secret=decoded_invoice.payment_secret,
|
|
|
|
|
full_path=full_path)]
|
|
|
|
|
|
2021-02-27 20:26:58 +01:00
|
|
|
get_payments = LNWallet.get_payments
|
2019-10-09 20:16:11 +02:00
|
|
|
get_payment_info = LNWallet.get_payment_info
|
|
|
|
|
save_payment_info = LNWallet.save_payment_info
|
2020-03-10 13:51:08 +01:00
|
|
|
set_invoice_status = LNWallet.set_invoice_status
|
2021-03-09 09:35:43 +01:00
|
|
|
set_request_status = LNWallet.set_request_status
|
2019-10-09 20:16:11 +02:00
|
|
|
set_payment_status = LNWallet.set_payment_status
|
|
|
|
|
get_payment_status = LNWallet.get_payment_status
|
2021-03-11 16:53:55 +01:00
|
|
|
check_received_mpp_htlc = LNWallet.check_received_mpp_htlc
|
2021-01-30 16:10:51 +01:00
|
|
|
htlc_fulfilled = LNWallet.htlc_fulfilled
|
|
|
|
|
htlc_failed = LNWallet.htlc_failed
|
2019-09-20 17:15:49 +02:00
|
|
|
save_preimage = LNWallet.save_preimage
|
2019-04-26 12:48:02 +02:00
|
|
|
get_preimage = LNWallet.get_preimage
|
2021-02-19 09:04:19 +01:00
|
|
|
create_route_for_payment = LNWallet.create_route_for_payment
|
2021-02-07 11:57:20 +01:00
|
|
|
create_routes_for_payment = LNWallet.create_routes_for_payment
|
2019-04-26 12:48:02 +02:00
|
|
|
_check_invoice = staticmethod(LNWallet._check_invoice)
|
2021-01-30 16:10:51 +01:00
|
|
|
pay_to_route = LNWallet.pay_to_route
|
2021-02-07 11:57:20 +01:00
|
|
|
pay_to_node = LNWallet.pay_to_node
|
2021-02-07 12:09:37 +01:00
|
|
|
pay_invoice = LNWallet.pay_invoice
|
2019-04-26 12:48:02 +02:00
|
|
|
force_close_channel = LNWallet.force_close_channel
|
2020-03-06 12:29:39 +01:00
|
|
|
try_force_closing = LNWallet.try_force_closing
|
2018-11-27 00:40:55 +01:00
|
|
|
get_first_timestamp = lambda self: 0
|
2020-04-15 17:39:39 +02:00
|
|
|
on_peer_successfully_established = LNWallet.on_peer_successfully_established
|
2020-04-30 21:13:29 +02:00
|
|
|
get_channel_by_id = LNWallet.get_channel_by_id
|
2020-05-06 10:44:38 +02:00
|
|
|
channels_for_peer = LNWallet.channels_for_peer
|
|
|
|
|
_calc_routing_hints_for_invoice = LNWallet._calc_routing_hints_for_invoice
|
|
|
|
|
handle_error_code_from_failed_htlc = LNWallet.handle_error_code_from_failed_htlc
|
2021-03-05 13:00:24 +01:00
|
|
|
is_trampoline_peer = LNWallet.is_trampoline_peer
|
2021-03-11 20:35:21 +01:00
|
|
|
wait_for_received_pending_htlcs_to_get_removed = LNWallet.wait_for_received_pending_htlcs_to_get_removed
|
|
|
|
|
on_proxy_changed = LNWallet.on_proxy_changed
|
2021-03-17 09:32:23 +01:00
|
|
|
_decode_channel_update_msg = LNWallet._decode_channel_update_msg
|
|
|
|
|
_handle_chanupd_from_failed_htlc = LNWallet._handle_chanupd_from_failed_htlc
|
2021-11-04 19:16:02 +01:00
|
|
|
_on_maybe_forwarded_htlc_resolved = LNWallet._on_maybe_forwarded_htlc_resolved
|
2020-03-04 18:09:43 +01:00
|
|
|
|
2018-10-25 18:28:18 +02:00
|
|
|
|
|
|
|
|
class MockTransport:
|
2019-02-10 19:17:04 +01:00
|
|
|
def __init__(self, name):
|
2018-10-25 18:28:18 +02:00
|
|
|
self.queue = asyncio.Queue()
|
2019-02-10 19:17:04 +01:00
|
|
|
self._name = name
|
2018-10-25 21:59:16 +02:00
|
|
|
|
2019-02-01 20:21:59 +01:00
|
|
|
def name(self):
|
2019-02-10 19:17:04 +01:00
|
|
|
return self._name
|
2019-02-01 20:21:59 +01:00
|
|
|
|
2018-10-25 18:28:18 +02:00
|
|
|
async def read_messages(self):
|
|
|
|
|
while True:
|
|
|
|
|
yield await self.queue.get()
|
|
|
|
|
|
2018-10-25 21:59:16 +02:00
|
|
|
class NoFeaturesTransport(MockTransport):
|
|
|
|
|
"""
|
|
|
|
|
This answers the init message with a init that doesn't signal any features.
|
|
|
|
|
Used for testing that we require DATA_LOSS_PROTECT.
|
|
|
|
|
"""
|
2018-10-25 18:28:18 +02:00
|
|
|
def send_bytes(self, data):
|
|
|
|
|
decoded = decode_msg(data)
|
|
|
|
|
print(decoded)
|
|
|
|
|
if decoded[0] == 'init':
|
2019-02-05 17:56:01 +01:00
|
|
|
self.queue.put_nowait(encode_msg('init', lflen=1, gflen=1, localfeatures=b"\x00", globalfeatures=b"\x00"))
|
2018-10-25 18:28:18 +02:00
|
|
|
|
2018-10-25 21:59:16 +02:00
|
|
|
class PutIntoOthersQueueTransport(MockTransport):
|
2020-04-06 19:06:27 +02:00
|
|
|
def __init__(self, keypair, name):
|
2019-02-10 19:17:04 +01:00
|
|
|
super().__init__(name)
|
2018-10-25 21:59:16 +02:00
|
|
|
self.other_mock_transport = None
|
2020-04-06 19:06:27 +02:00
|
|
|
self.privkey = keypair.privkey
|
2018-10-25 21:59:16 +02:00
|
|
|
|
|
|
|
|
def send_bytes(self, data):
|
|
|
|
|
self.other_mock_transport.queue.put_nowait(data)
|
|
|
|
|
|
2020-04-06 19:06:27 +02:00
|
|
|
def transport_pair(k1, k2, name1, name2):
|
2021-07-02 18:44:39 +02:00
|
|
|
t1 = PutIntoOthersQueueTransport(k1, name1)
|
|
|
|
|
t2 = PutIntoOthersQueueTransport(k2, name2)
|
2018-10-25 21:59:16 +02:00
|
|
|
t1.other_mock_transport = t2
|
|
|
|
|
t2.other_mock_transport = t1
|
|
|
|
|
return t1, t2
|
|
|
|
|
|
2020-05-06 10:44:38 +02:00
|
|
|
|
2021-11-04 18:04:16 +01:00
|
|
|
class PeerInTests(Peer):
|
|
|
|
|
DELAY_INC_MSG_PROCESSING_SLEEP = 0 # disable rate-limiting
|
|
|
|
|
|
|
|
|
|
|
2020-05-06 11:27:50 +02:00
|
|
|
class SquareGraph(NamedTuple):
|
2021-03-02 18:35:07 +01:00
|
|
|
# A
|
|
|
|
|
# high fee / \ low fee
|
|
|
|
|
# B C
|
|
|
|
|
# high fee \ / low fee
|
|
|
|
|
# D
|
2020-05-06 11:00:58 +02:00
|
|
|
w_a: MockLNWallet
|
|
|
|
|
w_b: MockLNWallet
|
|
|
|
|
w_c: MockLNWallet
|
|
|
|
|
w_d: MockLNWallet
|
|
|
|
|
peer_ab: Peer
|
|
|
|
|
peer_ac: Peer
|
|
|
|
|
peer_ba: Peer
|
|
|
|
|
peer_bd: Peer
|
|
|
|
|
peer_ca: Peer
|
|
|
|
|
peer_cd: Peer
|
|
|
|
|
peer_db: Peer
|
|
|
|
|
peer_dc: Peer
|
|
|
|
|
chan_ab: Channel
|
|
|
|
|
chan_ac: Channel
|
|
|
|
|
chan_ba: Channel
|
|
|
|
|
chan_bd: Channel
|
|
|
|
|
chan_ca: Channel
|
|
|
|
|
chan_cd: Channel
|
|
|
|
|
chan_db: Channel
|
|
|
|
|
chan_dc: Channel
|
|
|
|
|
|
|
|
|
|
def all_peers(self) -> Iterable[Peer]:
|
|
|
|
|
return self.peer_ab, self.peer_ac, self.peer_ba, self.peer_bd, self.peer_ca, self.peer_cd, self.peer_db, self.peer_dc
|
|
|
|
|
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
def all_lnworkers(self) -> Iterable[MockLNWallet]:
|
|
|
|
|
return self.w_a, self.w_b, self.w_c, self.w_d
|
|
|
|
|
|
2020-05-06 11:00:58 +02:00
|
|
|
|
2020-05-06 10:44:38 +02:00
|
|
|
class PaymentDone(Exception): pass
|
2021-03-23 17:17:43 +01:00
|
|
|
class SuccessfulTest(Exception): pass
|
2020-05-06 10:44:38 +02:00
|
|
|
|
|
|
|
|
|
2021-03-12 18:50:10 +01:00
|
|
|
class TestPeer(TestCaseForTestnet):
|
2019-03-06 06:17:52 +01:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
super().setUpClass()
|
2019-05-02 18:09:11 +02:00
|
|
|
console_stderr_handler.setLevel(logging.DEBUG)
|
2019-02-10 19:17:04 +01:00
|
|
|
|
2018-10-25 18:28:18 +02:00
|
|
|
def setUp(self):
|
2019-03-06 06:17:52 +01:00
|
|
|
super().setUp()
|
|
|
|
|
self.asyncio_loop, self._stop_loop, self._loop_thread = create_and_start_event_loop()
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
self._lnworkers_created = [] # type: List[MockLNWallet]
|
2018-10-25 21:59:16 +02:00
|
|
|
|
2019-03-06 06:17:52 +01:00
|
|
|
def tearDown(self):
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
async def cleanup_lnworkers():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for lnworker in self._lnworkers_created:
|
|
|
|
|
await group.spawn(lnworker.stop())
|
|
|
|
|
self._lnworkers_created.clear()
|
|
|
|
|
run(cleanup_lnworkers())
|
|
|
|
|
|
2019-03-06 06:17:52 +01:00
|
|
|
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
|
|
|
|
|
self._loop_thread.join(timeout=1)
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
super().tearDown()
|
2019-03-06 06:17:52 +01:00
|
|
|
|
2021-07-02 18:44:39 +02:00
|
|
|
def prepare_peers(self, alice_channel: Channel, bob_channel: Channel):
|
2018-10-25 21:59:16 +02:00
|
|
|
k1, k2 = keypair(), keypair()
|
2020-05-06 10:44:38 +02:00
|
|
|
alice_channel.node_id = k2.pubkey
|
|
|
|
|
bob_channel.node_id = k1.pubkey
|
|
|
|
|
t1, t2 = transport_pair(k1, k2, alice_channel.name, bob_channel.name)
|
2018-11-02 19:16:42 +01:00
|
|
|
q1, q2 = asyncio.Queue(), asyncio.Queue()
|
2021-03-08 22:18:06 +01:00
|
|
|
w1 = MockLNWallet(local_keypair=k1, chans=[alice_channel], tx_queue=q1, name=bob_channel.name)
|
|
|
|
|
w2 = MockLNWallet(local_keypair=k2, chans=[bob_channel], tx_queue=q2, name=alice_channel.name)
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
self._lnworkers_created.extend([w1, w2])
|
2021-11-04 18:04:16 +01:00
|
|
|
p1 = PeerInTests(w1, k2.pubkey, t1)
|
|
|
|
|
p2 = PeerInTests(w2, k1.pubkey, t2)
|
2020-05-06 10:44:38 +02:00
|
|
|
w1._peers[p1.pubkey] = p1
|
|
|
|
|
w2._peers[p2.pubkey] = p2
|
2018-10-25 21:59:16 +02:00
|
|
|
# mark_open won't work if state is already OPEN.
|
2019-10-29 08:02:14 +01:00
|
|
|
# so set it to FUNDED
|
2020-04-13 16:02:05 +02:00
|
|
|
alice_channel._state = ChannelState.FUNDED
|
|
|
|
|
bob_channel._state = ChannelState.FUNDED
|
2018-10-25 21:59:16 +02:00
|
|
|
# this populates the channel graph:
|
2020-02-12 10:32:55 +01:00
|
|
|
p1.mark_open(alice_channel)
|
|
|
|
|
p2.mark_open(bob_channel)
|
2018-11-02 19:16:42 +01:00
|
|
|
return p1, p2, w1, w2, q1, q2
|
|
|
|
|
|
2021-03-17 09:32:23 +01:00
|
|
|
def prepare_chans_and_peers_in_square(self, funds_distribution: Dict[str, Tuple[int, int]]=None) -> SquareGraph:
|
|
|
|
|
if not funds_distribution:
|
|
|
|
|
funds_distribution = {}
|
2020-05-06 11:00:58 +02:00
|
|
|
key_a, key_b, key_c, key_d = [keypair() for i in range(4)]
|
2021-03-17 09:32:23 +01:00
|
|
|
local_balance, remote_balance = funds_distribution.get('ab') or (None, None)
|
|
|
|
|
chan_ab, chan_ba = create_test_channels(
|
|
|
|
|
alice_name="alice", bob_name="bob",
|
|
|
|
|
alice_pubkey=key_a.pubkey, bob_pubkey=key_b.pubkey,
|
|
|
|
|
local_msat=local_balance,
|
|
|
|
|
remote_msat=remote_balance,
|
|
|
|
|
)
|
|
|
|
|
local_balance, remote_balance = funds_distribution.get('ac') or (None, None)
|
|
|
|
|
chan_ac, chan_ca = create_test_channels(
|
|
|
|
|
alice_name="alice", bob_name="carol",
|
|
|
|
|
alice_pubkey=key_a.pubkey, bob_pubkey=key_c.pubkey,
|
|
|
|
|
local_msat=local_balance,
|
|
|
|
|
remote_msat=remote_balance,
|
|
|
|
|
)
|
|
|
|
|
local_balance, remote_balance = funds_distribution.get('bd') or (None, None)
|
|
|
|
|
chan_bd, chan_db = create_test_channels(
|
|
|
|
|
alice_name="bob", bob_name="dave",
|
|
|
|
|
alice_pubkey=key_b.pubkey, bob_pubkey=key_d.pubkey,
|
|
|
|
|
local_msat=local_balance,
|
|
|
|
|
remote_msat=remote_balance,
|
|
|
|
|
)
|
|
|
|
|
local_balance, remote_balance = funds_distribution.get('cd') or (None, None)
|
|
|
|
|
chan_cd, chan_dc = create_test_channels(
|
|
|
|
|
alice_name="carol", bob_name="dave",
|
|
|
|
|
alice_pubkey=key_c.pubkey, bob_pubkey=key_d.pubkey,
|
|
|
|
|
local_msat=local_balance,
|
|
|
|
|
remote_msat=remote_balance,
|
|
|
|
|
)
|
2020-05-06 11:00:58 +02:00
|
|
|
trans_ab, trans_ba = transport_pair(key_a, key_b, chan_ab.name, chan_ba.name)
|
|
|
|
|
trans_ac, trans_ca = transport_pair(key_a, key_c, chan_ac.name, chan_ca.name)
|
|
|
|
|
trans_bd, trans_db = transport_pair(key_b, key_d, chan_bd.name, chan_db.name)
|
|
|
|
|
trans_cd, trans_dc = transport_pair(key_c, key_d, chan_cd.name, chan_dc.name)
|
|
|
|
|
txq_a, txq_b, txq_c, txq_d = [asyncio.Queue() for i in range(4)]
|
2021-03-08 22:18:06 +01:00
|
|
|
w_a = MockLNWallet(local_keypair=key_a, chans=[chan_ab, chan_ac], tx_queue=txq_a, name="alice")
|
|
|
|
|
w_b = MockLNWallet(local_keypair=key_b, chans=[chan_ba, chan_bd], tx_queue=txq_b, name="bob")
|
|
|
|
|
w_c = MockLNWallet(local_keypair=key_c, chans=[chan_ca, chan_cd], tx_queue=txq_c, name="carol")
|
|
|
|
|
w_d = MockLNWallet(local_keypair=key_d, chans=[chan_db, chan_dc], tx_queue=txq_d, name="dave")
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
self._lnworkers_created.extend([w_a, w_b, w_c, w_d])
|
2021-11-04 18:04:16 +01:00
|
|
|
peer_ab = PeerInTests(w_a, key_b.pubkey, trans_ab)
|
|
|
|
|
peer_ac = PeerInTests(w_a, key_c.pubkey, trans_ac)
|
|
|
|
|
peer_ba = PeerInTests(w_b, key_a.pubkey, trans_ba)
|
|
|
|
|
peer_bd = PeerInTests(w_b, key_d.pubkey, trans_bd)
|
|
|
|
|
peer_ca = PeerInTests(w_c, key_a.pubkey, trans_ca)
|
|
|
|
|
peer_cd = PeerInTests(w_c, key_d.pubkey, trans_cd)
|
|
|
|
|
peer_db = PeerInTests(w_d, key_b.pubkey, trans_db)
|
|
|
|
|
peer_dc = PeerInTests(w_d, key_c.pubkey, trans_dc)
|
2020-05-06 11:00:58 +02:00
|
|
|
w_a._peers[peer_ab.pubkey] = peer_ab
|
|
|
|
|
w_a._peers[peer_ac.pubkey] = peer_ac
|
|
|
|
|
w_b._peers[peer_ba.pubkey] = peer_ba
|
|
|
|
|
w_b._peers[peer_bd.pubkey] = peer_bd
|
|
|
|
|
w_c._peers[peer_ca.pubkey] = peer_ca
|
|
|
|
|
w_c._peers[peer_cd.pubkey] = peer_cd
|
|
|
|
|
w_d._peers[peer_db.pubkey] = peer_db
|
|
|
|
|
w_d._peers[peer_dc.pubkey] = peer_dc
|
|
|
|
|
|
|
|
|
|
w_b.network.config.set_key('lightning_forward_payments', True)
|
|
|
|
|
w_c.network.config.set_key('lightning_forward_payments', True)
|
2021-07-02 18:44:39 +02:00
|
|
|
w_b.network.config.set_key('lightning_forward_trampoline_payments', True)
|
|
|
|
|
w_c.network.config.set_key('lightning_forward_trampoline_payments', True)
|
2020-05-06 11:00:58 +02:00
|
|
|
|
2021-03-02 18:35:07 +01:00
|
|
|
# forwarding fees, etc
|
|
|
|
|
chan_ab.forwarding_fee_proportional_millionths *= 500
|
|
|
|
|
chan_ab.forwarding_fee_base_msat *= 500
|
|
|
|
|
chan_ba.forwarding_fee_proportional_millionths *= 500
|
|
|
|
|
chan_ba.forwarding_fee_base_msat *= 500
|
|
|
|
|
chan_bd.forwarding_fee_proportional_millionths *= 500
|
|
|
|
|
chan_bd.forwarding_fee_base_msat *= 500
|
|
|
|
|
chan_db.forwarding_fee_proportional_millionths *= 500
|
|
|
|
|
chan_db.forwarding_fee_base_msat *= 500
|
|
|
|
|
|
2020-05-06 11:00:58 +02:00
|
|
|
# mark_open won't work if state is already OPEN.
|
|
|
|
|
# so set it to FUNDED
|
|
|
|
|
for chan in [chan_ab, chan_ac, chan_ba, chan_bd, chan_ca, chan_cd, chan_db, chan_dc]:
|
|
|
|
|
chan._state = ChannelState.FUNDED
|
|
|
|
|
# this populates the channel graph:
|
|
|
|
|
peer_ab.mark_open(chan_ab)
|
|
|
|
|
peer_ac.mark_open(chan_ac)
|
|
|
|
|
peer_ba.mark_open(chan_ba)
|
|
|
|
|
peer_bd.mark_open(chan_bd)
|
|
|
|
|
peer_ca.mark_open(chan_ca)
|
|
|
|
|
peer_cd.mark_open(chan_cd)
|
|
|
|
|
peer_db.mark_open(chan_db)
|
|
|
|
|
peer_dc.mark_open(chan_dc)
|
2021-07-02 18:44:39 +02:00
|
|
|
graph = SquareGraph(
|
2020-05-06 11:00:58 +02:00
|
|
|
w_a=w_a,
|
|
|
|
|
w_b=w_b,
|
|
|
|
|
w_c=w_c,
|
|
|
|
|
w_d=w_d,
|
|
|
|
|
peer_ab=peer_ab,
|
|
|
|
|
peer_ac=peer_ac,
|
|
|
|
|
peer_ba=peer_ba,
|
|
|
|
|
peer_bd=peer_bd,
|
|
|
|
|
peer_ca=peer_ca,
|
|
|
|
|
peer_cd=peer_cd,
|
|
|
|
|
peer_db=peer_db,
|
|
|
|
|
peer_dc=peer_dc,
|
|
|
|
|
chan_ab=chan_ab,
|
|
|
|
|
chan_ac=chan_ac,
|
|
|
|
|
chan_ba=chan_ba,
|
|
|
|
|
chan_bd=chan_bd,
|
|
|
|
|
chan_ca=chan_ca,
|
|
|
|
|
chan_cd=chan_cd,
|
|
|
|
|
chan_db=chan_db,
|
|
|
|
|
chan_dc=chan_dc,
|
|
|
|
|
)
|
2021-07-02 18:44:39 +02:00
|
|
|
return graph
|
2020-05-06 11:00:58 +02:00
|
|
|
|
2018-11-02 19:16:42 +01:00
|
|
|
@staticmethod
|
2020-05-06 10:44:38 +02:00
|
|
|
async def prepare_invoice(
|
|
|
|
|
w2: MockLNWallet, # receiver
|
2020-03-06 21:54:05 +01:00
|
|
|
*,
|
2021-02-01 14:17:04 +01:00
|
|
|
amount_msat=100_000_000,
|
2020-05-06 10:44:38 +02:00
|
|
|
include_routing_hints=False,
|
2021-03-08 21:46:56 +01:00
|
|
|
) -> Tuple[LnAddr, str]:
|
2021-02-01 14:17:04 +01:00
|
|
|
amount_btc = amount_msat/Decimal(COIN*1000)
|
2018-10-25 21:59:16 +02:00
|
|
|
payment_preimage = os.urandom(32)
|
|
|
|
|
RHASH = sha256(payment_preimage)
|
2021-02-01 14:17:04 +01:00
|
|
|
info = PaymentInfo(RHASH, amount_msat, RECEIVED, PR_UNPAID)
|
2019-09-20 17:15:49 +02:00
|
|
|
w2.save_preimage(RHASH, payment_preimage)
|
2019-10-09 20:16:11 +02:00
|
|
|
w2.save_payment_info(info)
|
2020-05-06 10:44:38 +02:00
|
|
|
if include_routing_hints:
|
2021-02-01 14:17:04 +01:00
|
|
|
routing_hints = await w2._calc_routing_hints_for_invoice(amount_msat)
|
2020-05-06 10:44:38 +02:00
|
|
|
else:
|
|
|
|
|
routing_hints = []
|
2021-03-05 13:00:24 +01:00
|
|
|
trampoline_hints = []
|
|
|
|
|
for r in routing_hints:
|
|
|
|
|
node_id, short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta = r[1][0]
|
|
|
|
|
if len(r[1])== 1 and w2.is_trampoline_peer(node_id):
|
|
|
|
|
trampoline_hints.append(('t', (node_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta)))
|
2021-03-02 18:35:07 +01:00
|
|
|
invoice_features = w2.features.for_invoice()
|
|
|
|
|
if invoice_features.supports(LnFeatures.PAYMENT_SECRET_OPT):
|
|
|
|
|
payment_secret = derive_payment_secret_from_payment_preimage(payment_preimage)
|
|
|
|
|
else:
|
|
|
|
|
payment_secret = None
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr1 = LnAddr(
|
2020-03-24 20:07:00 +01:00
|
|
|
paymenthash=RHASH,
|
|
|
|
|
amount=amount_btc,
|
2018-10-25 21:59:16 +02:00
|
|
|
tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
|
2021-03-02 18:35:07 +01:00
|
|
|
('d', 'coffee'),
|
|
|
|
|
('9', invoice_features),
|
2021-03-05 13:00:24 +01:00
|
|
|
] + routing_hints + trampoline_hints,
|
2021-03-02 18:35:07 +01:00
|
|
|
payment_secret=payment_secret,
|
|
|
|
|
)
|
2021-03-08 21:46:56 +01:00
|
|
|
invoice = lnencode(lnaddr1, w2.node_keypair.privkey)
|
|
|
|
|
lnaddr2 = lndecode(invoice) # unlike lnaddr1, this now has a pubkey set
|
|
|
|
|
return lnaddr2, invoice
|
2018-11-02 19:16:42 +01:00
|
|
|
|
2020-02-11 19:53:21 +01:00
|
|
|
def test_reestablish(self):
|
2020-02-12 10:32:55 +01:00
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
2020-02-26 20:35:46 +01:00
|
|
|
for chan in (alice_channel, bob_channel):
|
2020-04-13 16:02:05 +02:00
|
|
|
chan.peer_state = PeerState.DISCONNECTED
|
2020-02-11 19:53:21 +01:00
|
|
|
async def reestablish():
|
|
|
|
|
await asyncio.gather(
|
2020-02-12 10:32:55 +01:00
|
|
|
p1.reestablish_channel(alice_channel),
|
|
|
|
|
p2.reestablish_channel(bob_channel))
|
2020-04-13 16:02:05 +02:00
|
|
|
self.assertEqual(alice_channel.peer_state, PeerState.GOOD)
|
|
|
|
|
self.assertEqual(bob_channel.peer_state, PeerState.GOOD)
|
2020-02-11 19:53:21 +01:00
|
|
|
gath.cancel()
|
2020-03-02 15:41:50 +01:00
|
|
|
gath = asyncio.gather(reestablish(), p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p1.htlc_switch())
|
2020-02-11 19:53:21 +01:00
|
|
|
async def f():
|
|
|
|
|
await gath
|
|
|
|
|
with self.assertRaises(concurrent.futures.CancelledError):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2020-03-04 18:54:20 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
2020-02-12 10:22:22 +01:00
|
|
|
def test_reestablish_with_old_state(self):
|
2020-05-06 10:44:38 +02:00
|
|
|
random_seed = os.urandom(32)
|
|
|
|
|
alice_channel, bob_channel = create_test_channels(random_seed=random_seed)
|
|
|
|
|
alice_channel_0, bob_channel_0 = create_test_channels(random_seed=random_seed) # these are identical
|
2020-02-12 10:32:55 +01:00
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = run(self.prepare_invoice(w2))
|
fix test: test_reestablish_with_old_state
Messages sent as part of the payment were getting interleaved with the channel_reestablish.
It does not actually make sense to do a payment and then reestablish the channel in the same transport -- the channel is supposed to already have been reestablished to do a payment in the first place.
So, after payment, strip down the transport, and set up a new transport before reestablishing.
Traceback (most recent call last):
File "...\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
yield
File "...\Python\Python38\lib\unittest\case.py", line 676, in run
self._callTestMethod(testMethod)
File "...\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
method()
File "...\electrum\electrum\tests\test_lnpeer.py", line 262, in test_reestablish_with_old_state
run(f())
File "...\electrum\electrum\tests\test_lnpeer.py", line 302, in run
return asyncio.run_coroutine_threadsafe(coro, loop=asyncio.get_event_loop()).result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 439, in result
return self.__get_result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 388, in __get_result
raise self._exception
File "...\electrum\electrum\tests\test_lnpeer.py", line 260, in f
await gath
File "...\electrum\electrum\lnpeer.py", line 439, in _message_loop
self.process_message(msg)
File "...\electrum\electrum\lnpeer.py", line 159, in process_message
execution_result = f(payload)
File "...\electrum\electrum\lnpeer.py", line 1308, in on_revoke_and_ack
chan.receive_revocation(rev)
File "...\electrum\electrum\lnchannel.py", line 556, in receive_revocation
raise Exception('revoked secret not for current point')
Exception: revoked secret not for current point
2020-02-24 21:09:34 +01:00
|
|
|
async def pay():
|
2021-02-07 12:09:37 +01:00
|
|
|
result, log = await w1.pay_invoice(pay_req)
|
2020-02-12 10:22:22 +01:00
|
|
|
self.assertEqual(result, True)
|
fix test: test_reestablish_with_old_state
Messages sent as part of the payment were getting interleaved with the channel_reestablish.
It does not actually make sense to do a payment and then reestablish the channel in the same transport -- the channel is supposed to already have been reestablished to do a payment in the first place.
So, after payment, strip down the transport, and set up a new transport before reestablishing.
Traceback (most recent call last):
File "...\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
yield
File "...\Python\Python38\lib\unittest\case.py", line 676, in run
self._callTestMethod(testMethod)
File "...\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
method()
File "...\electrum\electrum\tests\test_lnpeer.py", line 262, in test_reestablish_with_old_state
run(f())
File "...\electrum\electrum\tests\test_lnpeer.py", line 302, in run
return asyncio.run_coroutine_threadsafe(coro, loop=asyncio.get_event_loop()).result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 439, in result
return self.__get_result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 388, in __get_result
raise self._exception
File "...\electrum\electrum\tests\test_lnpeer.py", line 260, in f
await gath
File "...\electrum\electrum\lnpeer.py", line 439, in _message_loop
self.process_message(msg)
File "...\electrum\electrum\lnpeer.py", line 159, in process_message
execution_result = f(payload)
File "...\electrum\electrum\lnpeer.py", line 1308, in on_revoke_and_ack
chan.receive_revocation(rev)
File "...\electrum\electrum\lnchannel.py", line 556, in receive_revocation
raise Exception('revoked secret not for current point')
Exception: revoked secret not for current point
2020-02-24 21:09:34 +01:00
|
|
|
gath.cancel()
|
2020-03-02 15:41:50 +01:00
|
|
|
gath = asyncio.gather(pay(), p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
|
fix test: test_reestablish_with_old_state
Messages sent as part of the payment were getting interleaved with the channel_reestablish.
It does not actually make sense to do a payment and then reestablish the channel in the same transport -- the channel is supposed to already have been reestablished to do a payment in the first place.
So, after payment, strip down the transport, and set up a new transport before reestablishing.
Traceback (most recent call last):
File "...\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
yield
File "...\Python\Python38\lib\unittest\case.py", line 676, in run
self._callTestMethod(testMethod)
File "...\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
method()
File "...\electrum\electrum\tests\test_lnpeer.py", line 262, in test_reestablish_with_old_state
run(f())
File "...\electrum\electrum\tests\test_lnpeer.py", line 302, in run
return asyncio.run_coroutine_threadsafe(coro, loop=asyncio.get_event_loop()).result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 439, in result
return self.__get_result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 388, in __get_result
raise self._exception
File "...\electrum\electrum\tests\test_lnpeer.py", line 260, in f
await gath
File "...\electrum\electrum\lnpeer.py", line 439, in _message_loop
self.process_message(msg)
File "...\electrum\electrum\lnpeer.py", line 159, in process_message
execution_result = f(payload)
File "...\electrum\electrum\lnpeer.py", line 1308, in on_revoke_and_ack
chan.receive_revocation(rev)
File "...\electrum\electrum\lnchannel.py", line 556, in receive_revocation
raise Exception('revoked secret not for current point')
Exception: revoked secret not for current point
2020-02-24 21:09:34 +01:00
|
|
|
async def f():
|
|
|
|
|
await gath
|
|
|
|
|
with self.assertRaises(concurrent.futures.CancelledError):
|
|
|
|
|
run(f())
|
|
|
|
|
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel_0, bob_channel)
|
2020-02-26 20:35:46 +01:00
|
|
|
for chan in (alice_channel_0, bob_channel):
|
2020-04-13 16:02:05 +02:00
|
|
|
chan.peer_state = PeerState.DISCONNECTED
|
fix test: test_reestablish_with_old_state
Messages sent as part of the payment were getting interleaved with the channel_reestablish.
It does not actually make sense to do a payment and then reestablish the channel in the same transport -- the channel is supposed to already have been reestablished to do a payment in the first place.
So, after payment, strip down the transport, and set up a new transport before reestablishing.
Traceback (most recent call last):
File "...\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
yield
File "...\Python\Python38\lib\unittest\case.py", line 676, in run
self._callTestMethod(testMethod)
File "...\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
method()
File "...\electrum\electrum\tests\test_lnpeer.py", line 262, in test_reestablish_with_old_state
run(f())
File "...\electrum\electrum\tests\test_lnpeer.py", line 302, in run
return asyncio.run_coroutine_threadsafe(coro, loop=asyncio.get_event_loop()).result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 439, in result
return self.__get_result()
File "...\Python\Python38\lib\concurrent\futures\_base.py", line 388, in __get_result
raise self._exception
File "...\electrum\electrum\tests\test_lnpeer.py", line 260, in f
await gath
File "...\electrum\electrum\lnpeer.py", line 439, in _message_loop
self.process_message(msg)
File "...\electrum\electrum\lnpeer.py", line 159, in process_message
execution_result = f(payload)
File "...\electrum\electrum\lnpeer.py", line 1308, in on_revoke_and_ack
chan.receive_revocation(rev)
File "...\electrum\electrum\lnchannel.py", line 556, in receive_revocation
raise Exception('revoked secret not for current point')
Exception: revoked secret not for current point
2020-02-24 21:09:34 +01:00
|
|
|
async def reestablish():
|
2020-02-12 10:22:22 +01:00
|
|
|
await asyncio.gather(
|
2020-02-12 10:32:55 +01:00
|
|
|
p1.reestablish_channel(alice_channel_0),
|
|
|
|
|
p2.reestablish_channel(bob_channel))
|
2020-04-13 16:02:05 +02:00
|
|
|
self.assertEqual(alice_channel_0.peer_state, PeerState.BAD)
|
|
|
|
|
self.assertEqual(bob_channel._state, ChannelState.FORCE_CLOSING)
|
2020-02-12 10:22:22 +01:00
|
|
|
# wait so that pending messages are processed
|
|
|
|
|
#await asyncio.sleep(1)
|
|
|
|
|
gath.cancel()
|
2020-03-02 15:41:50 +01:00
|
|
|
gath = asyncio.gather(reestablish(), p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
|
2020-02-12 10:22:22 +01:00
|
|
|
async def f():
|
|
|
|
|
await gath
|
|
|
|
|
with self.assertRaises(concurrent.futures.CancelledError):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2020-03-04 18:54:20 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
2018-11-02 19:16:42 +01:00
|
|
|
def test_payment(self):
|
2021-03-08 22:18:06 +01:00
|
|
|
"""Alice pays Bob a single HTLC via direct channel."""
|
2020-02-12 10:32:55 +01:00
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
2021-03-08 22:18:06 +01:00
|
|
|
async def pay(lnaddr, pay_req):
|
|
|
|
|
self.assertEqual(PR_UNPAID, w2.get_payment_status(lnaddr.paymenthash))
|
2021-02-07 12:09:37 +01:00
|
|
|
result, log = await w1.pay_invoice(pay_req)
|
2020-02-17 20:38:41 +01:00
|
|
|
self.assertTrue(result)
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_PAID, w2.get_payment_status(lnaddr.paymenthash))
|
2020-05-06 10:44:38 +02:00
|
|
|
raise PaymentDone()
|
2019-03-06 06:17:52 +01:00
|
|
|
async def f():
|
2020-05-06 10:44:38 +02:00
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
await group.spawn(p1._message_loop())
|
|
|
|
|
await group.spawn(p1.htlc_switch())
|
|
|
|
|
await group.spawn(p2._message_loop())
|
|
|
|
|
await group.spawn(p2.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.01)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = await self.prepare_invoice(w2)
|
2021-03-08 22:18:06 +01:00
|
|
|
invoice_features = lnaddr.get_features()
|
|
|
|
|
self.assertFalse(invoice_features.supports(LnFeatures.BASIC_MPP_OPT))
|
|
|
|
|
await group.spawn(pay(lnaddr, pay_req))
|
2020-05-06 10:44:38 +02:00
|
|
|
with self.assertRaises(PaymentDone):
|
2019-03-06 06:17:52 +01:00
|
|
|
run(f())
|
2018-11-02 19:16:42 +01:00
|
|
|
|
2021-01-28 20:00:48 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_payment_race(self):
|
|
|
|
|
"""Alice and Bob pay each other simultaneously.
|
|
|
|
|
They both send 'update_add_htlc' and receive each other's update
|
|
|
|
|
before sending 'commitment_signed'. Neither party should fulfill
|
|
|
|
|
the respective HTLCs until those are irrevocably committed to.
|
|
|
|
|
"""
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
async def pay():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
|
|
|
|
# prep
|
|
|
|
|
_maybe_send_commitment1 = p1.maybe_send_commitment
|
|
|
|
|
_maybe_send_commitment2 = p2.maybe_send_commitment
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr2, pay_req2 = await self.prepare_invoice(w2)
|
|
|
|
|
lnaddr1, pay_req1 = await self.prepare_invoice(w1)
|
2021-02-28 13:58:31 +01:00
|
|
|
# create the htlc queues now (side-effecting defaultdict)
|
|
|
|
|
q1 = w1.sent_htlcs[lnaddr2.paymenthash]
|
|
|
|
|
q2 = w2.sent_htlcs[lnaddr1.paymenthash]
|
2021-01-28 20:00:48 +01:00
|
|
|
# alice sends htlc BUT NOT COMMITMENT_SIGNED
|
|
|
|
|
p1.maybe_send_commitment = lambda x: None
|
2021-05-10 09:21:53 +02:00
|
|
|
route1 = (await w1.create_routes_from_invoice(lnaddr2.get_amount_msat(), decoded_invoice=lnaddr2))[0][0]
|
2021-03-06 10:59:29 +01:00
|
|
|
amount_msat = lnaddr2.get_amount_msat()
|
2021-03-02 10:23:30 +01:00
|
|
|
await w1.pay_to_route(
|
2021-01-30 16:10:51 +01:00
|
|
|
route=route1,
|
2021-03-06 10:59:29 +01:00
|
|
|
amount_msat=amount_msat,
|
|
|
|
|
total_msat=amount_msat,
|
|
|
|
|
amount_receiver_msat=amount_msat,
|
2021-01-28 20:00:48 +01:00
|
|
|
payment_hash=lnaddr2.paymenthash,
|
2021-03-02 10:23:30 +01:00
|
|
|
min_cltv_expiry=lnaddr2.get_min_final_cltv_expiry(),
|
2021-01-28 20:00:48 +01:00
|
|
|
payment_secret=lnaddr2.payment_secret,
|
|
|
|
|
)
|
|
|
|
|
p1.maybe_send_commitment = _maybe_send_commitment1
|
|
|
|
|
# bob sends htlc BUT NOT COMMITMENT_SIGNED
|
|
|
|
|
p2.maybe_send_commitment = lambda x: None
|
2021-05-10 09:21:53 +02:00
|
|
|
route2 = (await w2.create_routes_from_invoice(lnaddr1.get_amount_msat(), decoded_invoice=lnaddr1))[0][0]
|
2021-03-06 10:59:29 +01:00
|
|
|
amount_msat = lnaddr1.get_amount_msat()
|
2021-03-02 10:23:30 +01:00
|
|
|
await w2.pay_to_route(
|
2021-01-30 16:10:51 +01:00
|
|
|
route=route2,
|
2021-03-06 10:59:29 +01:00
|
|
|
amount_msat=amount_msat,
|
|
|
|
|
total_msat=amount_msat,
|
|
|
|
|
amount_receiver_msat=amount_msat,
|
2021-01-28 20:00:48 +01:00
|
|
|
payment_hash=lnaddr1.paymenthash,
|
2021-03-02 10:23:30 +01:00
|
|
|
min_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(),
|
2021-01-28 20:00:48 +01:00
|
|
|
payment_secret=lnaddr1.payment_secret,
|
|
|
|
|
)
|
|
|
|
|
p2.maybe_send_commitment = _maybe_send_commitment2
|
|
|
|
|
# sleep a bit so that they both receive msgs sent so far
|
2021-02-28 09:43:46 +01:00
|
|
|
await asyncio.sleep(0.2)
|
2021-01-28 20:00:48 +01:00
|
|
|
# now they both send COMMITMENT_SIGNED
|
|
|
|
|
p1.maybe_send_commitment(alice_channel)
|
|
|
|
|
p2.maybe_send_commitment(bob_channel)
|
|
|
|
|
|
2021-02-28 13:58:31 +01:00
|
|
|
htlc_log1 = await q1.get()
|
2021-01-30 16:10:51 +01:00
|
|
|
assert htlc_log1.success
|
2021-02-28 13:58:31 +01:00
|
|
|
htlc_log2 = await q2.get()
|
2021-01-30 16:10:51 +01:00
|
|
|
assert htlc_log2.success
|
2021-01-28 20:00:48 +01:00
|
|
|
raise PaymentDone()
|
|
|
|
|
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
await group.spawn(p1._message_loop())
|
|
|
|
|
await group.spawn(p1.htlc_switch())
|
|
|
|
|
await group.spawn(p2._message_loop())
|
|
|
|
|
await group.spawn(p2.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.01)
|
|
|
|
|
await group.spawn(pay())
|
|
|
|
|
with self.assertRaises(PaymentDone):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2020-03-07 05:05:05 +01:00
|
|
|
#@unittest.skip("too expensive")
|
2020-03-06 21:54:05 +01:00
|
|
|
#@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_payments_stresstest(self):
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
alice_init_balance_msat = alice_channel.balance(HTLCOwner.LOCAL)
|
|
|
|
|
bob_init_balance_msat = bob_channel.balance(HTLCOwner.LOCAL)
|
2020-03-07 05:05:05 +01:00
|
|
|
num_payments = 50
|
2021-02-01 14:17:04 +01:00
|
|
|
payment_value_msat = 10_000_000 # make it large enough so that there are actually HTLCs on the ctx
|
2020-03-06 21:54:05 +01:00
|
|
|
max_htlcs_in_flight = asyncio.Semaphore(5)
|
|
|
|
|
async def single_payment(pay_req):
|
|
|
|
|
async with max_htlcs_in_flight:
|
2021-02-07 12:09:37 +01:00
|
|
|
await w1.pay_invoice(pay_req)
|
2020-03-06 21:54:05 +01:00
|
|
|
async def many_payments():
|
|
|
|
|
async with TaskGroup() as group:
|
2021-02-01 14:17:04 +01:00
|
|
|
pay_reqs_tasks = [await group.spawn(self.prepare_invoice(w2, amount_msat=payment_value_msat))
|
2020-05-06 10:44:38 +02:00
|
|
|
for i in range(num_payments)]
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for pay_req_task in pay_reqs_tasks:
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = pay_req_task.result()
|
2020-03-06 21:54:05 +01:00
|
|
|
await group.spawn(single_payment(pay_req))
|
|
|
|
|
gath.cancel()
|
|
|
|
|
gath = asyncio.gather(many_payments(), p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
|
|
|
|
|
async def f():
|
|
|
|
|
await gath
|
|
|
|
|
with self.assertRaises(concurrent.futures.CancelledError):
|
|
|
|
|
run(f())
|
2021-02-01 14:17:04 +01:00
|
|
|
self.assertEqual(alice_init_balance_msat - num_payments * payment_value_msat, alice_channel.balance(HTLCOwner.LOCAL))
|
|
|
|
|
self.assertEqual(alice_init_balance_msat - num_payments * payment_value_msat, bob_channel.balance(HTLCOwner.REMOTE))
|
|
|
|
|
self.assertEqual(bob_init_balance_msat + num_payments * payment_value_msat, bob_channel.balance(HTLCOwner.LOCAL))
|
|
|
|
|
self.assertEqual(bob_init_balance_msat + num_payments * payment_value_msat, alice_channel.balance(HTLCOwner.REMOTE))
|
2020-03-06 21:54:05 +01:00
|
|
|
|
2020-05-06 11:00:58 +02:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_payment_multihop(self):
|
2020-05-06 11:27:50 +02:00
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
2020-05-06 11:00:58 +02:00
|
|
|
peers = graph.all_peers()
|
2021-03-08 22:18:06 +01:00
|
|
|
async def pay(lnaddr, pay_req):
|
|
|
|
|
self.assertEqual(PR_UNPAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-02-07 12:09:37 +01:00
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req)
|
2020-05-06 11:00:58 +02:00
|
|
|
self.assertTrue(result)
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_PAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2020-05-06 11:00:58 +02:00
|
|
|
raise PaymentDone()
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, include_routing_hints=True)
|
2021-03-08 22:18:06 +01:00
|
|
|
await group.spawn(pay(lnaddr, pay_req))
|
2020-05-06 11:00:58 +02:00
|
|
|
with self.assertRaises(PaymentDone):
|
|
|
|
|
run(f())
|
|
|
|
|
|
|
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_payment_multihop_with_preselected_path(self):
|
2020-05-06 11:27:50 +02:00
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
2020-05-06 11:00:58 +02:00
|
|
|
peers = graph.all_peers()
|
|
|
|
|
async def pay(pay_req):
|
|
|
|
|
with self.subTest(msg="bad path: edges do not chain together"):
|
2021-03-02 18:00:31 +01:00
|
|
|
path = [PathEdge(start_node=graph.w_a.node_keypair.pubkey,
|
|
|
|
|
end_node=graph.w_c.node_keypair.pubkey,
|
|
|
|
|
short_channel_id=graph.chan_ab.short_channel_id),
|
|
|
|
|
PathEdge(start_node=graph.w_b.node_keypair.pubkey,
|
|
|
|
|
end_node=graph.w_d.node_keypair.pubkey,
|
|
|
|
|
short_channel_id=graph.chan_bd.short_channel_id)]
|
2021-01-30 16:10:51 +01:00
|
|
|
with self.assertRaises(LNPathInconsistent):
|
2021-02-07 12:09:37 +01:00
|
|
|
await graph.w_a.pay_invoice(pay_req, full_path=path)
|
2020-05-06 11:00:58 +02:00
|
|
|
with self.subTest(msg="bad path: last node id differs from invoice pubkey"):
|
2021-03-02 18:00:31 +01:00
|
|
|
path = [PathEdge(start_node=graph.w_a.node_keypair.pubkey,
|
|
|
|
|
end_node=graph.w_b.node_keypair.pubkey,
|
|
|
|
|
short_channel_id=graph.chan_ab.short_channel_id)]
|
2021-01-30 16:10:51 +01:00
|
|
|
with self.assertRaises(LNPathInconsistent):
|
2021-02-07 12:09:37 +01:00
|
|
|
await graph.w_a.pay_invoice(pay_req, full_path=path)
|
2020-05-06 11:00:58 +02:00
|
|
|
with self.subTest(msg="good path"):
|
2021-03-02 18:00:31 +01:00
|
|
|
path = [PathEdge(start_node=graph.w_a.node_keypair.pubkey,
|
|
|
|
|
end_node=graph.w_b.node_keypair.pubkey,
|
|
|
|
|
short_channel_id=graph.chan_ab.short_channel_id),
|
|
|
|
|
PathEdge(start_node=graph.w_b.node_keypair.pubkey,
|
|
|
|
|
end_node=graph.w_d.node_keypair.pubkey,
|
|
|
|
|
short_channel_id=graph.chan_bd.short_channel_id)]
|
2021-02-07 12:09:37 +01:00
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req, full_path=path)
|
2020-05-06 11:00:58 +02:00
|
|
|
self.assertTrue(result)
|
2021-01-30 16:10:51 +01:00
|
|
|
self.assertEqual(
|
|
|
|
|
[edge.short_channel_id for edge in path],
|
|
|
|
|
[edge.short_channel_id for edge in log[0].route])
|
2020-05-06 11:00:58 +02:00
|
|
|
raise PaymentDone()
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, include_routing_hints=True)
|
2020-05-06 11:00:58 +02:00
|
|
|
await group.spawn(pay(pay_req))
|
|
|
|
|
with self.assertRaises(PaymentDone):
|
|
|
|
|
run(f())
|
|
|
|
|
|
|
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_payment_multihop_temp_node_failure(self):
|
2020-05-06 11:27:50 +02:00
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
2020-05-14 19:14:42 +02:00
|
|
|
graph.w_b.network.config.set_key('test_fail_htlcs_with_temp_node_failure', True)
|
|
|
|
|
graph.w_c.network.config.set_key('test_fail_htlcs_with_temp_node_failure', True)
|
2020-05-06 11:00:58 +02:00
|
|
|
peers = graph.all_peers()
|
2021-03-08 22:18:06 +01:00
|
|
|
async def pay(lnaddr, pay_req):
|
|
|
|
|
self.assertEqual(PR_UNPAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-02-07 12:09:37 +01:00
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req)
|
2020-05-06 11:00:58 +02:00
|
|
|
self.assertFalse(result)
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_UNPAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-01-30 16:10:51 +01:00
|
|
|
self.assertEqual(OnionFailureCode.TEMPORARY_NODE_FAILURE, log[0].failure_msg.code)
|
2020-05-06 11:00:58 +02:00
|
|
|
raise PaymentDone()
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, include_routing_hints=True)
|
2021-03-08 22:18:06 +01:00
|
|
|
await group.spawn(pay(lnaddr, pay_req))
|
2020-05-06 11:00:58 +02:00
|
|
|
with self.assertRaises(PaymentDone):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2021-03-02 18:35:07 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_payment_multihop_route_around_failure(self):
|
|
|
|
|
# Alice will pay Dave. Alice first tries A->C->D route, due to lower fees, but Carol
|
|
|
|
|
# will fail the htlc and get blacklisted. Alice will then try A->B->D and succeed.
|
|
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
|
|
|
|
graph.w_c.network.config.set_key('test_fail_htlcs_with_temp_node_failure', True)
|
|
|
|
|
peers = graph.all_peers()
|
2021-03-08 22:18:06 +01:00
|
|
|
async def pay(lnaddr, pay_req):
|
2021-03-02 18:35:07 +01:00
|
|
|
self.assertEqual(500000000000, graph.chan_ab.balance(LOCAL))
|
|
|
|
|
self.assertEqual(500000000000, graph.chan_db.balance(LOCAL))
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_UNPAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-03-02 18:35:07 +01:00
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req, attempts=2)
|
|
|
|
|
self.assertEqual(2, len(log))
|
|
|
|
|
self.assertTrue(result)
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_PAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-03-02 18:35:07 +01:00
|
|
|
self.assertEqual([graph.chan_ac.short_channel_id, graph.chan_cd.short_channel_id],
|
|
|
|
|
[edge.short_channel_id for edge in log[0].route])
|
|
|
|
|
self.assertEqual([graph.chan_ab.short_channel_id, graph.chan_bd.short_channel_id],
|
|
|
|
|
[edge.short_channel_id for edge in log[1].route])
|
|
|
|
|
self.assertEqual(OnionFailureCode.TEMPORARY_NODE_FAILURE, log[0].failure_msg.code)
|
|
|
|
|
self.assertEqual(499899450000, graph.chan_ab.balance(LOCAL))
|
|
|
|
|
await asyncio.sleep(0.2) # wait for COMMITMENT_SIGNED / REVACK msgs to update balance
|
|
|
|
|
self.assertEqual(500100000000, graph.chan_db.balance(LOCAL))
|
|
|
|
|
raise PaymentDone()
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, include_routing_hints=True)
|
|
|
|
|
invoice_features = lnaddr.get_features()
|
2021-03-02 18:35:07 +01:00
|
|
|
self.assertFalse(invoice_features.supports(LnFeatures.BASIC_MPP_OPT))
|
2021-03-08 22:18:06 +01:00
|
|
|
await group.spawn(pay(lnaddr, pay_req))
|
2021-03-02 18:35:07 +01:00
|
|
|
with self.assertRaises(PaymentDone):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2021-03-17 09:32:23 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
2021-03-09 08:47:30 +01:00
|
|
|
def test_payment_with_temp_channel_failure_and_liquidty_hints(self):
|
2021-03-17 09:32:23 +01:00
|
|
|
# prepare channels such that a temporary channel failure happens at c->d
|
|
|
|
|
funds_distribution = {
|
|
|
|
|
'ac': (200_000_000, 200_000_000), # low fees
|
|
|
|
|
'cd': (50_000_000, 200_000_000), # low fees
|
|
|
|
|
'ab': (200_000_000, 200_000_000), # high fees
|
|
|
|
|
'bd': (200_000_000, 200_000_000), # high fees
|
|
|
|
|
}
|
2021-03-26 10:53:46 +01:00
|
|
|
# the payment happens in two attempts:
|
|
|
|
|
# 1. along a->c->d due to low fees with temp channel failure:
|
2021-03-17 09:32:23 +01:00
|
|
|
# with chanupd: ORPHANED, private channel update
|
2021-03-26 10:53:46 +01:00
|
|
|
# c->d gets a liquidity hint and gets blocked
|
|
|
|
|
# 2. along a->b->d with success
|
2021-03-17 09:32:23 +01:00
|
|
|
amount_to_pay = 100_000_000
|
|
|
|
|
graph = self.prepare_chans_and_peers_in_square(funds_distribution)
|
|
|
|
|
peers = graph.all_peers()
|
|
|
|
|
async def pay(lnaddr, pay_req):
|
|
|
|
|
self.assertEqual(PR_UNPAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
|
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req, attempts=3)
|
|
|
|
|
self.assertTrue(result)
|
2021-03-26 10:53:46 +01:00
|
|
|
self.assertEqual(2, len(log))
|
2021-03-17 09:32:23 +01:00
|
|
|
self.assertEqual(PR_PAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
|
|
|
|
self.assertEqual(OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, log[0].failure_msg.code)
|
2021-03-09 08:47:30 +01:00
|
|
|
|
|
|
|
|
liquidity_hints = graph.w_a.network.path_finder.liquidity_hints
|
|
|
|
|
pubkey_a = graph.w_a.node_keypair.pubkey
|
|
|
|
|
pubkey_b = graph.w_b.node_keypair.pubkey
|
|
|
|
|
pubkey_c = graph.w_c.node_keypair.pubkey
|
|
|
|
|
pubkey_d = graph.w_d.node_keypair.pubkey
|
|
|
|
|
# check liquidity hints for failing route:
|
|
|
|
|
hint_ac = liquidity_hints.get_hint(graph.chan_ac.short_channel_id)
|
|
|
|
|
hint_cd = liquidity_hints.get_hint(graph.chan_cd.short_channel_id)
|
|
|
|
|
self.assertEqual(amount_to_pay, hint_ac.can_send(pubkey_a < pubkey_c))
|
|
|
|
|
self.assertEqual(None, hint_ac.cannot_send(pubkey_a < pubkey_c))
|
|
|
|
|
self.assertEqual(None, hint_cd.can_send(pubkey_c < pubkey_d))
|
|
|
|
|
self.assertEqual(amount_to_pay, hint_cd.cannot_send(pubkey_c < pubkey_d))
|
|
|
|
|
# check liquidity hints for successful route:
|
|
|
|
|
hint_ab = liquidity_hints.get_hint(graph.chan_ab.short_channel_id)
|
|
|
|
|
hint_bd = liquidity_hints.get_hint(graph.chan_bd.short_channel_id)
|
|
|
|
|
self.assertEqual(amount_to_pay, hint_ab.can_send(pubkey_a < pubkey_b))
|
|
|
|
|
self.assertEqual(None, hint_ab.cannot_send(pubkey_a < pubkey_b))
|
|
|
|
|
self.assertEqual(amount_to_pay, hint_bd.can_send(pubkey_b < pubkey_d))
|
|
|
|
|
self.assertEqual(None, hint_bd.cannot_send(pubkey_b < pubkey_d))
|
|
|
|
|
|
2021-03-17 09:32:23 +01:00
|
|
|
raise PaymentDone()
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, amount_msat=amount_to_pay, include_routing_hints=True)
|
|
|
|
|
await group.spawn(pay(lnaddr, pay_req))
|
|
|
|
|
with self.assertRaises(PaymentDone):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2021-07-12 16:16:18 +02:00
|
|
|
def _run_mpp(self, graph, fail_kwargs, success_kwargs):
|
|
|
|
|
"""Tests a multipart payment scenario for failing and successful cases."""
|
2021-03-03 12:52:52 +01:00
|
|
|
self.assertEqual(500_000_000_000, graph.chan_ab.balance(LOCAL))
|
|
|
|
|
self.assertEqual(500_000_000_000, graph.chan_ac.balance(LOCAL))
|
|
|
|
|
amount_to_pay = 600_000_000_000
|
|
|
|
|
peers = graph.all_peers()
|
2021-07-12 16:16:18 +02:00
|
|
|
async def pay(
|
|
|
|
|
attempts=1,
|
|
|
|
|
alice_uses_trampoline=False,
|
|
|
|
|
bob_forwarding=True,
|
|
|
|
|
mpp_invoice=True
|
|
|
|
|
):
|
2021-03-11 10:37:44 +01:00
|
|
|
if mpp_invoice:
|
|
|
|
|
graph.w_d.features |= LnFeatures.BASIC_MPP_OPT
|
2021-03-11 11:01:35 +01:00
|
|
|
if not bob_forwarding:
|
2021-03-18 07:48:30 +01:00
|
|
|
graph.w_b.enable_htlc_forwarding = False
|
2021-03-11 10:37:44 +01:00
|
|
|
if alice_uses_trampoline:
|
|
|
|
|
if graph.w_a.network.channel_db:
|
|
|
|
|
graph.w_a.network.channel_db.stop()
|
|
|
|
|
await graph.w_a.network.channel_db.stopped_event.wait()
|
|
|
|
|
graph.w_a.network.channel_db = None
|
|
|
|
|
else:
|
|
|
|
|
assert graph.w_a.network.channel_db is not None
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, include_routing_hints=True, amount_msat=amount_to_pay)
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_UNPAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-03-05 13:00:24 +01:00
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req, attempts=attempts)
|
2021-03-11 11:01:35 +01:00
|
|
|
if not bob_forwarding:
|
|
|
|
|
# reset to previous state, sleep 2s so that the second htlc can time out
|
2021-03-18 07:48:30 +01:00
|
|
|
graph.w_b.enable_htlc_forwarding = True
|
2021-03-11 11:01:35 +01:00
|
|
|
await asyncio.sleep(2)
|
2021-03-03 12:52:52 +01:00
|
|
|
if result:
|
2021-03-08 22:18:06 +01:00
|
|
|
self.assertEqual(PR_PAID, graph.w_d.get_payment_status(lnaddr.paymenthash))
|
2021-03-03 12:52:52 +01:00
|
|
|
raise PaymentDone()
|
|
|
|
|
else:
|
|
|
|
|
raise NoPathFound()
|
2021-03-11 10:37:44 +01:00
|
|
|
|
|
|
|
|
async def f(kwargs):
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
await group.spawn(pay(**kwargs))
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(NoPathFound):
|
2021-07-12 16:16:18 +02:00
|
|
|
run(f(fail_kwargs))
|
2021-03-11 10:37:44 +01:00
|
|
|
with self.assertRaises(PaymentDone):
|
2021-07-12 16:16:18 +02:00
|
|
|
run(f(success_kwargs))
|
2021-03-10 17:09:07 +01:00
|
|
|
|
|
|
|
|
@needs_test_with_all_chacha20_implementations
|
2021-07-12 16:16:18 +02:00
|
|
|
def test_payment_multipart_with_timeout(self):
|
2021-03-10 17:09:07 +01:00
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
2021-07-12 16:16:18 +02:00
|
|
|
self._run_mpp(graph, {'bob_forwarding': False}, {'bob_forwarding': True})
|
2021-03-03 12:52:52 +01:00
|
|
|
|
2021-03-05 13:00:24 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
2021-07-12 16:16:18 +02:00
|
|
|
def test_payment_multipart(self):
|
2021-03-05 13:00:24 +01:00
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
2021-07-12 16:16:18 +02:00
|
|
|
self._run_mpp(graph, {'mpp_invoice': False}, {'mpp_invoice': True})
|
2021-03-05 13:00:24 +01:00
|
|
|
|
|
|
|
|
@needs_test_with_all_chacha20_implementations
|
2021-07-12 16:16:18 +02:00
|
|
|
def test_payment_multipart_trampoline(self):
|
2021-03-11 10:37:44 +01:00
|
|
|
# single attempt will fail with insufficient trampoline fee
|
2021-03-05 13:00:24 +01:00
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
2021-07-02 18:44:39 +02:00
|
|
|
electrum.trampoline._TRAMPOLINE_NODES_UNITTESTS = {
|
|
|
|
|
graph.w_b.name: LNPeerAddr(host="127.0.0.1", port=9735, pubkey=graph.w_b.node_keypair.pubkey),
|
|
|
|
|
graph.w_c.name: LNPeerAddr(host="127.0.0.1", port=9735, pubkey=graph.w_c.node_keypair.pubkey),
|
|
|
|
|
}
|
|
|
|
|
try:
|
2021-07-12 16:16:18 +02:00
|
|
|
self._run_mpp(
|
|
|
|
|
graph,
|
|
|
|
|
{'alice_uses_trampoline': True, 'attempts': 1},
|
|
|
|
|
{'alice_uses_trampoline': True, 'attempts': 30})
|
2021-07-02 18:44:39 +02:00
|
|
|
finally:
|
|
|
|
|
electrum.trampoline._TRAMPOLINE_NODES_UNITTESTS = {}
|
2021-03-05 13:00:24 +01:00
|
|
|
|
2021-03-11 20:35:21 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_fail_pending_htlcs_on_shutdown(self):
|
|
|
|
|
"""Alice tries to pay Dave via MPP. Dave receives some HTLCs but not all.
|
|
|
|
|
Dave shuts down (stops wallet).
|
|
|
|
|
We test if Dave fails the pending HTLCs during shutdown.
|
|
|
|
|
"""
|
|
|
|
|
graph = self.prepare_chans_and_peers_in_square()
|
|
|
|
|
self.assertEqual(500_000_000_000, graph.chan_ab.balance(LOCAL))
|
|
|
|
|
self.assertEqual(500_000_000_000, graph.chan_ac.balance(LOCAL))
|
|
|
|
|
amount_to_pay = 600_000_000_000
|
|
|
|
|
peers = graph.all_peers()
|
|
|
|
|
graph.w_d.MPP_EXPIRY = 120
|
|
|
|
|
graph.w_d.TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 3
|
|
|
|
|
async def pay():
|
|
|
|
|
graph.w_d.features |= LnFeatures.BASIC_MPP_OPT
|
2021-03-18 07:48:30 +01:00
|
|
|
graph.w_b.enable_htlc_forwarding = False # Bob will hold forwarded HTLCs
|
2021-03-11 20:35:21 +01:00
|
|
|
assert graph.w_a.network.channel_db is not None
|
|
|
|
|
lnaddr, pay_req = await self.prepare_invoice(graph.w_d, include_routing_hints=True, amount_msat=amount_to_pay)
|
|
|
|
|
try:
|
2021-10-27 16:46:15 +02:00
|
|
|
async with timeout_after(1.0):
|
2021-03-11 20:35:21 +01:00
|
|
|
result, log = await graph.w_a.pay_invoice(pay_req, attempts=1)
|
|
|
|
|
except TaskTimeout:
|
|
|
|
|
# by now Dave hopefully received some HTLCs:
|
|
|
|
|
self.assertTrue(len(graph.chan_dc.hm.htlcs(LOCAL)) > 0)
|
|
|
|
|
self.assertTrue(len(graph.chan_dc.hm.htlcs(REMOTE)) > 0)
|
|
|
|
|
else:
|
|
|
|
|
self.fail(f"pay_invoice finished but was not supposed to. result={result}")
|
|
|
|
|
await graph.w_d.stop()
|
|
|
|
|
# Dave is supposed to have failed the pending incomplete MPP HTLCs
|
|
|
|
|
self.assertEqual(0, len(graph.chan_dc.hm.htlcs(LOCAL)))
|
|
|
|
|
self.assertEqual(0, len(graph.chan_dc.hm.htlcs(REMOTE)))
|
2021-03-23 17:17:43 +01:00
|
|
|
raise SuccessfulTest()
|
2021-03-11 20:35:21 +01:00
|
|
|
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in peers:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
await group.spawn(pay())
|
|
|
|
|
|
2021-03-23 17:17:43 +01:00
|
|
|
with self.assertRaises(SuccessfulTest):
|
2021-03-11 20:35:21 +01:00
|
|
|
run(f())
|
|
|
|
|
|
2020-03-04 18:54:20 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
2020-02-25 12:35:07 +01:00
|
|
|
def test_close(self):
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
w1.network.config.set_key('dynamic_fees', False)
|
|
|
|
|
w2.network.config.set_key('dynamic_fees', False)
|
|
|
|
|
w1.network.config.set_key('fee_per_kb', 5000)
|
|
|
|
|
w2.network.config.set_key('fee_per_kb', 1000)
|
2021-03-18 07:48:30 +01:00
|
|
|
w2.enable_htlc_settle = False
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = run(self.prepare_invoice(w2))
|
2020-02-25 12:35:07 +01:00
|
|
|
async def pay():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
2020-02-27 13:41:40 +01:00
|
|
|
# alice sends htlc
|
2021-05-10 09:21:53 +02:00
|
|
|
route, amount_msat = (await w1.create_routes_from_invoice(lnaddr.get_amount_msat(), decoded_invoice=lnaddr))[0][0:2]
|
|
|
|
|
p1.pay(route=route,
|
|
|
|
|
chan=alice_channel,
|
|
|
|
|
amount_msat=lnaddr.get_amount_msat(),
|
|
|
|
|
total_msat=lnaddr.get_amount_msat(),
|
|
|
|
|
payment_hash=lnaddr.paymenthash,
|
|
|
|
|
min_final_cltv_expiry=lnaddr.get_min_final_cltv_expiry(),
|
|
|
|
|
payment_secret=lnaddr.payment_secret)
|
2020-02-27 13:41:40 +01:00
|
|
|
# alice closes
|
2020-02-25 12:35:07 +01:00
|
|
|
await p1.close_channel(alice_channel.channel_id)
|
|
|
|
|
gath.cancel()
|
2020-02-27 20:53:50 +01:00
|
|
|
async def set_settle():
|
|
|
|
|
await asyncio.sleep(0.1)
|
2021-03-18 07:48:30 +01:00
|
|
|
w2.enable_htlc_settle = True
|
2020-03-02 15:41:50 +01:00
|
|
|
gath = asyncio.gather(pay(), set_settle(), p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
|
2020-02-25 12:35:07 +01:00
|
|
|
async def f():
|
|
|
|
|
await gath
|
|
|
|
|
with self.assertRaises(concurrent.futures.CancelledError):
|
|
|
|
|
run(f())
|
|
|
|
|
|
2020-12-29 17:40:01 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_close_upfront_shutdown_script(self):
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
|
|
|
|
|
# create upfront shutdown script for bob, alice doesn't use upfront
|
|
|
|
|
# shutdown script
|
|
|
|
|
bob_uss_pub = lnutil.privkey_to_pubkey(os.urandom(32))
|
|
|
|
|
bob_uss_addr = bitcoin.pubkey_to_address('p2wpkh', bh2u(bob_uss_pub))
|
|
|
|
|
bob_uss = bfh(bitcoin.address_to_script(bob_uss_addr))
|
|
|
|
|
|
|
|
|
|
# bob commits to close to bob_uss
|
|
|
|
|
alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss
|
|
|
|
|
# but bob closes to some receiving address, which we achieve by not
|
|
|
|
|
# setting the upfront shutdown script in the channel config
|
|
|
|
|
bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = b''
|
|
|
|
|
|
|
|
|
|
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
w1.network.config.set_key('dynamic_fees', False)
|
|
|
|
|
w2.network.config.set_key('dynamic_fees', False)
|
|
|
|
|
w1.network.config.set_key('fee_per_kb', 5000)
|
|
|
|
|
w2.network.config.set_key('fee_per_kb', 1000)
|
|
|
|
|
|
|
|
|
|
async def test():
|
|
|
|
|
async def close():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
|
|
|
|
# bob closes channel with different shutdown script
|
|
|
|
|
await p1.close_channel(alice_channel.channel_id)
|
|
|
|
|
gath.cancel()
|
|
|
|
|
|
|
|
|
|
async def main_loop(peer):
|
|
|
|
|
async with peer.taskgroup as group:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
|
|
|
|
|
coros = [close(), main_loop(p1), main_loop(p2)]
|
|
|
|
|
gath = asyncio.gather(*coros)
|
|
|
|
|
await gath
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(UpfrontShutdownScriptViolation):
|
|
|
|
|
run(test())
|
|
|
|
|
|
|
|
|
|
# bob sends the same upfront_shutdown_script has he announced
|
|
|
|
|
alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss
|
|
|
|
|
bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = bob_uss
|
|
|
|
|
|
|
|
|
|
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
w1.network.config.set_key('dynamic_fees', False)
|
|
|
|
|
w2.network.config.set_key('dynamic_fees', False)
|
|
|
|
|
w1.network.config.set_key('fee_per_kb', 5000)
|
|
|
|
|
w2.network.config.set_key('fee_per_kb', 1000)
|
|
|
|
|
|
|
|
|
|
async def test():
|
|
|
|
|
async def close():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
|
|
|
|
await p1.close_channel(alice_channel.channel_id)
|
|
|
|
|
gath.cancel()
|
|
|
|
|
|
|
|
|
|
async def main_loop(peer):
|
|
|
|
|
async with peer.taskgroup as group:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
|
|
|
|
|
coros = [close(), main_loop(p1), main_loop(p2)]
|
|
|
|
|
gath = asyncio.gather(*coros)
|
|
|
|
|
await gath
|
2021-01-11 12:30:49 +01:00
|
|
|
with self.assertRaises(concurrent.futures.CancelledError):
|
2020-12-29 17:40:01 +01:00
|
|
|
run(test())
|
|
|
|
|
|
2018-11-02 19:16:42 +01:00
|
|
|
def test_channel_usage_after_closing(self):
|
2020-02-12 10:32:55 +01:00
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
2021-03-08 21:46:56 +01:00
|
|
|
lnaddr, pay_req = run(self.prepare_invoice(w2))
|
2018-11-02 19:16:42 +01:00
|
|
|
|
2021-01-30 16:10:51 +01:00
|
|
|
lnaddr = w1._check_invoice(pay_req)
|
2021-05-10 09:21:53 +02:00
|
|
|
route, amount_msat = run(w1.create_routes_from_invoice(lnaddr.get_amount_msat(), decoded_invoice=lnaddr))[0][0:2]
|
2021-01-30 16:10:51 +01:00
|
|
|
assert amount_msat == lnaddr.get_amount_msat()
|
2018-11-02 19:16:42 +01:00
|
|
|
|
2020-02-12 10:32:55 +01:00
|
|
|
run(w1.force_close_channel(alice_channel.channel_id))
|
2018-11-02 19:16:42 +01:00
|
|
|
# check if a tx (commitment transaction) was broadcasted:
|
|
|
|
|
assert q1.qsize() == 1
|
|
|
|
|
|
2019-10-09 19:23:09 +02:00
|
|
|
with self.assertRaises(NoPathFound) as e:
|
2021-05-10 09:21:53 +02:00
|
|
|
run(w1.create_routes_from_invoice(lnaddr.get_amount_msat(), decoded_invoice=lnaddr))
|
2018-11-02 19:16:42 +01:00
|
|
|
|
|
|
|
|
peer = w1.peers[route[0].node_id]
|
|
|
|
|
# AssertionError is ok since we shouldn't use old routes, and the
|
|
|
|
|
# route finding should fail when channel is closed
|
2019-03-06 06:17:52 +01:00
|
|
|
async def f():
|
2021-02-07 11:57:20 +01:00
|
|
|
min_cltv_expiry = lnaddr.get_min_final_cltv_expiry()
|
|
|
|
|
payment_hash = lnaddr.paymenthash
|
|
|
|
|
payment_secret = lnaddr.payment_secret
|
2021-02-24 20:03:12 +01:00
|
|
|
pay = w1.pay_to_route(
|
2021-02-27 20:26:58 +01:00
|
|
|
route=route,
|
2021-02-24 20:03:12 +01:00
|
|
|
amount_msat=amount_msat,
|
|
|
|
|
total_msat=amount_msat,
|
2021-03-06 10:59:29 +01:00
|
|
|
amount_receiver_msat=amount_msat,
|
2021-02-24 20:03:12 +01:00
|
|
|
payment_hash=payment_hash,
|
|
|
|
|
payment_secret=payment_secret,
|
|
|
|
|
min_cltv_expiry=min_cltv_expiry)
|
2021-02-07 11:57:20 +01:00
|
|
|
await asyncio.gather(pay, p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
|
2019-05-19 11:55:55 +02:00
|
|
|
with self.assertRaises(PaymentFailure):
|
2019-03-06 06:17:52 +01:00
|
|
|
run(f())
|
2018-11-02 19:16:42 +01:00
|
|
|
|
2021-03-19 20:51:38 +01:00
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_sending_weird_messages_that_should_be_ignored(self):
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
|
|
|
|
|
async def send_weird_messages():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
|
|
|
|
# peer1 sends known message with trailing garbage
|
|
|
|
|
# BOLT-01 says peer2 should ignore trailing garbage
|
|
|
|
|
raw_msg1 = encode_msg('ping', num_pong_bytes=4, byteslen=4) + bytes(range(55))
|
|
|
|
|
p1.transport.send_bytes(raw_msg1)
|
|
|
|
|
await asyncio.sleep(0.05)
|
|
|
|
|
# peer1 sends unknown 'odd-type' message
|
|
|
|
|
# BOLT-01 says peer2 should ignore whole message
|
|
|
|
|
raw_msg2 = (43333).to_bytes(length=2, byteorder="big") + bytes(range(55))
|
|
|
|
|
p1.transport.send_bytes(raw_msg2)
|
|
|
|
|
await asyncio.sleep(0.05)
|
2021-03-23 17:17:43 +01:00
|
|
|
raise SuccessfulTest()
|
2021-03-19 20:51:38 +01:00
|
|
|
|
|
|
|
|
async def f():
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
for peer in [p1, p2]:
|
|
|
|
|
await group.spawn(peer._message_loop())
|
|
|
|
|
await group.spawn(peer.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
await group.spawn(send_weird_messages())
|
|
|
|
|
|
2021-03-23 17:17:43 +01:00
|
|
|
with self.assertRaises(SuccessfulTest):
|
2021-03-19 20:51:38 +01:00
|
|
|
run(f())
|
|
|
|
|
|
|
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_sending_weird_messages__unknown_even_type(self):
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
|
|
|
|
|
async def send_weird_messages():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
|
|
|
|
# peer1 sends unknown 'even-type' message
|
|
|
|
|
# BOLT-01 says peer2 should close the connection
|
|
|
|
|
raw_msg2 = (43334).to_bytes(length=2, byteorder="big") + bytes(range(55))
|
|
|
|
|
p1.transport.send_bytes(raw_msg2)
|
|
|
|
|
await asyncio.sleep(0.05)
|
|
|
|
|
|
|
|
|
|
failing_task = None
|
|
|
|
|
async def f():
|
|
|
|
|
nonlocal failing_task
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
await group.spawn(p1._message_loop())
|
|
|
|
|
await group.spawn(p1.htlc_switch())
|
|
|
|
|
failing_task = await group.spawn(p2._message_loop())
|
|
|
|
|
await group.spawn(p2.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
await group.spawn(send_weird_messages())
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(lnmsg.UnknownMandatoryMsgType):
|
|
|
|
|
run(f())
|
|
|
|
|
self.assertTrue(isinstance(failing_task.exception(), lnmsg.UnknownMandatoryMsgType))
|
|
|
|
|
|
|
|
|
|
@needs_test_with_all_chacha20_implementations
|
|
|
|
|
def test_sending_weird_messages__known_msg_with_insufficient_length(self):
|
|
|
|
|
alice_channel, bob_channel = create_test_channels()
|
|
|
|
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
|
|
|
|
|
|
|
|
|
|
async def send_weird_messages():
|
|
|
|
|
await asyncio.wait_for(p1.initialized, 1)
|
|
|
|
|
await asyncio.wait_for(p2.initialized, 1)
|
|
|
|
|
# peer1 sends known message with insufficient length for the contents
|
|
|
|
|
# BOLT-01 says peer2 should fail the connection
|
|
|
|
|
raw_msg1 = encode_msg('ping', num_pong_bytes=4, byteslen=4)[:-1]
|
|
|
|
|
p1.transport.send_bytes(raw_msg1)
|
|
|
|
|
await asyncio.sleep(0.05)
|
|
|
|
|
|
|
|
|
|
failing_task = None
|
|
|
|
|
async def f():
|
|
|
|
|
nonlocal failing_task
|
|
|
|
|
async with TaskGroup() as group:
|
|
|
|
|
await group.spawn(p1._message_loop())
|
|
|
|
|
await group.spawn(p1.htlc_switch())
|
|
|
|
|
failing_task = await group.spawn(p2._message_loop())
|
|
|
|
|
await group.spawn(p2.htlc_switch())
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
await group.spawn(send_weird_messages())
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(lnmsg.UnexpectedEndOfStream):
|
|
|
|
|
run(f())
|
|
|
|
|
self.assertTrue(isinstance(failing_task.exception(), lnmsg.UnexpectedEndOfStream))
|
|
|
|
|
|
2020-03-06 21:54:05 +01:00
|
|
|
|
2018-11-02 19:16:42 +01:00
|
|
|
def run(coro):
|
2019-03-06 06:17:52 +01:00
|
|
|
return asyncio.run_coroutine_threadsafe(coro, loop=asyncio.get_event_loop()).result()
|