mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-20 01:24:31 +00:00
2748 lines
91 KiB
Python
2748 lines
91 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2020-2024 tecnovert
|
|
# Copyright (c) 2024-2025 The Basicswap developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import random
|
|
import shutil
|
|
import signal
|
|
import logging
|
|
import unittest
|
|
import traceback
|
|
import threading
|
|
|
|
import basicswap.config as cfg
|
|
from basicswap.db import (
|
|
Concepts,
|
|
)
|
|
from basicswap.basicswap import (
|
|
Coins,
|
|
BasicSwap,
|
|
BidStates,
|
|
SwapTypes,
|
|
DebugTypes,
|
|
)
|
|
from basicswap.basicswap_util import (
|
|
TxLockTypes,
|
|
EventLogTypes,
|
|
)
|
|
from basicswap.util import COIN, format_amount, make_int, TemporaryError
|
|
from basicswap.util.address import (
|
|
toWIF,
|
|
)
|
|
from basicswap.rpc import (
|
|
callrpc,
|
|
callrpc_cli,
|
|
)
|
|
from basicswap.rpc_xmr import (
|
|
callrpc_xmr,
|
|
)
|
|
from basicswap.interface.xmr import (
|
|
XMR_COIN,
|
|
)
|
|
from basicswap.contrib.key import (
|
|
ECKey,
|
|
)
|
|
from basicswap.http_server import (
|
|
HttpThread,
|
|
)
|
|
from tests.basicswap.util import (
|
|
make_boolean,
|
|
post_json_req,
|
|
)
|
|
from tests.basicswap.util import (
|
|
read_json_api,
|
|
)
|
|
from tests.basicswap.common import (
|
|
prepareDataDir,
|
|
make_rpc_func,
|
|
checkForks,
|
|
stopDaemons,
|
|
wait_for_bid,
|
|
wait_for_event,
|
|
wait_for_offer,
|
|
wait_for_no_offer,
|
|
wait_for_none_active,
|
|
wait_for_balance,
|
|
wait_for_unspent,
|
|
waitForRPC,
|
|
compare_bid_states,
|
|
extract_states_from_xu_file,
|
|
TEST_HTTP_HOST,
|
|
TEST_HTTP_PORT,
|
|
BASE_RPC_PORT,
|
|
BASE_ZMQ_PORT,
|
|
BTC_BASE_PORT,
|
|
BTC_BASE_RPC_PORT,
|
|
LTC_BASE_PORT,
|
|
LTC_BASE_RPC_PORT,
|
|
PREFIX_SECRET_KEY_REGTEST,
|
|
)
|
|
from basicswap.db_util import (
|
|
remove_expired_data,
|
|
)
|
|
from basicswap.bin.run import startDaemon, startXmrDaemon, startXmrWalletDaemon
|
|
|
|
|
|
logger = logging.getLogger()
|
|
|
|
NUM_NODES = 3
|
|
NUM_XMR_NODES = 3
|
|
NUM_BTC_NODES = 3
|
|
NUM_LTC_NODES = 3
|
|
TEST_DIR = cfg.TEST_DATADIRS
|
|
|
|
XMR_BASE_P2P_PORT = 17792
|
|
XMR_BASE_RPC_PORT = 21792
|
|
XMR_BASE_ZMQ_PORT = 22792
|
|
XMR_BASE_WALLET_RPC_PORT = 23792
|
|
|
|
signal_event = threading.Event() # Set if test was cancelled
|
|
test_delay_event = threading.Event()
|
|
RESET_TEST = make_boolean(os.getenv("RESET_TEST", "true"))
|
|
|
|
|
|
def prepareXmrDataDir(datadir, node_id, conf_file):
|
|
node_dir = os.path.join(datadir, "xmr_" + str(node_id))
|
|
if not os.path.exists(node_dir):
|
|
os.makedirs(node_dir)
|
|
cfg_file_path = os.path.join(node_dir, conf_file)
|
|
if os.path.exists(cfg_file_path):
|
|
return
|
|
with open(cfg_file_path, "w+") as fp:
|
|
fp.write("regtest=1\n")
|
|
fp.write("keep-fakechain=1\n")
|
|
fp.write("data-dir={}\n".format(node_dir))
|
|
fp.write("fixed-difficulty=1\n")
|
|
# fp.write('offline=1\n')
|
|
fp.write("p2p-bind-port={}\n".format(XMR_BASE_P2P_PORT + node_id))
|
|
fp.write("rpc-bind-port={}\n".format(XMR_BASE_RPC_PORT + node_id))
|
|
fp.write("p2p-bind-ip=127.0.0.1\n")
|
|
fp.write("rpc-bind-ip=127.0.0.1\n")
|
|
fp.write("prune-blockchain=1\n")
|
|
fp.write("zmq-rpc-bind-port={}\n".format(XMR_BASE_ZMQ_PORT + node_id))
|
|
fp.write("zmq-rpc-bind-ip=127.0.0.1\n")
|
|
|
|
for i in range(0, NUM_XMR_NODES):
|
|
if node_id == i:
|
|
continue
|
|
fp.write("add-exclusive-node=127.0.0.1:{}\n".format(XMR_BASE_P2P_PORT + i))
|
|
|
|
|
|
def prepare_swapclient_dir(
|
|
datadir, node_id, network_key, network_pubkey, with_coins=set(), cls=None
|
|
):
|
|
basicswap_dir = os.path.join(datadir, "basicswap_" + str(node_id))
|
|
if not os.path.exists(basicswap_dir):
|
|
os.makedirs(basicswap_dir)
|
|
|
|
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
|
settings = {
|
|
"debug": True,
|
|
"zmqhost": "tcp://127.0.0.1",
|
|
"zmqport": BASE_ZMQ_PORT + node_id,
|
|
"htmlhost": "127.0.0.1",
|
|
"htmlport": TEST_HTTP_PORT + node_id,
|
|
"network_key": network_key,
|
|
"network_pubkey": network_pubkey,
|
|
"chainclients": {
|
|
"particl": {
|
|
"connection_type": "rpc",
|
|
"manage_daemon": False,
|
|
"rpcport": BASE_RPC_PORT + node_id,
|
|
"rpcuser": "test" + str(node_id),
|
|
"rpcpassword": "test_pass" + str(node_id),
|
|
"datadir": os.path.join(datadir, "part_" + str(node_id)),
|
|
"bindir": cfg.PARTICL_BINDIR,
|
|
"blocks_confirmed": 2, # Faster testing
|
|
"anon_tx_ring_size": 5, # Faster testing
|
|
},
|
|
"bitcoin": {
|
|
"connection_type": "rpc",
|
|
"manage_daemon": False,
|
|
"rpcport": BTC_BASE_RPC_PORT + node_id,
|
|
"rpcuser": "test" + str(node_id),
|
|
"rpcpassword": "test_pass" + str(node_id),
|
|
"datadir": os.path.join(datadir, "btc_" + str(node_id)),
|
|
"bindir": cfg.BITCOIN_BINDIR,
|
|
"use_segwit": True,
|
|
},
|
|
},
|
|
"check_progress_seconds": 2,
|
|
"check_watched_seconds": 4,
|
|
"check_expired_seconds": 60,
|
|
"check_events_seconds": 1,
|
|
"check_xmr_swaps_seconds": 1,
|
|
"min_delay_event": 1,
|
|
"max_delay_event": 4,
|
|
"min_delay_event_short": 1,
|
|
"max_delay_event_short": 3,
|
|
"min_delay_retry": 2,
|
|
"max_delay_retry": 10,
|
|
"debug_ui": True,
|
|
"restrict_unknown_seed_wallets": False,
|
|
}
|
|
|
|
if Coins.XMR in with_coins:
|
|
settings["chainclients"]["monero"] = {
|
|
"connection_type": "rpc",
|
|
"manage_daemon": False,
|
|
"rpcport": XMR_BASE_RPC_PORT + node_id,
|
|
"walletrpcport": XMR_BASE_WALLET_RPC_PORT + node_id,
|
|
"walletrpcuser": "test" + str(node_id),
|
|
"walletrpcpassword": "test_pass" + str(node_id),
|
|
"walletfile": "testwallet",
|
|
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
|
|
"bindir": cfg.XMR_BINDIR,
|
|
}
|
|
|
|
if Coins.LTC in with_coins:
|
|
settings["chainclients"]["litecoin"] = {
|
|
"connection_type": "rpc",
|
|
"manage_daemon": False,
|
|
"rpcport": LTC_BASE_RPC_PORT + node_id,
|
|
"rpcuser": "test" + str(node_id),
|
|
"rpcpassword": "test_pass" + str(node_id),
|
|
"datadir": os.path.join(datadir, "ltc_" + str(node_id)),
|
|
"bindir": cfg.LITECOIN_BINDIR,
|
|
"use_segwit": True,
|
|
}
|
|
|
|
if cls:
|
|
cls.addCoinSettings(settings, datadir, node_id)
|
|
|
|
with open(settings_path, "w") as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
|
|
|
|
def btcCli(cmd, node_id=0):
|
|
return callrpc_cli(
|
|
cfg.BITCOIN_BINDIR,
|
|
os.path.join(TEST_DIR, "btc_" + str(node_id)),
|
|
"regtest",
|
|
cmd,
|
|
cfg.BITCOIN_CLI,
|
|
)
|
|
|
|
|
|
def ltcCli(cmd, node_id=0):
|
|
return callrpc_cli(
|
|
cfg.LITECOIN_BINDIR,
|
|
os.path.join(TEST_DIR, "ltc_" + str(node_id)),
|
|
"regtest",
|
|
cmd,
|
|
cfg.LITECOIN_CLI,
|
|
)
|
|
|
|
|
|
def signal_handler(sig, frame):
|
|
logging.info("signal {} detected.".format(sig))
|
|
signal_event.set()
|
|
test_delay_event.set()
|
|
|
|
|
|
def waitForXMRNode(rpc_offset, max_tries=7):
|
|
for i in range(max_tries + 1):
|
|
try:
|
|
callrpc_xmr(XMR_BASE_RPC_PORT + rpc_offset, "get_block_count")
|
|
return
|
|
except Exception as ex:
|
|
if i < max_tries:
|
|
logging.warning(
|
|
"Can't connect to XMR RPC: %s. Retrying in %d second/s.",
|
|
str(ex),
|
|
(i + 1),
|
|
)
|
|
time.sleep(i + 1)
|
|
raise ValueError("waitForXMRNode failed")
|
|
|
|
|
|
def waitForXMRWallet(rpc_offset, auth, max_tries=7):
|
|
for i in range(max_tries + 1):
|
|
try:
|
|
callrpc_xmr(
|
|
XMR_BASE_WALLET_RPC_PORT + rpc_offset, "get_languages", auth=auth
|
|
)
|
|
return
|
|
except Exception as ex:
|
|
if i < max_tries:
|
|
logging.warning(
|
|
"Can't connect to XMR wallet RPC: %s. Retrying in %d second/s.",
|
|
str(ex),
|
|
(i + 1),
|
|
)
|
|
time.sleep(i + 1)
|
|
raise ValueError("waitForXMRWallet failed")
|
|
|
|
|
|
def callnoderpc(node_id, method, params=[], wallet=None, base_rpc_port=BASE_RPC_PORT):
|
|
auth = "test{0}:test_pass{0}".format(node_id)
|
|
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
|
|
|
|
|
pause_event = threading.Event()
|
|
|
|
|
|
def run_coins_loop(cls):
|
|
while not test_delay_event.is_set():
|
|
pause_event.wait()
|
|
try:
|
|
cls.coins_loop()
|
|
except Exception as e:
|
|
logging.warning("run_coins_loop " + str(e))
|
|
test_delay_event.wait(1.0)
|
|
|
|
|
|
def run_loop(cls):
|
|
while not test_delay_event.is_set():
|
|
for c in cls.swap_clients:
|
|
c.update()
|
|
test_delay_event.wait(1.0)
|
|
|
|
|
|
class BaseTest(unittest.TestCase):
|
|
__test__ = False
|
|
update_thread = None
|
|
coins_update_thread = None
|
|
http_threads = []
|
|
swap_clients = []
|
|
part_daemons = []
|
|
btc_daemons = []
|
|
ltc_daemons = []
|
|
xmr_daemons = []
|
|
xmr_wallet_auth = []
|
|
restore_instance = False
|
|
extra_wait_time = 0
|
|
|
|
start_ltc_nodes = False
|
|
start_xmr_nodes = True
|
|
has_segwit = True
|
|
|
|
xmr_addr = None
|
|
btc_addr = None
|
|
ltc_addr = None
|
|
|
|
@classmethod
|
|
def getRandomPubkey(cls):
|
|
eckey = ECKey()
|
|
eckey.generate()
|
|
return eckey.get_pubkey().get_bytes()
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if signal_event.is_set():
|
|
raise ValueError("Test has been cancelled.")
|
|
test_delay_event.clear()
|
|
random.seed(time.time())
|
|
|
|
logger.propagate = False
|
|
logger.handlers = []
|
|
logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post
|
|
formatter = logging.Formatter("%(asctime)s %(levelname)s : %(message)s")
|
|
stream_stdout = logging.StreamHandler()
|
|
stream_stdout.setFormatter(formatter)
|
|
logger.addHandler(stream_stdout)
|
|
|
|
logging.info("Setting up tests for class: " + cls.__name__)
|
|
|
|
diagrams_dir = "doc/protocols/sequence_diagrams"
|
|
cls.states_bidder = extract_states_from_xu_file(
|
|
os.path.join(diagrams_dir, "ads.bidder.alt.xu"), "B"
|
|
)
|
|
cls.states_offerer = extract_states_from_xu_file(
|
|
os.path.join(diagrams_dir, "ads.offerer.alt.xu"), "O"
|
|
)
|
|
|
|
cls.states_bidder_sh = extract_states_from_xu_file(
|
|
os.path.join(diagrams_dir, "bidder.alt.xu"), "B"
|
|
)
|
|
cls.states_offerer_sh = extract_states_from_xu_file(
|
|
os.path.join(diagrams_dir, "offerer.alt.xu"), "O"
|
|
)
|
|
|
|
if os.path.isdir(TEST_DIR):
|
|
if RESET_TEST:
|
|
logging.info("Removing " + TEST_DIR)
|
|
for name in os.listdir(TEST_DIR):
|
|
if name == "pivx-params":
|
|
continue
|
|
fullpath = os.path.join(TEST_DIR, name)
|
|
if os.path.isdir(fullpath):
|
|
shutil.rmtree(fullpath)
|
|
else:
|
|
os.remove(fullpath)
|
|
else:
|
|
logging.info("Restoring instance from " + TEST_DIR)
|
|
cls.restore_instance = True
|
|
if not os.path.exists(TEST_DIR):
|
|
os.makedirs(TEST_DIR)
|
|
|
|
cls.stream_fp = logging.FileHandler(os.path.join(TEST_DIR, "test.log"))
|
|
cls.stream_fp.setFormatter(formatter)
|
|
logger.addHandler(cls.stream_fp)
|
|
|
|
try:
|
|
logging.info("Preparing coin nodes.")
|
|
for i in range(NUM_NODES):
|
|
if not cls.restore_instance:
|
|
data_dir = prepareDataDir(TEST_DIR, i, "particl.conf", "part_")
|
|
if os.path.exists(
|
|
os.path.join(cfg.PARTICL_BINDIR, "particl-wallet")
|
|
):
|
|
try:
|
|
callrpc_cli(
|
|
cfg.PARTICL_BINDIR,
|
|
data_dir,
|
|
"regtest",
|
|
"-wallet=wallet.dat -legacy create",
|
|
"particl-wallet",
|
|
)
|
|
except Exception as e:
|
|
logging.warning(
|
|
f"particl-wallet create failed {e}, retrying without -legacy"
|
|
)
|
|
callrpc_cli(
|
|
cfg.PARTICL_BINDIR,
|
|
data_dir,
|
|
"regtest",
|
|
"-wallet=wallet.dat create",
|
|
"particl-wallet",
|
|
)
|
|
|
|
cls.part_daemons.append(
|
|
startDaemon(
|
|
os.path.join(TEST_DIR, "part_" + str(i)),
|
|
cfg.PARTICL_BINDIR,
|
|
cfg.PARTICLD,
|
|
)
|
|
)
|
|
logging.info(
|
|
"Started %s %d", cfg.PARTICLD, cls.part_daemons[-1].handle.pid
|
|
)
|
|
|
|
if not cls.restore_instance:
|
|
for i in range(NUM_NODES):
|
|
# Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync
|
|
rpc = make_rpc_func(i)
|
|
waitForRPC(rpc, test_delay_event)
|
|
if i == 0:
|
|
rpc(
|
|
"extkeyimportmaster",
|
|
[
|
|
"abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb"
|
|
],
|
|
)
|
|
elif i == 1:
|
|
rpc(
|
|
"extkeyimportmaster",
|
|
[
|
|
"pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic",
|
|
"",
|
|
"true",
|
|
],
|
|
)
|
|
rpc("getnewextaddress", ["lblExtTest"])
|
|
rpc("rescanblockchain")
|
|
else:
|
|
rpc("extkeyimportmaster", [rpc("mnemonic", ["new"])["master"]])
|
|
# Lower output split threshold for more stakeable outputs
|
|
rpc(
|
|
"walletsettings",
|
|
[
|
|
"stakingoptions",
|
|
{"stakecombinethreshold": 100, "stakesplitthreshold": 200},
|
|
],
|
|
)
|
|
rpc("reservebalance", [False])
|
|
|
|
for i in range(NUM_BTC_NODES):
|
|
if not cls.restore_instance:
|
|
data_dir = prepareDataDir(
|
|
TEST_DIR,
|
|
i,
|
|
"bitcoin.conf",
|
|
"btc_",
|
|
base_p2p_port=BTC_BASE_PORT,
|
|
base_rpc_port=BTC_BASE_RPC_PORT,
|
|
)
|
|
if os.path.exists(
|
|
os.path.join(cfg.BITCOIN_BINDIR, "bitcoin-wallet")
|
|
):
|
|
try:
|
|
callrpc_cli(
|
|
cfg.BITCOIN_BINDIR,
|
|
data_dir,
|
|
"regtest",
|
|
"-wallet=wallet.dat -legacy create",
|
|
"bitcoin-wallet",
|
|
)
|
|
except Exception as e:
|
|
logging.warning(
|
|
f"bitcoin-wallet create failed {e}, retrying without -legacy"
|
|
)
|
|
callrpc_cli(
|
|
cfg.BITCOIN_BINDIR,
|
|
data_dir,
|
|
"regtest",
|
|
"-wallet=wallet.dat create",
|
|
"bitcoin-wallet",
|
|
)
|
|
|
|
cls.btc_daemons.append(
|
|
startDaemon(
|
|
os.path.join(TEST_DIR, "btc_" + str(i)),
|
|
cfg.BITCOIN_BINDIR,
|
|
cfg.BITCOIND,
|
|
)
|
|
)
|
|
logging.info(
|
|
"Started %s %d", cfg.BITCOIND, cls.part_daemons[-1].handle.pid
|
|
)
|
|
|
|
waitForRPC(
|
|
make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), test_delay_event
|
|
)
|
|
|
|
if cls.start_ltc_nodes:
|
|
for i in range(NUM_LTC_NODES):
|
|
if not cls.restore_instance:
|
|
data_dir = prepareDataDir(
|
|
TEST_DIR,
|
|
i,
|
|
"litecoin.conf",
|
|
"ltc_",
|
|
base_p2p_port=LTC_BASE_PORT,
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
)
|
|
if os.path.exists(
|
|
os.path.join(cfg.LITECOIN_BINDIR, "litecoin-wallet")
|
|
):
|
|
callrpc_cli(
|
|
cfg.LITECOIN_BINDIR,
|
|
data_dir,
|
|
"regtest",
|
|
"-wallet=wallet.dat create",
|
|
"litecoin-wallet",
|
|
)
|
|
|
|
cls.ltc_daemons.append(
|
|
startDaemon(
|
|
os.path.join(TEST_DIR, "ltc_" + str(i)),
|
|
cfg.LITECOIN_BINDIR,
|
|
cfg.LITECOIND,
|
|
)
|
|
)
|
|
logging.info(
|
|
"Started %s %d", cfg.LITECOIND, cls.part_daemons[-1].handle.pid
|
|
)
|
|
|
|
waitForRPC(
|
|
make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT),
|
|
test_delay_event,
|
|
)
|
|
|
|
if cls.start_xmr_nodes:
|
|
for i in range(NUM_XMR_NODES):
|
|
if not cls.restore_instance:
|
|
prepareXmrDataDir(TEST_DIR, i, "monerod.conf")
|
|
|
|
node_dir = os.path.join(TEST_DIR, "xmr_" + str(i))
|
|
cls.xmr_daemons.append(
|
|
startXmrDaemon(node_dir, cfg.XMR_BINDIR, cfg.XMRD)
|
|
)
|
|
logging.info(
|
|
"Started %s %d", cfg.XMRD, cls.xmr_daemons[-1].handle.pid
|
|
)
|
|
waitForXMRNode(i)
|
|
|
|
opts = [
|
|
"--daemon-address=127.0.0.1:{}".format(XMR_BASE_RPC_PORT + i),
|
|
"--no-dns",
|
|
"--rpc-bind-port={}".format(XMR_BASE_WALLET_RPC_PORT + i),
|
|
"--wallet-dir={}".format(os.path.join(node_dir, "wallets")),
|
|
"--log-file={}".format(os.path.join(node_dir, "wallet.log")),
|
|
"--rpc-login=test{0}:test_pass{0}".format(i),
|
|
"--shared-ringdb-dir={}".format(
|
|
os.path.join(node_dir, "shared-ringdb")
|
|
),
|
|
"--allow-mismatched-daemon-version",
|
|
]
|
|
cls.xmr_daemons.append(
|
|
startXmrWalletDaemon(
|
|
node_dir, cfg.XMR_BINDIR, cfg.XMR_WALLET_RPC, opts=opts
|
|
)
|
|
)
|
|
|
|
for i in range(NUM_XMR_NODES):
|
|
cls.xmr_wallet_auth.append(
|
|
("test{0}".format(i), "test_pass{0}".format(i))
|
|
)
|
|
logging.info("Creating XMR wallet %i", i)
|
|
|
|
waitForXMRWallet(i, cls.xmr_wallet_auth[i])
|
|
|
|
if not cls.restore_instance:
|
|
cls.callxmrnodewallet(
|
|
cls,
|
|
i,
|
|
"create_wallet",
|
|
{"filename": "testwallet", "language": "English"},
|
|
)
|
|
cls.callxmrnodewallet(
|
|
cls, i, "open_wallet", {"filename": "testwallet"}
|
|
)
|
|
|
|
for i in range(NUM_NODES):
|
|
# Hook for descendant classes
|
|
cls.prepareExtraDataDir(i)
|
|
|
|
logging.info("Preparing swap clients.")
|
|
if not cls.restore_instance:
|
|
eckey = ECKey()
|
|
eckey.generate()
|
|
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
|
|
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
|
|
|
for i in range(NUM_NODES):
|
|
start_nodes = set()
|
|
if cls.start_ltc_nodes:
|
|
start_nodes.add(Coins.LTC)
|
|
if cls.start_xmr_nodes:
|
|
start_nodes.add(Coins.XMR)
|
|
if not cls.restore_instance:
|
|
prepare_swapclient_dir(
|
|
TEST_DIR,
|
|
i,
|
|
cls.network_key,
|
|
cls.network_pubkey,
|
|
start_nodes,
|
|
cls,
|
|
)
|
|
basicswap_dir = os.path.join(
|
|
os.path.join(TEST_DIR, "basicswap_" + str(i))
|
|
)
|
|
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
|
with open(settings_path) as fs:
|
|
settings = json.load(fs)
|
|
if cls.restore_instance and i == 1:
|
|
cls.network_key = settings["network_key"]
|
|
cls.network_pubkey = settings["network_pubkey"]
|
|
fp = open(os.path.join(basicswap_dir, "basicswap.log"), "w")
|
|
sc = BasicSwap(
|
|
fp,
|
|
basicswap_dir,
|
|
settings,
|
|
"regtest",
|
|
log_name="BasicSwap{}".format(i),
|
|
)
|
|
cls.swap_clients.append(sc)
|
|
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].handle.pid)
|
|
sc.setDaemonPID(Coins.PART, cls.part_daemons[i].handle.pid)
|
|
|
|
if cls.start_ltc_nodes:
|
|
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].handle.pid)
|
|
cls.addPIDInfo(sc, i)
|
|
|
|
sc.start()
|
|
if cls.start_xmr_nodes:
|
|
# Set XMR main wallet address
|
|
xmr_ci = sc.ci(Coins.XMR)
|
|
sc.setStringKV(
|
|
"main_wallet_addr_" + xmr_ci.coin_name().lower(),
|
|
xmr_ci.getMainWalletAddress(),
|
|
)
|
|
|
|
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
|
|
cls.http_threads.append(t)
|
|
t.start()
|
|
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
|
|
void_block_rewards_pubkey = cls.getRandomPubkey()
|
|
if cls.restore_instance:
|
|
cls.btc_addr = (
|
|
cls.swap_clients[0]
|
|
.ci(Coins.BTC)
|
|
.pubkey_to_segwit_address(void_block_rewards_pubkey)
|
|
)
|
|
if cls.start_ltc_nodes:
|
|
cls.ltc_addr = (
|
|
cls.swap_clients[0]
|
|
.ci(Coins.LTC)
|
|
.pubkey_to_address(void_block_rewards_pubkey)
|
|
)
|
|
if cls.start_xmr_nodes:
|
|
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, "get_address")[
|
|
"address"
|
|
]
|
|
else:
|
|
cls.btc_addr = callnoderpc(
|
|
0,
|
|
"getnewaddress",
|
|
["mining_addr", "bech32"],
|
|
base_rpc_port=BTC_BASE_RPC_PORT,
|
|
)
|
|
num_blocks = 400 # Mine enough to activate segwit
|
|
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
|
|
callnoderpc(
|
|
0,
|
|
"generatetoaddress",
|
|
[num_blocks, cls.btc_addr],
|
|
base_rpc_port=BTC_BASE_RPC_PORT,
|
|
)
|
|
|
|
btc_addr1 = callnoderpc(
|
|
1,
|
|
"getnewaddress",
|
|
["initial addr"],
|
|
base_rpc_port=BTC_BASE_RPC_PORT,
|
|
)
|
|
for i in range(5):
|
|
callnoderpc(
|
|
0,
|
|
"sendtoaddress",
|
|
[btc_addr1, 100],
|
|
base_rpc_port=BTC_BASE_RPC_PORT,
|
|
)
|
|
|
|
# Switch addresses so wallet amounts stay constant
|
|
num_blocks = 100
|
|
cls.btc_addr = (
|
|
cls.swap_clients[0]
|
|
.ci(Coins.BTC)
|
|
.pubkey_to_segwit_address(void_block_rewards_pubkey)
|
|
)
|
|
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
|
|
callnoderpc(
|
|
0,
|
|
"generatetoaddress",
|
|
[num_blocks, cls.btc_addr],
|
|
base_rpc_port=BTC_BASE_RPC_PORT,
|
|
)
|
|
|
|
major_version = int(
|
|
str(
|
|
callnoderpc(
|
|
0, "getnetworkinfo", base_rpc_port=BTC_BASE_RPC_PORT
|
|
)["version"]
|
|
)[:2]
|
|
)
|
|
if major_version >= 23:
|
|
checkForks(
|
|
callnoderpc(
|
|
0, "getdeploymentinfo", base_rpc_port=BTC_BASE_RPC_PORT
|
|
)
|
|
)
|
|
else:
|
|
checkForks(
|
|
callnoderpc(
|
|
0, "getblockchaininfo", base_rpc_port=BTC_BASE_RPC_PORT
|
|
)
|
|
)
|
|
|
|
if cls.start_ltc_nodes:
|
|
num_blocks = 400
|
|
cls.ltc_addr = callnoderpc(
|
|
0,
|
|
"getnewaddress",
|
|
["mining_addr", "bech32"],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
logging.info(
|
|
"Mining %d Litecoin blocks to %s", num_blocks, cls.ltc_addr
|
|
)
|
|
callnoderpc(
|
|
0,
|
|
"generatetoaddress",
|
|
[num_blocks, cls.ltc_addr],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
|
|
num_blocks = 31
|
|
cls.ltc_addr = (
|
|
cls.swap_clients[0]
|
|
.ci(Coins.LTC)
|
|
.pubkey_to_address(void_block_rewards_pubkey)
|
|
)
|
|
logging.info(
|
|
"Mining %d Litecoin blocks to %s", num_blocks, cls.ltc_addr
|
|
)
|
|
callnoderpc(
|
|
0,
|
|
"generatetoaddress",
|
|
[num_blocks, cls.ltc_addr],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
|
|
# https://github.com/litecoin-project/litecoin/issues/807
|
|
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
|
|
mweb_addr = callnoderpc(
|
|
2,
|
|
"getnewaddress",
|
|
["mweb_addr", "mweb"],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
callnoderpc(
|
|
0,
|
|
"sendtoaddress",
|
|
[mweb_addr, 1],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
|
|
ltc_addr1 = callnoderpc(
|
|
1,
|
|
"getnewaddress",
|
|
["initial addr"],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
for i in range(5):
|
|
callnoderpc(
|
|
0,
|
|
"sendtoaddress",
|
|
[ltc_addr1, 100],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
|
|
num_blocks = 69
|
|
cls.ltc_addr = (
|
|
cls.swap_clients[0]
|
|
.ci(Coins.LTC)
|
|
.pubkey_to_address(void_block_rewards_pubkey)
|
|
)
|
|
callnoderpc(
|
|
0,
|
|
"generatetoaddress",
|
|
[num_blocks, cls.ltc_addr],
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
|
|
checkForks(
|
|
callnoderpc(
|
|
0,
|
|
"getblockchaininfo",
|
|
base_rpc_port=LTC_BASE_RPC_PORT,
|
|
wallet="wallet.dat",
|
|
)
|
|
)
|
|
|
|
num_blocks = 100
|
|
if cls.start_xmr_nodes:
|
|
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, "get_address")[
|
|
"address"
|
|
]
|
|
if (
|
|
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count")["count"]
|
|
< num_blocks
|
|
):
|
|
logging.info(
|
|
"Mining %d Monero blocks to %s.", num_blocks, cls.xmr_addr
|
|
)
|
|
callrpc_xmr(
|
|
XMR_BASE_RPC_PORT + 1,
|
|
"generateblocks",
|
|
{
|
|
"wallet_address": cls.xmr_addr,
|
|
"amount_of_blocks": num_blocks,
|
|
},
|
|
)
|
|
logging.info(
|
|
"XMR blocks: %d",
|
|
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count")["count"],
|
|
)
|
|
|
|
logging.info("Adding anon outputs")
|
|
outputs = []
|
|
for i in range(8):
|
|
sx_addr = callnoderpc(1, "getnewstealthaddress")
|
|
outputs.append({"address": sx_addr, "amount": 0.5})
|
|
for i in range(7):
|
|
callnoderpc(0, "sendtypeto", ["part", "anon", outputs])
|
|
|
|
part_addr1 = callnoderpc(1, "getnewaddress", ["initial addr"])
|
|
part_addr2 = callnoderpc(1, "getnewaddress", ["initial addr 2"])
|
|
callnoderpc(
|
|
0,
|
|
"sendtypeto",
|
|
[
|
|
"part",
|
|
"part",
|
|
[
|
|
{"address": part_addr1, "amount": 100},
|
|
{"address": part_addr2, "amount": 100},
|
|
],
|
|
],
|
|
)
|
|
|
|
cls.prepareExtraCoins()
|
|
|
|
logging.info("Starting update thread.")
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
|
|
cls.update_thread.start()
|
|
|
|
pause_event.set()
|
|
cls.coins_update_thread = threading.Thread(
|
|
target=run_coins_loop, args=(cls,)
|
|
)
|
|
cls.coins_update_thread.start()
|
|
|
|
except Exception:
|
|
traceback.print_exc()
|
|
cls.tearDownClass()
|
|
raise ValueError("setUpClass() failed.")
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
logging.info("Finalising")
|
|
test_delay_event.set()
|
|
if cls.update_thread is not None:
|
|
try:
|
|
cls.update_thread.join()
|
|
except Exception:
|
|
logging.info("Failed to join update_thread")
|
|
if cls.coins_update_thread is not None:
|
|
try:
|
|
cls.coins_update_thread.join()
|
|
except Exception:
|
|
logging.info("Failed to join coins_update_thread")
|
|
for t in cls.http_threads:
|
|
t.stop()
|
|
t.join()
|
|
logging.info("Stopping swap clients")
|
|
for c in cls.swap_clients:
|
|
c.finalise()
|
|
c.fp.close()
|
|
|
|
logging.info("Stopping coin nodes")
|
|
stopDaemons(cls.xmr_daemons)
|
|
stopDaemons(cls.part_daemons)
|
|
stopDaemons(cls.btc_daemons)
|
|
stopDaemons(cls.ltc_daemons)
|
|
|
|
cls.http_threads.clear()
|
|
cls.swap_clients.clear()
|
|
cls.part_daemons.clear()
|
|
cls.btc_daemons.clear()
|
|
cls.ltc_daemons.clear()
|
|
cls.xmr_daemons.clear()
|
|
|
|
super(BaseTest, cls).tearDownClass()
|
|
|
|
@classmethod
|
|
def addCoinSettings(cls, settings, datadir, node_id):
|
|
pass
|
|
|
|
@classmethod
|
|
def prepareExtraDataDir(cls, i):
|
|
pass
|
|
|
|
@classmethod
|
|
def addPIDInfo(cls, sc, i):
|
|
pass
|
|
|
|
@classmethod
|
|
def prepareExtraCoins(cls):
|
|
pass
|
|
|
|
@classmethod
|
|
def coins_loop(cls):
|
|
if cls.btc_addr is not None:
|
|
btcCli("generatetoaddress 1 {}".format(cls.btc_addr))
|
|
if cls.ltc_addr is not None:
|
|
ltcCli("generatetoaddress 1 {}".format(cls.ltc_addr))
|
|
if cls.xmr_addr is not None:
|
|
callrpc_xmr(
|
|
XMR_BASE_RPC_PORT + 1,
|
|
"generateblocks",
|
|
{"wallet_address": cls.xmr_addr, "amount_of_blocks": 1},
|
|
)
|
|
|
|
@classmethod
|
|
def waitForParticlHeight(cls, num_blocks, node_id=0):
|
|
logging.info(
|
|
f"Waiting for Particl chain height {num_blocks}",
|
|
)
|
|
for i in range(60):
|
|
if test_delay_event.is_set():
|
|
raise ValueError("Test stopped.")
|
|
particl_blocks = callnoderpc(0, "getblockcount")
|
|
print("particl_blocks", particl_blocks)
|
|
if particl_blocks >= num_blocks:
|
|
break
|
|
test_delay_event.wait(1)
|
|
logging.info("PART blocks: %d", callnoderpc(0, "getblockcount"))
|
|
assert particl_blocks >= num_blocks
|
|
|
|
def callxmrnodewallet(self, node_id, method, params=None):
|
|
return callrpc_xmr(
|
|
XMR_BASE_WALLET_RPC_PORT + node_id,
|
|
method,
|
|
params,
|
|
auth=self.xmr_wallet_auth[node_id],
|
|
)
|
|
|
|
def getXmrBalance(self, js_wallets):
|
|
return float(js_wallets[Coins.XMR.name]["unconfirmed"]) + float(
|
|
js_wallets[Coins.XMR.name]["balance"]
|
|
)
|
|
|
|
def prepare_balance(
|
|
self,
|
|
coin,
|
|
amount: float,
|
|
port_target_node: int,
|
|
port_take_from_node: int,
|
|
test_balance: bool = True,
|
|
) -> None:
|
|
delay_iterations = 100 if coin == Coins.NAV else 20
|
|
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,
|
|
"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,
|
|
)
|
|
|
|
|
|
class Test(BaseTest):
|
|
__test__ = True
|
|
|
|
def notest_00_delay(self):
|
|
test_delay_event.wait(100000)
|
|
|
|
def test_003_api(self):
|
|
logging.info("---------- Test API")
|
|
|
|
help_output = read_json_api(1800, "help")
|
|
assert "getcoinseed" in help_output["commands"]
|
|
|
|
rv = read_json_api(1800, "getcoinseed")
|
|
assert rv["error"] == "No post data"
|
|
|
|
rv = read_json_api(1800, "getcoinseed", {"coin": "PART"})
|
|
assert "seed is set from the Basicswap mnemonic" in rv["error"]
|
|
|
|
rv = read_json_api(1800, "getcoinseed", {"coin": "BTC"})
|
|
assert (
|
|
rv["seed"]
|
|
== "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
|
)
|
|
assert rv["seed_id"] == "3da5c0af91879e8ce97d9a843874601c08688078"
|
|
assert rv["seed_id"] == rv["expected_seed_id"]
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
|
|
{"set_label": "test 1"},
|
|
)
|
|
assert isinstance(rv, dict)
|
|
assert rv["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
|
|
assert rv["label"] == "test 1"
|
|
rv = read_json_api(
|
|
1800,
|
|
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
|
|
{"set_label": "test 2"},
|
|
)
|
|
assert isinstance(rv, dict)
|
|
assert rv["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
|
|
assert rv["label"] == "test 2"
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"identities/pPCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
|
|
{"set_label": "test 3"},
|
|
)
|
|
assert rv["error"] == "Invalid identity address"
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
|
|
{"set_note": "note 1"},
|
|
)
|
|
assert isinstance(rv, dict)
|
|
assert rv["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
|
|
assert rv["label"] == "test 2"
|
|
assert rv["note"] == "note 1"
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
|
|
{"set_automation_override": 1},
|
|
)
|
|
assert isinstance(rv, dict)
|
|
assert rv["automation_override"] == 1
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
|
|
{"set_visibility_override": "hide"},
|
|
)
|
|
assert isinstance(rv, dict)
|
|
assert rv["visibility_override"] == 1
|
|
|
|
rv = read_json_api(1800, "automationstrategies")
|
|
assert len(rv) == 2
|
|
|
|
rv = read_json_api(1800, "automationstrategies/1")
|
|
assert rv["label"] == "Accept All"
|
|
|
|
sx_addr = read_json_api(1800, "wallets/part/newstealthaddress")
|
|
assert (
|
|
callnoderpc(
|
|
0,
|
|
"getaddressinfo",
|
|
[
|
|
sx_addr,
|
|
],
|
|
)["isstealthaddress"]
|
|
is True
|
|
)
|
|
|
|
rv = read_json_api(1800, "wallets/part")
|
|
assert "locked_utxos" in rv
|
|
|
|
rv = read_json_api(
|
|
1800, "validateamount", {"coin": "part", "amount": 0.000000015}
|
|
)
|
|
assert "Mantissa too long" in rv["error"]
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"validateamount",
|
|
{"coin": "part", "amount": 0.000000015, "method": "roundoff"},
|
|
)
|
|
assert rv == "0.00000002"
|
|
|
|
rv = read_json_api(
|
|
1800,
|
|
"validateamount",
|
|
{"coin": "part", "amount": 0.000000015, "method": "rounddown"},
|
|
)
|
|
assert rv == "0.00000001"
|
|
|
|
def test_010_txn_size(self):
|
|
logging.info("---------- Test {} txn_size".format(Coins.PART))
|
|
|
|
swap_clients = self.swap_clients
|
|
ci = swap_clients[0].ci(Coins.PART)
|
|
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
|
|
|
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
|
|
|
# Record unspents before createSCLockTx as the used ones will be locked
|
|
unspents = ci.rpc("listunspent")
|
|
|
|
# fee_rate is in sats/kvB
|
|
fee_rate: int = 1000
|
|
|
|
a = ci.getNewRandomKey()
|
|
b = ci.getNewRandomKey()
|
|
|
|
A = ci.getPubkey(a)
|
|
B = ci.getPubkey(b)
|
|
lock_tx_script = pi.genScriptLockTxScript(ci, A, B)
|
|
|
|
lock_tx = ci.createSCLockTx(amount, lock_tx_script)
|
|
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate)
|
|
lock_tx = ci.signTxWithWallet(lock_tx)
|
|
|
|
unspents_after = ci.rpc("listunspent")
|
|
assert len(unspents) > len(unspents_after)
|
|
|
|
tx_decoded = ci.rpc("decoderawtransaction", [lock_tx.hex()])
|
|
txid = tx_decoded["txid"]
|
|
|
|
vsize = tx_decoded["vsize"]
|
|
expect_fee_int = round(fee_rate * vsize / 1000)
|
|
|
|
out_value: int = 0
|
|
for txo in tx_decoded["vout"]:
|
|
if "value" in txo:
|
|
out_value += ci.make_int(txo["value"])
|
|
in_value: int = 0
|
|
for txi in tx_decoded["vin"]:
|
|
for utxo in unspents:
|
|
if "vout" not in utxo:
|
|
continue
|
|
if utxo["txid"] == txi["txid"] and utxo["vout"] == txi["vout"]:
|
|
in_value += ci.make_int(utxo["amount"])
|
|
break
|
|
fee_value = in_value - out_value
|
|
|
|
ci.rpc("sendrawtransaction", [lock_tx.hex()])
|
|
rv = ci.rpc("gettransaction", [txid])
|
|
wallet_tx_fee = -ci.make_int(rv["fee"])
|
|
|
|
assert wallet_tx_fee == fee_value
|
|
assert wallet_tx_fee == expect_fee_int
|
|
|
|
addr_out = ci.getNewAddress(True)
|
|
pkh_out = ci.decodeAddress(addr_out)
|
|
fee_info = {}
|
|
lock_spend_tx = ci.createSCLockSpendTx(
|
|
lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info
|
|
)
|
|
vsize_estimated: int = fee_info["vsize"]
|
|
|
|
tx_decoded = ci.rpc("decoderawtransaction", [lock_spend_tx.hex()])
|
|
txid = tx_decoded["txid"]
|
|
|
|
witness_stack = [
|
|
b"",
|
|
ci.signTx(a, lock_spend_tx, 0, lock_tx_script, amount),
|
|
ci.signTx(b, lock_spend_tx, 0, lock_tx_script, amount),
|
|
lock_tx_script,
|
|
]
|
|
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack)
|
|
tx_decoded = ci.rpc("decoderawtransaction", [lock_spend_tx.hex()])
|
|
vsize_actual: int = tx_decoded["vsize"]
|
|
|
|
assert vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4
|
|
assert ci.rpc("sendrawtransaction", [lock_spend_tx.hex()]) == txid
|
|
|
|
expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
|
|
assert expect_vsize >= vsize_actual
|
|
assert expect_vsize - vsize_actual < 10
|
|
|
|
# Test chain b (no-script) lock tx size
|
|
v = ci.getNewRandomKey()
|
|
s = ci.getNewRandomKey()
|
|
S = ci.getPubkey(s)
|
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
|
|
|
addr_out = ci.getNewAddress(True)
|
|
lock_tx_b_spend_txid = ci.spendBLockTx(
|
|
lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0
|
|
)
|
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
|
if lock_tx_b_spend is None:
|
|
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
|
|
lock_tx_b_spend_decoded = ci.rpc(
|
|
"decoderawtransaction", [lock_tx_b_spend.hex()]
|
|
)
|
|
|
|
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
|
assert expect_vsize >= lock_tx_b_spend_decoded["vsize"]
|
|
assert expect_vsize - lock_tx_b_spend_decoded["vsize"] < 10
|
|
|
|
def test_010_xmr_txn_size(self):
|
|
logging.info("---------- Test {} txn_size".format(Coins.XMR))
|
|
|
|
swap_clients = self.swap_clients
|
|
ci = swap_clients[1].ci(Coins.XMR)
|
|
|
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
|
fee_rate: int = 1000 # TODO: How to set feerate for rpc functions?
|
|
|
|
v = ci.getNewRandomKey()
|
|
s = ci.getNewRandomKey()
|
|
S = ci.getPubkey(s)
|
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
|
|
|
addr_out = ci.getNewAddress(True)
|
|
for i in range(20):
|
|
try:
|
|
lock_tx_b_spend_txid = ci.spendBLockTx(
|
|
lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0
|
|
)
|
|
break
|
|
except Exception as e:
|
|
if isinstance(e, TemporaryError):
|
|
continue
|
|
else:
|
|
raise (e)
|
|
test_delay_event.wait(1)
|
|
|
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
|
|
|
actual_size: int = len(lock_tx_b_spend["txs_as_hex"][0]) // 2
|
|
expect_size: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
|
assert expect_size >= actual_size
|
|
assert expect_size - actual_size < 100 # TODO
|
|
|
|
def test_011_smsgaddresses(self):
|
|
logging.info("---------- Test address management and private offers")
|
|
swap_clients = self.swap_clients
|
|
js_1 = read_json_api(1801, "smsgaddresses")
|
|
|
|
post_json = {
|
|
"addressnote": "testing",
|
|
}
|
|
json_rv = read_json_api(1801, "smsgaddresses/new", post_json)
|
|
new_address = json_rv["new_address"]
|
|
new_address_pk = json_rv["pubkey"]
|
|
|
|
js_2 = read_json_api(1801, "smsgaddresses")
|
|
assert len(js_2) == len(js_1) + 1
|
|
found = False
|
|
for addr in js_2:
|
|
if addr["addr"] == new_address:
|
|
assert addr["note"] == "testing"
|
|
found = True
|
|
assert found is True
|
|
|
|
found = False
|
|
lks = callnoderpc(1, "smsglocalkeys")
|
|
for key in lks["wallet_keys"]:
|
|
if key["address"] == new_address:
|
|
assert key["receive"] == "1"
|
|
found = True
|
|
assert found is True
|
|
|
|
# Disable
|
|
post_json = {
|
|
"address": new_address,
|
|
"addressnote": "testing2",
|
|
"active_ind": "0",
|
|
}
|
|
json_rv = read_json_api(1801, "smsgaddresses/edit", post_json)
|
|
assert json_rv["edited_address"] == new_address
|
|
|
|
js_3 = read_json_api(1801, "smsgaddresses")
|
|
assert len(js_3) == 0
|
|
|
|
post_json = {
|
|
"exclude_inactive": False,
|
|
}
|
|
js_3 = read_json_api(1801, "smsgaddresses", post_json)
|
|
found = False
|
|
for addr in js_3:
|
|
if addr["addr"] == new_address:
|
|
assert addr["note"] == "testing2"
|
|
assert addr["active_ind"] == 0
|
|
found = True
|
|
assert found is True
|
|
|
|
found = False
|
|
lks = callnoderpc(1, "smsglocalkeys")
|
|
for key in lks["wallet_keys"]:
|
|
if key["address"] == new_address:
|
|
found = True
|
|
assert found is False
|
|
|
|
# Re-enable
|
|
post_json = {
|
|
"address": new_address,
|
|
"active_ind": "1",
|
|
}
|
|
json_rv = read_json_api(1801, "smsgaddresses/edit", post_json)
|
|
assert json_rv["edited_address"] == new_address
|
|
|
|
found = False
|
|
lks = callnoderpc(1, "smsglocalkeys")
|
|
for key in lks["wallet_keys"]:
|
|
if key["address"] == new_address:
|
|
assert key["receive"] == "1"
|
|
found = True
|
|
assert found is True
|
|
|
|
post_json = {
|
|
"addresspubkey": new_address_pk,
|
|
"addressnote": "testing_add_addr",
|
|
}
|
|
json_rv = read_json_api(1800, "smsgaddresses/add", post_json)
|
|
assert json_rv["added_address"] == new_address
|
|
|
|
post_json = {
|
|
"addr_to": new_address,
|
|
"addr_from": -1,
|
|
"coin_from": 1,
|
|
"coin_to": 6,
|
|
"amt_from": 1,
|
|
"amt_to": 1,
|
|
"lockhrs": 24,
|
|
}
|
|
rv = read_json_api(1800, "offers/new", post_json)
|
|
offer_id_hex = rv["offer_id"]
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], bytes.fromhex(offer_id_hex))
|
|
|
|
rv = read_json_api(1801, f"offers/{offer_id_hex}")
|
|
assert rv[0]["addr_to"] == new_address
|
|
|
|
rv = read_json_api(1800, f"offers/{offer_id_hex}")
|
|
assert rv[0]["addr_to"] == new_address
|
|
|
|
# Disable all
|
|
json_rv = read_json_api(1800, "smsgaddresses/disableall")
|
|
assert json_rv["num_disabled"] >= 1
|
|
|
|
def test_01_part_xmr(self):
|
|
logging.info("---------- Test PART to XMR")
|
|
swap_clients = self.swap_clients
|
|
|
|
start_xmr_amount = self.getXmrBalance(read_json_api(1800, "wallets"))
|
|
js_1 = read_json_api(1801, "wallets")
|
|
assert self.getXmrBalance(js_1) > 0.0
|
|
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
100 * COIN,
|
|
0.11 * XMR_COIN,
|
|
100 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offers = swap_clients[1].listOffers(filters={"offer_id": offer_id})
|
|
assert len(offers) == 1
|
|
offer = offers[0]
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
js_0_end = read_json_api(1800, "wallets")
|
|
end_xmr_amount = self.getXmrBalance(js_0_end)
|
|
xmr_amount_diff = end_xmr_amount - start_xmr_amount
|
|
assert xmr_amount_diff > 10.9 and xmr_amount_diff < 11.0
|
|
|
|
bid_id_hex = bid_id.hex()
|
|
path = f"bids/{bid_id_hex}/states"
|
|
offerer_states = read_json_api(1800, path)
|
|
bidder_states = read_json_api(1801, path)
|
|
|
|
assert compare_bid_states(offerer_states, self.states_offerer[0]) is True
|
|
assert compare_bid_states(bidder_states, self.states_bidder[0]) is True
|
|
|
|
# Test remove_expired_data
|
|
remove_expired_data(
|
|
swap_clients[0], -swap_clients[0]._expire_db_records_after * 2
|
|
)
|
|
offers = swap_clients[0].listOffers(filters={"offer_id": offer_id})
|
|
assert len(offers) == 0
|
|
|
|
def test_02_leader_recover_a_lock_tx(self):
|
|
logging.info("---------- Test PART to XMR leader recovers coin a lock tx")
|
|
swap_clients = self.swap_clients
|
|
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
101 * COIN,
|
|
0.12 * XMR_COIN,
|
|
101 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS,
|
|
lock_value=12,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_REFUNDED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
[BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED],
|
|
sent=True,
|
|
)
|
|
|
|
bid_id_hex = bid_id.hex()
|
|
path = f"bids/{bid_id_hex}/states"
|
|
offerer_states = read_json_api(1800, path)
|
|
|
|
assert compare_bid_states(offerer_states, self.states_offerer[1]) is True
|
|
|
|
def test_03_follower_recover_a_lock_tx(self):
|
|
logging.info("---------- Test PART to XMR follower recovers coin a lock tx")
|
|
swap_clients = self.swap_clients
|
|
|
|
swap_clients[1].ci(Coins.PART)._altruistic = False
|
|
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
101 * COIN,
|
|
0.13 * XMR_COIN,
|
|
101 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS,
|
|
lock_value=16,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
|
swap_clients[0].setBidDebugInd(
|
|
bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND
|
|
)
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_STALLED_FOR_TEST,
|
|
wait_for=220,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_SWIPED,
|
|
wait_for=80,
|
|
sent=True,
|
|
)
|
|
|
|
wait_for_none_active(test_delay_event, 1800)
|
|
wait_for_none_active(test_delay_event, 1801)
|
|
|
|
bid_id_hex = bid_id.hex()
|
|
path = f"bids/{bid_id_hex}/states"
|
|
bidder_states = read_json_api(1801, path)
|
|
|
|
bidder_states = [s for s in bidder_states if s[1] != "Bid Stalled (debug)"]
|
|
assert compare_bid_states(bidder_states, self.states_bidder[2]) is True
|
|
|
|
def test_03b_follower_recover_a_lock_tx_with_mercy(self):
|
|
logging.info(
|
|
"---------- Test PART to XMR follower recovers coin a lock tx with mercy output"
|
|
)
|
|
swap_clients = self.swap_clients
|
|
|
|
swap_clients[1].ci(Coins.PART)._altruistic = True
|
|
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
101 * COIN,
|
|
0.13 * XMR_COIN,
|
|
101 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS,
|
|
lock_value=16,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_B_LOCK)
|
|
swap_clients[0].setBidDebugInd(
|
|
bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2
|
|
)
|
|
|
|
swap_clients[0].setBidDebugInd(
|
|
bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, False
|
|
)
|
|
swap_clients[1].setBidDebugInd(
|
|
bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, False
|
|
)
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
(BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED, BidStates.SWAP_COMPLETED),
|
|
wait_for=220,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_SWIPED,
|
|
wait_for=120,
|
|
sent=True,
|
|
)
|
|
|
|
wait_for_none_active(test_delay_event, 1800)
|
|
wait_for_none_active(test_delay_event, 1801)
|
|
|
|
def test_04_follower_recover_b_lock_tx(self):
|
|
logging.info("---------- Test PART to XMR follower recovers coin b lock tx")
|
|
|
|
swap_clients = self.swap_clients
|
|
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
101 * COIN,
|
|
0.14 * XMR_COIN,
|
|
101 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS,
|
|
lock_value=28,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_REFUNDED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_REFUNDED,
|
|
sent=True,
|
|
)
|
|
|
|
bid_id_hex = bid_id.hex()
|
|
path = f"bids/{bid_id_hex}/states"
|
|
offerer_states = read_json_api(1800, path)
|
|
bidder_states = read_json_api(1801, path)
|
|
|
|
assert compare_bid_states(offerer_states, self.states_offerer[1]) is True
|
|
assert compare_bid_states(bidder_states, self.states_bidder[1]) is True
|
|
|
|
def test_05_btc_xmr(self):
|
|
logging.info("---------- Test BTC to XMR")
|
|
swap_clients = self.swap_clients
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.BTC,
|
|
Coins.XMR,
|
|
10 * COIN,
|
|
100 * XMR_COIN,
|
|
10 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offers = swap_clients[1].listOffers(filters={"offer_id": offer_id})
|
|
offer = offers[0]
|
|
|
|
swap_clients[1].ci(Coins.XMR).setFeePriority(3)
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
swap_clients[1].ci(Coins.XMR).setFeePriority(0)
|
|
|
|
def test_06_multiple_swaps(self):
|
|
logging.info("---------- Test Multiple concurrent swaps")
|
|
swap_clients = self.swap_clients
|
|
|
|
js_w0_before = read_json_api(1800, "wallets")
|
|
js_w1_before = read_json_api(1801, "wallets")
|
|
|
|
amt_1 = make_int(random.uniform(0.001, 19.0), scale=8, r=1)
|
|
amt_2 = make_int(random.uniform(0.001, 49.0), scale=8, r=1)
|
|
|
|
rate_1 = make_int(random.uniform(80.0, 110.0), scale=12, r=1)
|
|
rate_2 = make_int(random.uniform(0.01, 0.5), scale=12, r=1)
|
|
|
|
logging.info("amt_1 {}, rate_1 {}".format(amt_1, rate_1))
|
|
logging.info("amt_2 {}, rate_2 {}".format(amt_2, rate_2))
|
|
offer1_id = swap_clients[0].postOffer(
|
|
Coins.BTC, Coins.XMR, amt_1, rate_1, amt_1, SwapTypes.XMR_SWAP
|
|
)
|
|
offer2_id = swap_clients[0].postOffer(
|
|
Coins.PART, Coins.XMR, amt_2, rate_2, amt_2, SwapTypes.XMR_SWAP
|
|
)
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer1_id)
|
|
offer1 = swap_clients[1].getOffer(offer1_id)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer2_id)
|
|
offer2 = swap_clients[1].getOffer(offer2_id)
|
|
|
|
bid1_id = swap_clients[1].postXmrBid(offer1_id, offer1.amount_from)
|
|
bid2_id = swap_clients[1].postXmrBid(offer2_id, offer2.amount_from)
|
|
|
|
offer3_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
11 * COIN,
|
|
0.15 * XMR_COIN,
|
|
11 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid1_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[0].acceptXmrBid(bid1_id)
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer3_id)
|
|
offer3 = swap_clients[1].getOffer(offer3_id)
|
|
bid3_id = swap_clients[1].postXmrBid(offer3_id, offer3.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid2_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[0].acceptXmrBid(bid2_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid3_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[0].acceptXmrBid(bid3_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid1_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid1_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid2_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=120,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid2_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid3_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=120,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid3_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
wait_for_none_active(test_delay_event, 1800)
|
|
wait_for_none_active(test_delay_event, 1801)
|
|
|
|
js_w0_after = read_json_api(1800, "wallets")
|
|
js_w1_after = read_json_api(1801, "wallets")
|
|
assert (
|
|
make_int(js_w1_after["BTC"]["balance"], scale=8, r=1)
|
|
- (make_int(js_w1_before["BTC"]["balance"], scale=8, r=1) + amt_1)
|
|
< 1000
|
|
)
|
|
logging.debug(
|
|
"node0 PART difference: {}".format(
|
|
make_int(js_w0_after["PART"]["balance"], scale=8, r=1)
|
|
- (make_int(js_w0_before["PART"]["balance"], scale=8, r=1) + amt_1)
|
|
)
|
|
) # TODO: exclude staking
|
|
|
|
def test_07_revoke_offer(self):
|
|
logging.info("---------- Test offer revocaction")
|
|
swap_clients = self.swap_clients
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.BTC,
|
|
Coins.XMR,
|
|
10 * COIN,
|
|
100 * XMR_COIN,
|
|
10 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
|
|
swap_clients[0].revokeOffer(offer_id)
|
|
|
|
wait_for_no_offer(test_delay_event, swap_clients[1], offer_id)
|
|
|
|
def test_08_withdraw(self):
|
|
logging.info("---------- Test XMR withdrawals")
|
|
|
|
js_0 = read_json_api(1800, "wallets")
|
|
address_to = js_0[Coins.XMR.name]["deposit_address"]
|
|
|
|
js_1 = read_json_api(1801, "wallets")
|
|
assert float(js_1[Coins.XMR.name]["balance"]) > 0.0
|
|
|
|
post_json = {
|
|
"value": 1.1,
|
|
"address": address_to,
|
|
"sweepall": False,
|
|
}
|
|
rv = read_json_api(1801, "wallets/xmr/withdraw", post_json)
|
|
assert len(rv["txid"]) == 64
|
|
|
|
def test_09_auto_accept(self):
|
|
logging.info("---------- Test BTC to XMR auto accept")
|
|
swap_clients = self.swap_clients
|
|
amt_swap = make_int(random.uniform(0.01, 11.0), scale=8, r=1)
|
|
rate_swap = make_int(random.uniform(10.0, 101.0), scale=12, r=1)
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.BTC,
|
|
Coins.XMR,
|
|
amt_swap,
|
|
rate_swap,
|
|
amt_swap,
|
|
SwapTypes.XMR_SWAP,
|
|
auto_accept_bids=True,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].listOffers(filters={"offer_id": offer_id})[0]
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
def test_09_1_auto_accept_multiple(self):
|
|
logging.info("---------- Test BTC to XMR auto accept multiple bids")
|
|
swap_clients = self.swap_clients
|
|
amt_swap = make_int(10, scale=8, r=1)
|
|
rate_swap = make_int(100, scale=12, r=1)
|
|
min_bid = make_int(1, scale=8, r=1)
|
|
|
|
extra_options = {
|
|
"amount_negotiable": True,
|
|
"automation_id": 1,
|
|
}
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.BTC,
|
|
Coins.XMR,
|
|
amt_swap,
|
|
rate_swap,
|
|
min_bid,
|
|
SwapTypes.XMR_SWAP,
|
|
extra_options=extra_options,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
|
|
below_min_bid = min_bid - 1
|
|
|
|
# Ensure bids below the minimum amount fails on sender and recipient.
|
|
try:
|
|
bid_id = swap_clients[1].postBid(offer_id, below_min_bid)
|
|
except Exception as e:
|
|
assert "Bid amount below minimum" in str(e)
|
|
extra_bid_options = {
|
|
"debug_skip_validation": True,
|
|
}
|
|
bid_id = swap_clients[1].postBid(
|
|
offer_id, below_min_bid, extra_options=extra_bid_options
|
|
)
|
|
|
|
event = wait_for_event(
|
|
test_delay_event, swap_clients[0], Concepts.NETWORK_MESSAGE, bid_id
|
|
)
|
|
assert "Bid amount below minimum" in event.event_msg
|
|
|
|
bid_ids = []
|
|
for i in range(5):
|
|
bid_ids.append(swap_clients[1].postBid(offer_id, min_bid))
|
|
|
|
# Should fail > max concurrent
|
|
test_delay_event.wait(1.0)
|
|
bid_id = swap_clients[1].postBid(offer_id, min_bid)
|
|
logging.info("Waiting for bid {} to fail.".format(bid_id.hex()))
|
|
event = wait_for_event(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
Concepts.BID,
|
|
bid_id,
|
|
event_type=EventLogTypes.AUTOMATION_CONSTRAINT,
|
|
)
|
|
assert "Already have 5 bids to complete" in event.event_msg
|
|
|
|
for bid_id in bid_ids:
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=240,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
amt_bid = make_int(5, scale=8, r=1)
|
|
|
|
# Should fail > total value
|
|
amt_bid += 1
|
|
bid_id = swap_clients[1].postBid(offer_id, amt_bid)
|
|
event = wait_for_event(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
Concepts.BID,
|
|
bid_id,
|
|
event_type=EventLogTypes.AUTOMATION_CONSTRAINT,
|
|
)
|
|
assert "Over remaining offer value" in event.event_msg
|
|
|
|
# Should pass
|
|
amt_bid -= 1
|
|
bid_id = swap_clients[1].postBid(offer_id, amt_bid)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
def test_10_locked_refundtx(self):
|
|
logging.info("---------- Test Refund tx is locked")
|
|
swap_clients = self.swap_clients
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.BTC,
|
|
Coins.XMR,
|
|
10 * COIN,
|
|
100 * XMR_COIN,
|
|
10 * COIN,
|
|
SwapTypes.XMR_SWAP,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offers = swap_clients[1].listOffers(filters={"offer_id": offer_id})
|
|
offer = offers[0]
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED,
|
|
wait_for=180,
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
|
|
try:
|
|
swap_clients[0].ci(Coins.BTC).publishTx(xmr_swap.a_lock_refund_tx)
|
|
assert False, "Lock refund tx should be locked"
|
|
except Exception as e:
|
|
assert "non-BIP68-final" in str(e)
|
|
|
|
def test_11_particl_anon(self):
|
|
logging.info("---------- Test Particl anon transactions")
|
|
swap_clients = self.swap_clients
|
|
|
|
js_0 = read_json_api(1800, "wallets/part")
|
|
assert float(js_0["anon_balance"]) == 0.0
|
|
node0_anon_before = js_0["anon_balance"] + js_0["anon_pending"]
|
|
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1801/json/wallets/part",
|
|
"balance",
|
|
200.0,
|
|
)
|
|
js_1 = read_json_api(1801, "wallets/part")
|
|
assert float(js_1["balance"]) > 200.0
|
|
node1_anon_before = js_1["anon_balance"] + js_1["anon_pending"]
|
|
|
|
callnoderpc(
|
|
1, "reservebalance", [True, 1000000]
|
|
) # Stop staking to avoid conflicts (input used by tx->anon staked before tx gets in the chain)
|
|
post_json = {
|
|
"value": 100,
|
|
"address": js_1["stealth_address"],
|
|
"subfee": False,
|
|
"type_to": "anon",
|
|
}
|
|
json_rv = json.loads(
|
|
post_json_req("http://127.0.0.1:1801/json/wallets/part/withdraw", post_json)
|
|
)
|
|
assert len(json_rv["txid"]) == 64
|
|
|
|
logging.info("Waiting for anon balance")
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1801/json/wallets/part",
|
|
"anon_balance",
|
|
100.0 + node1_anon_before,
|
|
)
|
|
js_1 = read_json_api(1801, "wallets/part")
|
|
node1_anon_before = js_1["anon_balance"] + js_1["anon_pending"]
|
|
|
|
callnoderpc(1, "reservebalance", [False])
|
|
post_json = {
|
|
"value": 10,
|
|
"address": js_0["stealth_address"],
|
|
"subfee": True,
|
|
"type_from": "anon",
|
|
"type_to": "blind",
|
|
}
|
|
json_rv = json.loads(
|
|
post_json_req("http://127.0.0.1:1801/json/wallets/part/withdraw", post_json)
|
|
)
|
|
assert len(json_rv["txid"]) == 64
|
|
|
|
logging.info("Waiting for blind balance")
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1800/json/wallets/part",
|
|
"blind_balance",
|
|
9.8,
|
|
)
|
|
if float(js_0["blind_balance"]) >= 10.0:
|
|
raise ValueError("Expect blind balance < 10")
|
|
|
|
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
|
rate_swap = make_int(random.uniform(2.0, 20.0), scale=8, r=1)
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.BTC,
|
|
Coins.PART_ANON,
|
|
amt_swap,
|
|
rate_swap,
|
|
amt_swap,
|
|
SwapTypes.XMR_SWAP,
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offers = swap_clients[0].listOffers(filters={"offer_id": offer_id})
|
|
offer = offers[0]
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
|
assert xmr_swap
|
|
amount_to = float(format_amount(bid.amount_to, 8))
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
js_1 = read_json_api(1801, "wallets/part")
|
|
assert js_1["anon_balance"] < node1_anon_before - amount_to
|
|
|
|
js_0 = read_json_api(1800, "wallets/part")
|
|
assert js_0["anon_balance"] + js_0["anon_pending"] > node0_anon_before + (
|
|
amount_to - 0.05
|
|
)
|
|
|
|
# Test chain b (no-script) lock tx size
|
|
ci = swap_clients[1].ci(Coins.PART_ANON)
|
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
|
fee_rate: int = 1000
|
|
v = ci.getNewRandomKey()
|
|
s = ci.getNewRandomKey()
|
|
S = ci.getPubkey(s)
|
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
|
|
|
addr_out = ci.getNewStealthAddress()
|
|
lock_tx_b_spend_txid = None
|
|
for i in range(20):
|
|
try:
|
|
lock_tx_b_spend_txid = ci.spendBLockTx(
|
|
lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0
|
|
)
|
|
break
|
|
except Exception as e:
|
|
print("spendBLockTx failed", str(e))
|
|
test_delay_event.wait(2)
|
|
assert lock_tx_b_spend_txid is not None
|
|
|
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
|
if lock_tx_b_spend is None:
|
|
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
|
|
lock_tx_b_spend_decoded = ci.rpc(
|
|
"decoderawtransaction", [lock_tx_b_spend.hex()]
|
|
)
|
|
|
|
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
|
assert expect_vsize >= lock_tx_b_spend_decoded["vsize"]
|
|
assert expect_vsize - lock_tx_b_spend_decoded["vsize"] < 10
|
|
|
|
def test_12_particl_blind(self):
|
|
logging.info("---------- Test Particl blind transactions")
|
|
swap_clients = self.swap_clients
|
|
|
|
js_0 = read_json_api(1800, "wallets/part")
|
|
node0_blind_before = js_0["blind_balance"] + js_0["blind_unconfirmed"]
|
|
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1801/json/wallets/part",
|
|
"balance",
|
|
200.0,
|
|
)
|
|
js_1 = read_json_api(1801, "wallets/part")
|
|
assert float(js_1["balance"]) > 200.0
|
|
node1_blind_before = js_1["blind_balance"] + js_1["blind_unconfirmed"]
|
|
|
|
post_json = {
|
|
"value": 100,
|
|
"address": js_0["stealth_address"],
|
|
"subfee": False,
|
|
"type_to": "blind",
|
|
}
|
|
json_rv = json.loads(
|
|
post_json_req("http://127.0.0.1:1800/json/wallets/part/withdraw", post_json)
|
|
)
|
|
assert len(json_rv["txid"]) == 64
|
|
|
|
logging.info("Waiting for blind balance")
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1800/json/wallets/part",
|
|
"blind_balance",
|
|
100.0 + node0_blind_before,
|
|
)
|
|
js_0 = read_json_api(1800, "wallets/part")
|
|
node0_blind_before = js_0["blind_balance"] + js_0["blind_unconfirmed"]
|
|
|
|
coin_from = Coins.PART_BLIND
|
|
coin_to = Coins.XMR
|
|
ci_from = swap_clients[0].ci(coin_from)
|
|
ci_to = swap_clients[0].ci(coin_to)
|
|
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
|
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
|
offer_id = swap_clients[0].postOffer(
|
|
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP
|
|
)
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offers = swap_clients[0].listOffers(filters={"offer_id": offer_id})
|
|
offer = offers[0]
|
|
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
amount_from = float(format_amount(amt_swap, 8))
|
|
js_1 = read_json_api(1801, "wallets/part")
|
|
node1_blind_after = js_1["blind_balance"] + js_1["blind_unconfirmed"]
|
|
assert node1_blind_after > node1_blind_before + (amount_from - 0.05)
|
|
|
|
js_0 = read_json_api(1800, "wallets/part")
|
|
node0_blind_after = js_0["blind_balance"] + js_0["blind_unconfirmed"]
|
|
assert node0_blind_after < node0_blind_before - amount_from
|
|
|
|
def test_13_locked_xmr(self):
|
|
logging.info("---------- Test PART to XMR leader recovers coin a lock tx")
|
|
swap_clients = self.swap_clients
|
|
|
|
amt_swap = make_int(random.uniform(0.1, 10.0), scale=8, r=1)
|
|
rate_swap = make_int(random.uniform(2.0, 20.0), scale=12, r=1)
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP
|
|
)
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.SEND_LOCKED_XMR)
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_event(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
Concepts.BID,
|
|
bid_id,
|
|
event_type=EventLogTypes.LOCK_TX_B_INVALID,
|
|
wait_for=180,
|
|
)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED,
|
|
sent=True,
|
|
)
|
|
|
|
swap_clients[0].abandonBid(bid_id)
|
|
swap_clients[1].abandonBid(bid_id)
|
|
|
|
def test_14_sweep_balance(self):
|
|
logging.info("---------- Test sweep balance offer")
|
|
swap_clients = self.swap_clients
|
|
|
|
# Disable staking
|
|
walletsettings = callnoderpc(
|
|
2,
|
|
"walletsettings",
|
|
[
|
|
"stakingoptions",
|
|
],
|
|
)
|
|
walletsettings["enabled"] = False
|
|
walletsettings = callnoderpc(
|
|
2, "walletsettings", ["stakingoptions", walletsettings]
|
|
)
|
|
walletsettings = callnoderpc(
|
|
2,
|
|
"walletsettings",
|
|
[
|
|
"stakingoptions",
|
|
],
|
|
)
|
|
assert walletsettings["stakingoptions"]["enabled"] is False
|
|
|
|
# Prepare balance
|
|
js_w2 = read_json_api(1802, "wallets")
|
|
if float(js_w2["PART"]["balance"]) < 100.0:
|
|
post_json = {
|
|
"value": 100,
|
|
"address": js_w2["PART"]["deposit_address"],
|
|
"subfee": False,
|
|
}
|
|
json_rv = read_json_api(
|
|
TEST_HTTP_PORT + 0, "wallets/part/withdraw", post_json
|
|
)
|
|
assert len(json_rv["txid"]) == 64
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1802/json/wallets/part",
|
|
"balance",
|
|
100.0,
|
|
)
|
|
|
|
js_w2 = read_json_api(1802, "wallets")
|
|
assert float(js_w2["PART"]["balance"]) >= 100.0
|
|
|
|
js_w2 = read_json_api(1802, "wallets")
|
|
post_json = {
|
|
"value": float(js_w2["PART"]["balance"]),
|
|
"address": read_json_api(1802, "wallets/part/nextdepositaddr"),
|
|
"subfee": True,
|
|
}
|
|
json_rv = read_json_api(TEST_HTTP_PORT + 2, "wallets/part/withdraw", post_json)
|
|
wait_for_balance(
|
|
test_delay_event, "http://127.0.0.1:1802/json/wallets/part", "balance", 10.0
|
|
)
|
|
assert len(json_rv["txid"]) == 64
|
|
|
|
# Create prefunded ITX
|
|
ci = swap_clients[2].ci(Coins.PART)
|
|
pi = swap_clients[2].pi(SwapTypes.XMR_SWAP)
|
|
js_w2 = read_json_api(1802, "wallets")
|
|
swap_value = ci.make_int(js_w2["PART"]["balance"])
|
|
|
|
itx = pi.getFundedInitiateTxTemplate(ci, swap_value, True)
|
|
itx_decoded = ci.describeTx(itx.hex())
|
|
n = pi.findMockVout(ci, itx_decoded)
|
|
value_after_subfee = ci.make_int(itx_decoded["vout"][n]["value"])
|
|
assert value_after_subfee < swap_value
|
|
swap_value = value_after_subfee
|
|
wait_for_unspent(test_delay_event, ci, swap_value)
|
|
|
|
extra_options = {"prefunded_itx": itx}
|
|
offer_id = swap_clients[2].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
swap_value,
|
|
2 * COIN,
|
|
swap_value,
|
|
SwapTypes.XMR_SWAP,
|
|
extra_options=extra_options,
|
|
)
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[2],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[2].acceptBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[2],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=120,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
wait_for=120,
|
|
)
|
|
|
|
# Verify expected inputs were used
|
|
bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
|
|
assert bid.xmr_a_lock_tx
|
|
wtx = ci.rpc(
|
|
"gettransaction",
|
|
[
|
|
bid.xmr_a_lock_tx.txid.hex(),
|
|
],
|
|
)
|
|
itx_after = ci.describeTx(wtx["hex"])
|
|
assert len(itx_after["vin"]) == len(itx_decoded["vin"])
|
|
for i, txin in enumerate(itx_decoded["vin"]):
|
|
assert txin["txid"] == itx_after["vin"][i]["txid"]
|
|
assert txin["vout"] == itx_after["vin"][i]["vout"]
|
|
|
|
def test_15_missed_xmr_send(self):
|
|
logging.info("---------- Test PART to XMR B lock tx is lost")
|
|
swap_clients = self.swap_clients
|
|
|
|
amt_swap = make_int(random.uniform(0.1, 10.0), scale=8, r=1)
|
|
rate_swap = make_int(random.uniform(2.0, 20.0), scale=12, r=1)
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART,
|
|
Coins.XMR,
|
|
amt_swap,
|
|
rate_swap,
|
|
amt_swap,
|
|
SwapTypes.XMR_SWAP,
|
|
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS,
|
|
lock_value=28,
|
|
)
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
offer = swap_clients[1].getOffer(offer_id)
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.B_LOCK_TX_MISSED_SEND)
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_REFUNDED,
|
|
wait_for=1800,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.XMR_SWAP_FAILED_REFUNDED,
|
|
wait_for=30,
|
|
sent=True,
|
|
)
|
|
|
|
def test_16_new_subaddress(self):
|
|
logging.info("---------- Test that new subaddresses are created")
|
|
|
|
current_subaddress = read_json_api(1800, "wallets/xmr")["deposit_address"]
|
|
first_subaddress = read_json_api(1800, "wallets/xmr/nextdepositaddr")
|
|
second_subaddress = read_json_api(1800, "wallets/xmr/nextdepositaddr")
|
|
assert first_subaddress != second_subaddress
|
|
assert first_subaddress != current_subaddress
|
|
assert second_subaddress != current_subaddress
|
|
|
|
def test_17_edit_bid_state(self):
|
|
logging.info("---------- Test manually changing the state of a bid")
|
|
# Stall the bid by setting a debug token. Once it's stalled, clear the debug token and fix the bid state.
|
|
swap_clients = self.swap_clients
|
|
|
|
amt_swap = make_int(random.uniform(0.1, 10.0), scale=8, r=1)
|
|
rate_swap = make_int(random.uniform(2.0, 20.0), scale=12, r=1)
|
|
offer_id = swap_clients[0].postOffer(
|
|
Coins.PART, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP
|
|
)
|
|
|
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
|
bid_id = swap_clients[1].postXmrBid(offer_id, amt_swap)
|
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.BID_RECEIVED,
|
|
wait_for=(self.extra_wait_time + 40),
|
|
)
|
|
swap_clients[0].acceptXmrBid(bid_id)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.BID_STALLED_FOR_TEST,
|
|
sent=True,
|
|
wait_for=90,
|
|
)
|
|
data = {
|
|
"debug_ind": int(DebugTypes.NONE),
|
|
"bid_state": int(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX),
|
|
}
|
|
swap_clients[1].manualBidUpdate(bid_id, data)
|
|
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[0],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
wait_for=180,
|
|
)
|
|
wait_for_bid(
|
|
test_delay_event,
|
|
swap_clients[1],
|
|
bid_id,
|
|
BidStates.SWAP_COMPLETED,
|
|
sent=True,
|
|
)
|
|
|
|
def test_97_withdraw_all(self):
|
|
logging.info("---------- Test XMR withdrawal all")
|
|
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1800/json/wallets/xmr",
|
|
"unconfirmed",
|
|
0.0,
|
|
)
|
|
wallets0 = read_json_api(TEST_HTTP_PORT + 0, "wallets")
|
|
xmr_total = float(wallets0[Coins.XMR.name]["balance"])
|
|
|
|
if xmr_total < 10.0:
|
|
address_to = read_json_api(1800, "wallets")[Coins.XMR.name][
|
|
"deposit_address"
|
|
]
|
|
post_json = {
|
|
"value": 10.0,
|
|
"address": address_to,
|
|
"sweepall": False,
|
|
}
|
|
json_rv = read_json_api(
|
|
TEST_HTTP_PORT + 1, "wallets/xmr/withdraw", post_json
|
|
)
|
|
wait_for_balance(
|
|
test_delay_event,
|
|
"http://127.0.0.1:1800/json/wallets/xmr",
|
|
"balance",
|
|
10.0,
|
|
)
|
|
|
|
post_json = {
|
|
"address": read_json_api(1801, "wallets")[Coins.XMR.name][
|
|
"deposit_address"
|
|
],
|
|
"sweepall": True,
|
|
}
|
|
json_rv = json.loads(
|
|
post_json_req(
|
|
"http://127.0.0.1:{}/json/wallets/xmr/withdraw".format(
|
|
TEST_HTTP_PORT + 0
|
|
),
|
|
post_json,
|
|
)
|
|
)
|
|
assert len(json_rv["txid"]) == 64
|
|
|
|
try:
|
|
logging.info("Disabling XMR mining")
|
|
pause_event.clear()
|
|
|
|
address_to = read_json_api(1800, "wallets")[Coins.XMR.name][
|
|
"deposit_address"
|
|
]
|
|
|
|
wallets1 = read_json_api(TEST_HTTP_PORT + 1, "wallets")
|
|
xmr_total = float(wallets1[Coins.XMR.name]["balance"])
|
|
assert xmr_total > 10
|
|
|
|
post_json = {
|
|
"address": address_to,
|
|
"sweepall": True,
|
|
}
|
|
json_rv = json.loads(
|
|
post_json_req(
|
|
"http://127.0.0.1:{}/json/wallets/xmr/withdraw".format(
|
|
TEST_HTTP_PORT + 1
|
|
),
|
|
post_json,
|
|
)
|
|
)
|
|
assert (
|
|
"Balance must be fully confirmed to use sweep all" in json_rv["error"]
|
|
)
|
|
finally:
|
|
logging.info("Restoring XMR mining")
|
|
pause_event.set()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|