mirror of
https://github.com/basicswap/basicswap.git
synced 2026-04-08 18:37:23 +02:00
tests: add base for electrum functional tests
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ import subprocess
|
|||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from .util import read_json_api
|
from .util import read_json_api
|
||||||
|
from basicswap.basicswap import Coins
|
||||||
from basicswap.rpc import callrpc
|
from basicswap.rpc import callrpc
|
||||||
from basicswap.util import toBool
|
from basicswap.util import toBool
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||||
@@ -125,6 +126,65 @@ def prepareDataDir(
|
|||||||
return node_dir
|
return node_dir
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_balance(
|
||||||
|
use_delay_event,
|
||||||
|
coin,
|
||||||
|
amount: float,
|
||||||
|
port_target_node: int,
|
||||||
|
port_take_from_node: int,
|
||||||
|
test_balance: bool = True,
|
||||||
|
) -> None:
|
||||||
|
if coin == Coins.PART_BLIND:
|
||||||
|
coin_ticker: str = "PART"
|
||||||
|
balance_type: str = "blind_balance"
|
||||||
|
address_type: str = "stealth_address"
|
||||||
|
type_to: str = "blind"
|
||||||
|
elif coin == Coins.PART_ANON:
|
||||||
|
coin_ticker: str = "PART"
|
||||||
|
balance_type: str = "anon_balance"
|
||||||
|
address_type: str = "stealth_address"
|
||||||
|
type_to: str = "anon"
|
||||||
|
else:
|
||||||
|
coin_ticker: str = coin.name
|
||||||
|
balance_type: str = "balance"
|
||||||
|
address_type: str = "deposit_address"
|
||||||
|
js_w = read_json_api(port_target_node, "wallets")
|
||||||
|
current_balance: float = float(js_w[coin_ticker][balance_type])
|
||||||
|
|
||||||
|
if test_balance and current_balance >= amount:
|
||||||
|
return
|
||||||
|
post_json = {
|
||||||
|
"value": amount,
|
||||||
|
"address": js_w[coin_ticker][address_type],
|
||||||
|
"subfee": False,
|
||||||
|
}
|
||||||
|
if coin in (Coins.XMR, Coins.WOW):
|
||||||
|
post_json["sweepall"] = False
|
||||||
|
if coin in (Coins.PART_BLIND, Coins.PART_ANON):
|
||||||
|
post_json["type_to"] = type_to
|
||||||
|
json_rv = read_json_api(
|
||||||
|
port_take_from_node,
|
||||||
|
"wallets/{}/withdraw".format(coin_ticker.lower()),
|
||||||
|
post_json,
|
||||||
|
)
|
||||||
|
assert len(json_rv["txid"]) == 64
|
||||||
|
wait_for_amount: float = amount
|
||||||
|
if not test_balance:
|
||||||
|
wait_for_amount += current_balance
|
||||||
|
delay_iterations = 100 if coin == Coins.NAV else 20
|
||||||
|
delay_time = 5 if coin == Coins.NAV else 3
|
||||||
|
wait_for_balance(
|
||||||
|
use_delay_event,
|
||||||
|
"http://127.0.0.1:{}/json/wallets/{}".format(
|
||||||
|
port_target_node, coin_ticker.lower()
|
||||||
|
),
|
||||||
|
balance_type,
|
||||||
|
wait_for_amount,
|
||||||
|
iterations=delay_iterations,
|
||||||
|
delay_time=delay_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def checkForks(ro):
|
def checkForks(ro):
|
||||||
try:
|
try:
|
||||||
if "bip9_softforks" in ro:
|
if "bip9_softforks" in ro:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -139,6 +139,7 @@ def run_prepare(
|
|||||||
use_rpcauth=False,
|
use_rpcauth=False,
|
||||||
extra_settings={},
|
extra_settings={},
|
||||||
port_ofs=0,
|
port_ofs=0,
|
||||||
|
extra_args=[],
|
||||||
):
|
):
|
||||||
config_path = os.path.join(datadir_path, cfg.CONFIG_FILENAME)
|
config_path = os.path.join(datadir_path, cfg.CONFIG_FILENAME)
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ def run_prepare(
|
|||||||
"-noextractover",
|
"-noextractover",
|
||||||
"-noreleasesizecheck",
|
"-noreleasesizecheck",
|
||||||
"-xmrrestoreheight=0",
|
"-xmrrestoreheight=0",
|
||||||
]
|
] + extra_args
|
||||||
if mnemonic_in:
|
if mnemonic_in:
|
||||||
testargs.append(f'-particl_mnemonic="{mnemonic_in}"')
|
testargs.append(f'-particl_mnemonic="{mnemonic_in}"')
|
||||||
|
|
||||||
@@ -644,6 +645,7 @@ class XmrTestBase(TestBase):
|
|||||||
runSystem.main()
|
runSystem.main()
|
||||||
|
|
||||||
def start_processes(self):
|
def start_processes(self):
|
||||||
|
multiprocessing.set_start_method("fork")
|
||||||
self.delay_event.clear()
|
self.delay_event.clear()
|
||||||
|
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2024 tecnovert
|
# Copyright (c) 2021-2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ def signal_handler(self, sig, frame):
|
|||||||
self.delay_event.set()
|
self.delay_event.set()
|
||||||
|
|
||||||
|
|
||||||
def run_thread(self, client_id):
|
def run_process(client_id):
|
||||||
client_path = os.path.join(test_path, "client{}".format(client_id))
|
client_path = os.path.join(test_path, "client{}".format(client_id))
|
||||||
testargs = [
|
testargs = [
|
||||||
"basicswap-run",
|
"basicswap-run",
|
||||||
@@ -283,16 +283,14 @@ def run_thread(self, client_id):
|
|||||||
|
|
||||||
|
|
||||||
def start_processes(self):
|
def start_processes(self):
|
||||||
|
multiprocessing.set_start_method("spawn")
|
||||||
self.delay_event.clear()
|
self.delay_event.clear()
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
self.processes.append(
|
self.processes.append(
|
||||||
multiprocessing.Process(
|
multiprocessing.Process(
|
||||||
target=run_thread,
|
target=run_process,
|
||||||
args=(
|
args=(i,),
|
||||||
self,
|
|
||||||
i,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.processes[-1].start()
|
self.processes[-1].start()
|
||||||
@@ -302,7 +300,7 @@ def start_processes(self):
|
|||||||
|
|
||||||
wallets = read_json_api(UI_PORT + 1, "wallets")
|
wallets = read_json_api(UI_PORT + 1, "wallets")
|
||||||
|
|
||||||
if "monero" in TEST_COINS_LIST:
|
if "monero" in self.test_coins_list:
|
||||||
xmr_auth = None
|
xmr_auth = None
|
||||||
if os.getenv("XMR_RPC_USER", "") != "":
|
if os.getenv("XMR_RPC_USER", "") != "":
|
||||||
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
|
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
|
||||||
@@ -336,7 +334,7 @@ def start_processes(self):
|
|||||||
callbtcrpc(0, "generatetoaddress", [num_blocks, self.btc_addr])
|
callbtcrpc(0, "generatetoaddress", [num_blocks, self.btc_addr])
|
||||||
logging.info("BTC blocks: {}".format(callbtcrpc(0, "getblockcount")))
|
logging.info("BTC blocks: {}".format(callbtcrpc(0, "getblockcount")))
|
||||||
|
|
||||||
if "litecoin" in TEST_COINS_LIST:
|
if "litecoin" in self.test_coins_list:
|
||||||
self.ltc_addr = callltcrpc(
|
self.ltc_addr = callltcrpc(
|
||||||
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
|
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
@@ -367,7 +365,7 @@ def start_processes(self):
|
|||||||
wallet="wallet.dat",
|
wallet="wallet.dat",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "decred" in TEST_COINS_LIST:
|
if "decred" in self.test_coins_list:
|
||||||
if RESET_TEST:
|
if RESET_TEST:
|
||||||
_ = calldcrrpc(0, "getnewaddress")
|
_ = calldcrrpc(0, "getnewaddress")
|
||||||
# assert (addr == self.dcr_addr)
|
# assert (addr == self.dcr_addr)
|
||||||
@@ -397,7 +395,7 @@ def start_processes(self):
|
|||||||
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
|
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
|
||||||
self.update_thread_dcr.start()
|
self.update_thread_dcr.start()
|
||||||
|
|
||||||
if "firo" in TEST_COINS_LIST:
|
if "firo" in self.test_coins_list:
|
||||||
self.firo_addr = callfirorpc(0, "getnewaddress", ["mining_addr"])
|
self.firo_addr = callfirorpc(0, "getnewaddress", ["mining_addr"])
|
||||||
num_blocks: int = 200
|
num_blocks: int = 200
|
||||||
have_blocks: int = callfirorpc(0, "getblockcount")
|
have_blocks: int = callfirorpc(0, "getblockcount")
|
||||||
@@ -413,7 +411,7 @@ def start_processes(self):
|
|||||||
[num_blocks - have_blocks, self.firo_addr],
|
[num_blocks - have_blocks, self.firo_addr],
|
||||||
)
|
)
|
||||||
|
|
||||||
if "bitcoincash" in TEST_COINS_LIST:
|
if "bitcoincash" in self.test_coins_list:
|
||||||
self.bch_addr = callbchrpc(
|
self.bch_addr = callbchrpc(
|
||||||
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
|
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
@@ -432,7 +430,7 @@ def start_processes(self):
|
|||||||
wallet="wallet.dat",
|
wallet="wallet.dat",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "dogecoin" in TEST_COINS_LIST:
|
if "dogecoin" in self.test_coins_list:
|
||||||
self.doge_addr = calldogerpc(0, "getnewaddress", ["mining_addr"])
|
self.doge_addr = calldogerpc(0, "getnewaddress", ["mining_addr"])
|
||||||
num_blocks: int = 200
|
num_blocks: int = 200
|
||||||
have_blocks: int = calldogerpc(0, "getblockcount")
|
have_blocks: int = calldogerpc(0, "getblockcount")
|
||||||
@@ -446,7 +444,7 @@ def start_processes(self):
|
|||||||
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
|
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
|
||||||
)
|
)
|
||||||
|
|
||||||
if "namecoin" in TEST_COINS_LIST:
|
if "namecoin" in self.test_coins_list:
|
||||||
self.nmc_addr = callnmcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
self.nmc_addr = callnmcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
||||||
num_blocks: int = 500
|
num_blocks: int = 500
|
||||||
have_blocks: int = callnmcrpc(0, "getblockcount")
|
have_blocks: int = callnmcrpc(0, "getblockcount")
|
||||||
@@ -540,6 +538,22 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
bch_addr = None
|
bch_addr = None
|
||||||
doge_addr = None
|
doge_addr = None
|
||||||
initialised = False
|
initialised = False
|
||||||
|
test_coins_list = TEST_COINS_LIST
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def modifyConfig(cls, test_path, i):
|
||||||
|
modifyConfig(test_path, i)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setupNodes(cls):
|
||||||
|
logging.info(f"Preparing {NUM_NODES} nodes.")
|
||||||
|
prepare_nodes(
|
||||||
|
NUM_NODES,
|
||||||
|
cls.test_coins_list,
|
||||||
|
True,
|
||||||
|
{"min_sequence_lock_seconds": 60},
|
||||||
|
PORT_OFS,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
@@ -550,17 +564,9 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
if os.path.exists(test_path) and not RESET_TEST:
|
if os.path.exists(test_path) and not RESET_TEST:
|
||||||
logging.info(f"Continuing with existing directory: {test_path}")
|
logging.info(f"Continuing with existing directory: {test_path}")
|
||||||
else:
|
else:
|
||||||
logging.info(f"Preparing {NUM_NODES} nodes.")
|
cls.setupNodes()
|
||||||
prepare_nodes(
|
|
||||||
NUM_NODES,
|
|
||||||
TEST_COINS_LIST,
|
|
||||||
True,
|
|
||||||
{"min_sequence_lock_seconds": 60},
|
|
||||||
PORT_OFS,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
modifyConfig(test_path, i)
|
cls.modifyConfig(test_path, i)
|
||||||
|
|
||||||
signal.signal(
|
signal.signal(
|
||||||
signal.SIGINT, lambda signal, frame: signal_handler(cls, signal, frame)
|
signal.SIGINT, lambda signal, frame: signal_handler(cls, signal, frame)
|
||||||
|
|||||||
282
tests/basicswap/test_electrum.py
Normal file
282
tests/basicswap/test_electrum.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2026 The Basicswap developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Ensure Electrumx is installed to a venv in ELECTRUMX_SRC_DIR/venv
|
||||||
|
# Example setup with default paths:
|
||||||
|
|
||||||
|
The leveldb system package may be required to install plyvel:
|
||||||
|
sudo pacman -S leveldb
|
||||||
|
|
||||||
|
cd ~/tmp/
|
||||||
|
git clone git@github.com:spesmilo/electrumx.git
|
||||||
|
cd electrumx
|
||||||
|
python3 -m venv venv
|
||||||
|
. venv/bin/activate
|
||||||
|
pip install ".[ujson]"
|
||||||
|
|
||||||
|
# Run test
|
||||||
|
export TEST_PATH=/tmp/test_electrum
|
||||||
|
mkdir -p ${TEST_PATH}/bin
|
||||||
|
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
|
||||||
|
export ELECTRUMX_SRC_DIR="~/tmp/electrumx"
|
||||||
|
export EXTRA_CONFIG_JSON="{\"btc0\":[\"txindex=1\",\"rpcworkqueue=1100\"]}"
|
||||||
|
export TEST_COINS_LIST="bitcoin"
|
||||||
|
export PYTHONPATH=$(pwd)
|
||||||
|
python tests/basicswap/test_electrum.py
|
||||||
|
|
||||||
|
|
||||||
|
# Optionally copy coin releases to permanent storage for faster subsequent startups
|
||||||
|
cp -r ${TEST_PATH}/bin/* ~/tmp/basicswap_bin/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import basicswap.config as cfg
|
||||||
|
from basicswap.basicswap import Coins
|
||||||
|
from basicswap.util.daemon import Daemon
|
||||||
|
|
||||||
|
from tests.basicswap.common import (
|
||||||
|
prepare_balance,
|
||||||
|
stopDaemons,
|
||||||
|
waitForNumBids,
|
||||||
|
waitForNumOffers,
|
||||||
|
)
|
||||||
|
from tests.basicswap.common_xmr import run_prepare, TEST_PATH
|
||||||
|
from tests.basicswap.extended.test_xmr_persistent import (
|
||||||
|
BaseTestWithPrepare,
|
||||||
|
NUM_NODES,
|
||||||
|
PORT_OFS,
|
||||||
|
RESET_TEST,
|
||||||
|
)
|
||||||
|
from tests.basicswap.mnemonics import mnemonics
|
||||||
|
from tests.basicswap.util import (
|
||||||
|
read_json_api,
|
||||||
|
post_json_api,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.level = logging.DEBUG
|
||||||
|
if not len(logger.handlers):
|
||||||
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
|
||||||
|
def modifyConfig(test_path, i):
|
||||||
|
if i == 1:
|
||||||
|
config_path = os.path.join(test_path, f"client{i}", cfg.CONFIG_FILENAME)
|
||||||
|
with open(config_path) as fp:
|
||||||
|
settings = json.load(fp)
|
||||||
|
|
||||||
|
settings["fetchpricesthread"] = False
|
||||||
|
with open(config_path, "w") as fp:
|
||||||
|
json.dump(settings, fp, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
class Test(BaseTestWithPrepare):
|
||||||
|
update_min = 2
|
||||||
|
daemons = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(Test, cls).setUpClass()
|
||||||
|
|
||||||
|
logger.info("Starting Electrumx for BTC")
|
||||||
|
ELECTRUMX_SRC_DIR = os.path.expanduser(os.getenv("ELECTRUMX_SRC_DIR"))
|
||||||
|
if ELECTRUMX_SRC_DIR is None:
|
||||||
|
raise ValueError("Please set ELECTRUMX_SRC_DIR")
|
||||||
|
ELECTRUMX_VENV = os.getenv(
|
||||||
|
"ELECTRUMX_VENV", os.path.join(ELECTRUMX_SRC_DIR, "venv")
|
||||||
|
)
|
||||||
|
BTC_BASE_RPC_PORT = 32793 # client0
|
||||||
|
ELECTRUMX_DATADIR_BTC = os.path.join(TEST_PATH, "electrumx_btc")
|
||||||
|
SSL_CERTFILE = f"{ELECTRUMX_DATADIR_BTC}/certfile.crt"
|
||||||
|
SSL_KEYFILE = f"{ELECTRUMX_DATADIR_BTC}/keyfile.key"
|
||||||
|
|
||||||
|
if os.path.isdir(ELECTRUMX_DATADIR_BTC):
|
||||||
|
if RESET_TEST:
|
||||||
|
logger.info("Removing " + ELECTRUMX_DATADIR_BTC)
|
||||||
|
shutil.rmtree(ELECTRUMX_DATADIR_BTC)
|
||||||
|
if not os.path.exists(ELECTRUMX_DATADIR_BTC):
|
||||||
|
os.makedirs(os.path.join(ELECTRUMX_DATADIR_BTC, "db"))
|
||||||
|
with open(os.path.join(ELECTRUMX_DATADIR_BTC, "banner"), "w") as fp:
|
||||||
|
fp.write("TEST BANNER")
|
||||||
|
try:
|
||||||
|
stdout = subprocess.check_output(
|
||||||
|
[
|
||||||
|
"openssl",
|
||||||
|
"req",
|
||||||
|
"-nodes",
|
||||||
|
"-new",
|
||||||
|
"-x509",
|
||||||
|
"-keyout",
|
||||||
|
SSL_KEYFILE,
|
||||||
|
"-out",
|
||||||
|
SSL_CERTFILE,
|
||||||
|
"-subj",
|
||||||
|
'/C=CA/ST=Quebec/L=Montreal/O="Poutine LLC"/OU=devops/CN=*.poutine.net\n',
|
||||||
|
],
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
logger.info(f"openssl {stdout}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.info(f"Error openssl {e.output}")
|
||||||
|
|
||||||
|
electrumx_env = {
|
||||||
|
"COIN": "Bitcoin",
|
||||||
|
"NET": "regtest",
|
||||||
|
"LOG_LEVEL": "debug",
|
||||||
|
"SERVICES": "tcp://:50001,ssl://:50002,rpc://",
|
||||||
|
"CACHE_MB": "400",
|
||||||
|
"DAEMON_URL": f"http://test_btc_0:test_btc_pwd_0@127.0.0.1:{BTC_BASE_RPC_PORT}",
|
||||||
|
"DB_DIRECTORY": f"{ELECTRUMX_DATADIR_BTC}/db",
|
||||||
|
"SSL_CERTFILE": f"{ELECTRUMX_DATADIR_BTC}/certfile.crt",
|
||||||
|
"SSL_KEYFILE": f"{ELECTRUMX_DATADIR_BTC}/keyfile.key",
|
||||||
|
"BANNER_FILE": f"{ELECTRUMX_DATADIR_BTC}/banner",
|
||||||
|
"DAEMON_POLL_INTERVAL_BLOCKS": "1000",
|
||||||
|
"DAEMON_POLL_INTERVAL_MEMPOOL": "1000",
|
||||||
|
}
|
||||||
|
opened_files = []
|
||||||
|
stdout_dest = open(f"{ELECTRUMX_DATADIR_BTC}/electrumx.log", "w")
|
||||||
|
stderr_dest = stdout_dest
|
||||||
|
cls.daemons.append(
|
||||||
|
Daemon(
|
||||||
|
subprocess.Popen(
|
||||||
|
[
|
||||||
|
os.path.join(ELECTRUMX_VENV, "bin", "python"),
|
||||||
|
os.path.join(ELECTRUMX_SRC_DIR, "electrumx_server"),
|
||||||
|
],
|
||||||
|
shell=False,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=stdout_dest,
|
||||||
|
stderr=stderr_dest,
|
||||||
|
cwd=ELECTRUMX_SRC_DIR,
|
||||||
|
env=electrumx_env,
|
||||||
|
),
|
||||||
|
[
|
||||||
|
opened_files,
|
||||||
|
],
|
||||||
|
"electrumx_btc",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def modifyConfig(cls, test_path, i):
|
||||||
|
modifyConfig(test_path, i)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setupNodes(cls):
|
||||||
|
logger.info(f"Preparing {NUM_NODES} nodes.")
|
||||||
|
|
||||||
|
bins_path = os.path.join(TEST_PATH, "bin")
|
||||||
|
for i in range(NUM_NODES):
|
||||||
|
logger.info(f"Preparing node: {i}.")
|
||||||
|
client_path = os.path.join(TEST_PATH, f"client{i}")
|
||||||
|
try:
|
||||||
|
shutil.rmtree(client_path)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(f"setupNodes {ex}")
|
||||||
|
|
||||||
|
extra_args = []
|
||||||
|
if i == 1:
|
||||||
|
extra_args = [
|
||||||
|
"--btc-mode=electrum",
|
||||||
|
"--btc-electrum-server=127.0.0.1:50001",
|
||||||
|
]
|
||||||
|
run_prepare(
|
||||||
|
i,
|
||||||
|
client_path,
|
||||||
|
bins_path,
|
||||||
|
cls.test_coins_list,
|
||||||
|
mnemonics[i] if i < len(mnemonics) else None,
|
||||||
|
num_nodes=NUM_NODES,
|
||||||
|
use_rpcauth=True,
|
||||||
|
extra_settings={"min_sequence_lock_seconds": 60},
|
||||||
|
port_ofs=PORT_OFS,
|
||||||
|
extra_args=extra_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
logger.info("Finalising Test")
|
||||||
|
super().tearDownClass()
|
||||||
|
stopDaemons(cls.daemons)
|
||||||
|
|
||||||
|
def test_electrum(self):
|
||||||
|
|
||||||
|
port_node_from: int = 12701
|
||||||
|
port_node_to: int = 12702
|
||||||
|
prepare_balance(self.delay_event, Coins.BTC, 100, 12702, 12701, True)
|
||||||
|
|
||||||
|
amt_from_str = f"{random.uniform(0.5, 10.0):.{8}f}"
|
||||||
|
amt_to_str = f"{random.uniform(0.5, 10.0):.{8}f}"
|
||||||
|
data = {
|
||||||
|
"addr_from": "-1",
|
||||||
|
"coin_from": "part",
|
||||||
|
"coin_to": "btc",
|
||||||
|
"amt_from": amt_from_str,
|
||||||
|
"amt_to": amt_to_str,
|
||||||
|
"lockhrs": "24",
|
||||||
|
"swap_type": "adaptor_sig",
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"Creating offer {amt_from_str} PART -> {amt_to_str} BTC")
|
||||||
|
offer_id: str = post_json_api(port_node_from, "offers/new", data)["offer_id"]
|
||||||
|
summary = read_json_api(port_node_from)
|
||||||
|
assert summary["num_sent_offers"] == 1
|
||||||
|
|
||||||
|
logger.info(f"Waiting for offer: {offer_id}")
|
||||||
|
waitForNumOffers(self.delay_event, port_node_to, 1)
|
||||||
|
|
||||||
|
offers = read_json_api(port_node_to, "offers")
|
||||||
|
offer = offers[0]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"offer_id": offer["offer_id"],
|
||||||
|
"amount_from": offer["amount_from"],
|
||||||
|
"validmins": 60,
|
||||||
|
}
|
||||||
|
post_json_api(port_node_to, "bids/new", data)
|
||||||
|
waitForNumBids(self.delay_event, port_node_from, 1)
|
||||||
|
|
||||||
|
for i in range(20):
|
||||||
|
bids = read_json_api(port_node_from, "bids")
|
||||||
|
bid = bids[0]
|
||||||
|
if bid["bid_state"] == "Received":
|
||||||
|
break
|
||||||
|
self.delay_event.wait(1)
|
||||||
|
assert bid["bid_state"] == "Received"
|
||||||
|
|
||||||
|
data = {"accept": True}
|
||||||
|
rv = post_json_api(12701, "bids/{}".format(bid["bid_id"]), data)
|
||||||
|
assert rv["bid_state"] == "Accepted"
|
||||||
|
|
||||||
|
logger.info("Completing swap")
|
||||||
|
for i in range(240):
|
||||||
|
if self.delay_event.is_set():
|
||||||
|
raise ValueError("Test stopped.")
|
||||||
|
self.delay_event.wait(4)
|
||||||
|
|
||||||
|
rv = read_json_api(12701, "bids/{}".format(bid["bid_id"]))
|
||||||
|
if rv["bid_state"] == "Completed":
|
||||||
|
break
|
||||||
|
assert rv["bid_state"] == "Completed"
|
||||||
|
|
||||||
|
# Wait for bid to be removed from in-progress
|
||||||
|
waitForNumBids(self.delay_event, 12701, 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -60,6 +60,7 @@ from tests.basicswap.util import (
|
|||||||
from tests.basicswap.common import (
|
from tests.basicswap.common import (
|
||||||
callrpc_cli,
|
callrpc_cli,
|
||||||
prepareDataDir,
|
prepareDataDir,
|
||||||
|
prepare_balance,
|
||||||
make_rpc_func,
|
make_rpc_func,
|
||||||
checkForks,
|
checkForks,
|
||||||
stopDaemons,
|
stopDaemons,
|
||||||
@@ -1055,54 +1056,13 @@ class BaseTest(unittest.TestCase):
|
|||||||
port_take_from_node: int,
|
port_take_from_node: int,
|
||||||
test_balance: bool = True,
|
test_balance: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
delay_iterations = 100 if coin == Coins.NAV else 20
|
prepare_balance(
|
||||||
delay_time = 5 if coin == Coins.NAV else 3
|
|
||||||
if coin == Coins.PART_BLIND:
|
|
||||||
coin_ticker: str = "PART"
|
|
||||||
balance_type: str = "blind_balance"
|
|
||||||
address_type: str = "stealth_address"
|
|
||||||
type_to: str = "blind"
|
|
||||||
elif coin == Coins.PART_ANON:
|
|
||||||
coin_ticker: str = "PART"
|
|
||||||
balance_type: str = "anon_balance"
|
|
||||||
address_type: str = "stealth_address"
|
|
||||||
type_to: str = "anon"
|
|
||||||
else:
|
|
||||||
coin_ticker: str = coin.name
|
|
||||||
balance_type: str = "balance"
|
|
||||||
address_type: str = "deposit_address"
|
|
||||||
js_w = read_json_api(port_target_node, "wallets")
|
|
||||||
current_balance: float = float(js_w[coin_ticker][balance_type])
|
|
||||||
|
|
||||||
if test_balance and current_balance >= amount:
|
|
||||||
return
|
|
||||||
post_json = {
|
|
||||||
"value": amount,
|
|
||||||
"address": js_w[coin_ticker][address_type],
|
|
||||||
"subfee": False,
|
|
||||||
}
|
|
||||||
if coin in (Coins.XMR, Coins.WOW):
|
|
||||||
post_json["sweepall"] = False
|
|
||||||
if coin in (Coins.PART_BLIND, Coins.PART_ANON):
|
|
||||||
post_json["type_to"] = type_to
|
|
||||||
json_rv = read_json_api(
|
|
||||||
port_take_from_node,
|
|
||||||
"wallets/{}/withdraw".format(coin_ticker.lower()),
|
|
||||||
post_json,
|
|
||||||
)
|
|
||||||
assert len(json_rv["txid"]) == 64
|
|
||||||
wait_for_amount: float = amount
|
|
||||||
if not test_balance:
|
|
||||||
wait_for_amount += current_balance
|
|
||||||
wait_for_balance(
|
|
||||||
test_delay_event,
|
test_delay_event,
|
||||||
"http://127.0.0.1:{}/json/wallets/{}".format(
|
coin,
|
||||||
port_target_node, coin_ticker.lower()
|
amount,
|
||||||
),
|
port_target_node,
|
||||||
balance_type,
|
port_take_from_node,
|
||||||
wait_for_amount,
|
test_balance,
|
||||||
iterations=delay_iterations,
|
|
||||||
delay_time=delay_time,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user