mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
Merge pull request #252 from tecnovert/descriptors
Add BTC descriptor wallet support.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2024 tecnovert
|
||||
# Copyright (c) 2024 The Basicswap developers
|
||||
# Copyright (c) 2024-2025 The Basicswap developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -14,6 +14,7 @@ from urllib.request import urlopen
|
||||
|
||||
from .util import read_json_api
|
||||
from basicswap.rpc import callrpc
|
||||
from basicswap.util import toBool
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
from basicswap.bin.prepare import downloadPIVXParams
|
||||
|
||||
@@ -44,6 +45,8 @@ PIVX_BASE_ZMQ_PORT = 36892
|
||||
|
||||
PREFIX_SECRET_KEY_REGTEST = 0x2E
|
||||
|
||||
BTC_USE_DESCRIPTORS = toBool(os.getenv("BTC_USE_DESCRIPTORS", False))
|
||||
|
||||
|
||||
def prepareDataDir(
|
||||
datadir,
|
||||
|
||||
@@ -19,13 +19,10 @@ from io import StringIO
|
||||
from urllib.request import urlopen
|
||||
from unittest.mock import patch
|
||||
|
||||
from basicswap.rpc_xmr import (
|
||||
callrpc_xmr,
|
||||
)
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
from basicswap.rpc_xmr import callrpc_xmr
|
||||
from tests.basicswap.mnemonics import mnemonics
|
||||
from tests.basicswap.util import (
|
||||
waitForServer,
|
||||
)
|
||||
from tests.basicswap.util import waitForServer
|
||||
from tests.basicswap.common import (
|
||||
BASE_PORT,
|
||||
BASE_RPC_PORT,
|
||||
@@ -35,6 +32,7 @@ from tests.basicswap.common import (
|
||||
LTC_BASE_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
PIVX_BASE_PORT,
|
||||
BTC_USE_DESCRIPTORS,
|
||||
)
|
||||
from tests.basicswap.extended.test_dcr import (
|
||||
DCR_BASE_PORT,
|
||||
@@ -49,8 +47,6 @@ from tests.basicswap.extended.test_doge import (
|
||||
DOGE_BASE_RPC_PORT,
|
||||
)
|
||||
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
|
||||
import basicswap.config as cfg
|
||||
import basicswap.bin.run as runSystem
|
||||
|
||||
@@ -133,6 +129,7 @@ def run_prepare(
|
||||
os.environ["PART_RPC_PORT"] = str(PARTICL_RPC_PORT_BASE)
|
||||
os.environ["BTC_RPC_PORT"] = str(BITCOIN_RPC_PORT_BASE)
|
||||
os.environ["BTC_PORT"] = str(BITCOIN_PORT_BASE)
|
||||
os.environ["BTC_USE_DESCRIPTORS"] = str(BTC_USE_DESCRIPTORS)
|
||||
os.environ["LTC_RPC_PORT"] = str(LITECOIN_RPC_PORT_BASE)
|
||||
os.environ["DCR_RPC_PORT"] = str(DECRED_RPC_PORT_BASE)
|
||||
os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
|
||||
|
||||
@@ -26,6 +26,7 @@ from basicswap.db import (
|
||||
from basicswap.util import (
|
||||
make_int,
|
||||
)
|
||||
from basicswap.util.extkey import ExtKeyPair
|
||||
from basicswap.interface.base import Curves
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
@@ -40,6 +41,7 @@ from tests.basicswap.common import (
|
||||
wait_for_none_active,
|
||||
BTC_BASE_RPC_PORT,
|
||||
)
|
||||
from basicswap.contrib.test_framework.descriptors import descsum_create
|
||||
from basicswap.contrib.test_framework.messages import (
|
||||
ToHex,
|
||||
FromHex,
|
||||
@@ -58,6 +60,8 @@ from .test_xmr import BaseTest, test_delay_event, callnoderpc
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
||||
|
||||
|
||||
class TestFunctions(BaseTest):
|
||||
base_rpc_port = None
|
||||
@@ -1166,7 +1170,6 @@ class BasicSwapTest(TestFunctions):
|
||||
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
|
||||
ci = self.swap_clients[0].ci(self.test_coin_from)
|
||||
|
||||
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
||||
test_wif = (
|
||||
self.swap_clients[0]
|
||||
.ci(self.test_coin_from)
|
||||
@@ -1178,10 +1181,35 @@ class BasicSwapTest(TestFunctions):
|
||||
"createwallet", [new_wallet_name, False, True, "", False, False]
|
||||
)
|
||||
self.callnoderpc("sethdseed", [True, test_wif], wallet=new_wallet_name)
|
||||
|
||||
wi = self.callnoderpc("getwalletinfo", wallet=new_wallet_name)
|
||||
assert wi["hdseedid"] == "3da5c0af91879e8ce97d9a843874601c08688078"
|
||||
|
||||
addr = self.callnoderpc("getnewaddress", wallet=new_wallet_name)
|
||||
self.callnoderpc("unloadwallet", [new_wallet_name])
|
||||
addr_info = self.callnoderpc(
|
||||
"getaddressinfo",
|
||||
[
|
||||
addr,
|
||||
],
|
||||
wallet=new_wallet_name,
|
||||
)
|
||||
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
|
||||
assert addr_info["hdkeypath"] == "m/0'/0'/0'"
|
||||
assert addr == "bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr"
|
||||
|
||||
addr_change = self.callnoderpc("getrawchangeaddress", wallet=new_wallet_name)
|
||||
addr_info = self.callnoderpc(
|
||||
"getaddressinfo",
|
||||
[
|
||||
addr_change,
|
||||
],
|
||||
wallet=new_wallet_name,
|
||||
)
|
||||
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
|
||||
assert addr_info["hdkeypath"] == "m/0'/1'/0'"
|
||||
assert addr_change == "bcrt1qdl9ryxkqjltv42lhfnqgdjf9tagxsjpp2xak9a"
|
||||
self.callnoderpc("unloadwallet", [new_wallet_name])
|
||||
|
||||
self.swap_clients[0].initialiseWallet(Coins.BTC, raise_errors=True)
|
||||
assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True
|
||||
for i in range(1500):
|
||||
@@ -1561,6 +1589,97 @@ class BasicSwapTest(TestFunctions):
|
||||
)
|
||||
assert len(tx_wallet["blockhash"]) == 64
|
||||
|
||||
def test_013_descriptor_wallet(self):
|
||||
logging.info(f"---------- Test {self.test_coin_from.name} descriptor wallet")
|
||||
|
||||
ci = self.swap_clients[0].ci(self.test_coin_from)
|
||||
|
||||
ek = ExtKeyPair()
|
||||
ek.set_seed(bytes.fromhex(test_seed))
|
||||
ek_encoded: str = ci.encode_secret_extkey(ek.encode_v())
|
||||
new_wallet_name = "descriptors_" + random.randbytes(10).hex()
|
||||
new_watch_wallet_name = "watch_descriptors_" + random.randbytes(10).hex()
|
||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||
ci.rpc("createwallet", [new_wallet_name, False, True, "", False, True])
|
||||
ci.rpc("createwallet", [new_watch_wallet_name, True, True, "", False, True])
|
||||
|
||||
desc_external = descsum_create(f"wpkh({ek_encoded}/0h/0h/*h)")
|
||||
desc_internal = descsum_create(f"wpkh({ek_encoded}/0h/1h/*h)")
|
||||
self.callnoderpc(
|
||||
"importdescriptors",
|
||||
[
|
||||
[
|
||||
{
|
||||
"desc": desc_external,
|
||||
"timestamp": "now",
|
||||
"active": True,
|
||||
"range": [0, 10],
|
||||
"next_index": 0,
|
||||
},
|
||||
{
|
||||
"desc": desc_internal,
|
||||
"timestamp": "now",
|
||||
"active": True,
|
||||
"internal": True,
|
||||
},
|
||||
],
|
||||
],
|
||||
wallet=new_wallet_name,
|
||||
)
|
||||
|
||||
addr = self.callnoderpc("getnewaddress", wallet=new_wallet_name)
|
||||
addr_info = self.callnoderpc(
|
||||
"getaddressinfo",
|
||||
[
|
||||
addr,
|
||||
],
|
||||
wallet=new_wallet_name,
|
||||
)
|
||||
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
|
||||
assert addr_info["hdkeypath"] == "m/0h/0h/0h"
|
||||
assert addr == "bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr"
|
||||
|
||||
addr_change = self.callnoderpc("getrawchangeaddress", wallet=new_wallet_name)
|
||||
addr_info = self.callnoderpc(
|
||||
"getaddressinfo",
|
||||
[
|
||||
addr_change,
|
||||
],
|
||||
wallet=new_wallet_name,
|
||||
)
|
||||
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
|
||||
assert addr_info["hdkeypath"] == "m/0h/1h/0h"
|
||||
assert addr_change == "bcrt1qdl9ryxkqjltv42lhfnqgdjf9tagxsjpp2xak9a"
|
||||
|
||||
desc_watch = descsum_create(f"addr({addr})")
|
||||
self.callnoderpc(
|
||||
"importdescriptors",
|
||||
[
|
||||
[
|
||||
{"desc": desc_watch, "timestamp": "now", "active": False},
|
||||
],
|
||||
],
|
||||
wallet=new_watch_wallet_name,
|
||||
)
|
||||
ci.rpc_wallet("sendtoaddress", [addr, 1])
|
||||
found: bool = False
|
||||
for i in range(10):
|
||||
txn_list = self.callnoderpc(
|
||||
"listtransactions", ["*", 100, 0, True], wallet=new_watch_wallet_name
|
||||
)
|
||||
test_delay_event.wait(1)
|
||||
if len(txn_list) > 0:
|
||||
found = True
|
||||
break
|
||||
assert found
|
||||
|
||||
# Test that addresses can be generated beyond range in listdescriptors
|
||||
for i in range(2000):
|
||||
self.callnoderpc("getnewaddress", wallet=new_wallet_name)
|
||||
|
||||
self.callnoderpc("unloadwallet", [new_wallet_name])
|
||||
self.callnoderpc("unloadwallet", [new_watch_wallet_name])
|
||||
|
||||
def test_01_0_lock_bad_prevouts(self):
|
||||
logging.info(
|
||||
"---------- Test {} lock_bad_prevouts".format(self.test_coin_from.name)
|
||||
@@ -1862,11 +1981,11 @@ class TestBTC(BasicSwapTest):
|
||||
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"] == test_seed
|
||||
assert rv["seed_id"] in (
|
||||
"3da5c0af91879e8ce97d9a843874601c08688078",
|
||||
"4a231080ec6f4078e543d39cc6dcf0b922c9b16b",
|
||||
)
|
||||
assert rv["seed_id"] == "3da5c0af91879e8ce97d9a843874601c08688078"
|
||||
assert rv["seed_id"] == rv["expected_seed_id"]
|
||||
|
||||
rv = read_json_api(
|
||||
|
||||
@@ -83,6 +83,7 @@ from tests.basicswap.common import (
|
||||
LTC_BASE_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
PREFIX_SECRET_KEY_REGTEST,
|
||||
BTC_USE_DESCRIPTORS,
|
||||
)
|
||||
from basicswap.db_util import (
|
||||
remove_expired_data,
|
||||
@@ -172,6 +173,7 @@ def prepare_swapclient_dir(
|
||||
"datadir": os.path.join(datadir, "btc_" + str(node_id)),
|
||||
"bindir": cfg.BITCOIN_BINDIR,
|
||||
"use_segwit": True,
|
||||
"use_descriptors": BTC_USE_DESCRIPTORS,
|
||||
},
|
||||
},
|
||||
"check_progress_seconds": 2,
|
||||
@@ -189,6 +191,9 @@ def prepare_swapclient_dir(
|
||||
"restrict_unknown_seed_wallets": False,
|
||||
}
|
||||
|
||||
if BTC_USE_DESCRIPTORS:
|
||||
settings["chainclients"]["bitcoin"]["watch_wallet_name"] = "bsx_watch"
|
||||
|
||||
if Coins.XMR in with_coins:
|
||||
settings["chainclients"]["monero"] = {
|
||||
"connection_type": "rpc",
|
||||
@@ -474,25 +479,29 @@ class BaseTest(unittest.TestCase):
|
||||
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",
|
||||
)
|
||||
if BTC_USE_DESCRIPTORS:
|
||||
# How to set blank and disable_private_keys with wallet util?
|
||||
pass
|
||||
else:
|
||||
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(
|
||||
@@ -505,9 +514,21 @@ class BaseTest(unittest.TestCase):
|
||||
"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 BTC_USE_DESCRIPTORS:
|
||||
rpc_func = make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT)
|
||||
waitForRPC(
|
||||
rpc_func, test_delay_event, rpc_command="getblockchaininfo"
|
||||
)
|
||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||
rpc_func(
|
||||
"createwallet", ["wallet.dat", False, True, "", False, True]
|
||||
)
|
||||
rpc_func("createwallet", ["bsx_watch", True, True, "", False, True])
|
||||
else:
|
||||
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):
|
||||
@@ -658,6 +679,11 @@ class BaseTest(unittest.TestCase):
|
||||
xmr_ci.getMainWalletAddress(),
|
||||
)
|
||||
|
||||
if BTC_USE_DESCRIPTORS:
|
||||
# sc.initialiseWallet(Coins.BTC)
|
||||
# Import a random seed to keep the existing test behaviour. BTC core rescans even with timestamp: now.
|
||||
sc.ci(Coins.BTC).initialiseWallet(random.randbytes(32))
|
||||
|
||||
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
|
||||
cls.http_threads.append(t)
|
||||
t.start()
|
||||
@@ -685,6 +711,7 @@ class BaseTest(unittest.TestCase):
|
||||
"getnewaddress",
|
||||
["mining_addr", "bech32"],
|
||||
base_rpc_port=BTC_BASE_RPC_PORT,
|
||||
wallet="wallet.dat",
|
||||
)
|
||||
num_blocks = 400 # Mine enough to activate segwit
|
||||
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
|
||||
@@ -700,6 +727,7 @@ class BaseTest(unittest.TestCase):
|
||||
"getnewaddress",
|
||||
["initial addr"],
|
||||
base_rpc_port=BTC_BASE_RPC_PORT,
|
||||
wallet="wallet.dat",
|
||||
)
|
||||
for i in range(5):
|
||||
callnoderpc(
|
||||
@@ -707,6 +735,7 @@ class BaseTest(unittest.TestCase):
|
||||
"sendtoaddress",
|
||||
[btc_addr1, 100],
|
||||
base_rpc_port=BTC_BASE_RPC_PORT,
|
||||
wallet="wallet.dat",
|
||||
)
|
||||
|
||||
# Switch addresses so wallet amounts stay constant
|
||||
|
||||
Reference in New Issue
Block a user