mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
nmc: Use descriptor wallets by default.
This commit is contained in:
@@ -379,6 +379,12 @@ def getWalletName(coin_params: str, default_name: str, prefix_override=None) ->
|
|||||||
return wallet_name
|
return wallet_name
|
||||||
|
|
||||||
|
|
||||||
|
def getDescriptorWalletOption(coin_params):
|
||||||
|
ticker: str = coin_params["ticker"]
|
||||||
|
default_option: bool = True if ticker in ("NMC",) else False
|
||||||
|
return toBool(os.getenv(ticker + "_USE_DESCRIPTORS", default_option))
|
||||||
|
|
||||||
|
|
||||||
def getKnownVersion(coin_name: str) -> str:
|
def getKnownVersion(coin_name: str) -> str:
|
||||||
version, version_tag, _ = known_coins[coin_name]
|
version, version_tag, _ = known_coins[coin_name]
|
||||||
return version + version_tag
|
return version + version_tag
|
||||||
@@ -1928,11 +1934,15 @@ def initialise_wallets(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
if use_descriptors:
|
if use_descriptors:
|
||||||
|
watch_wallet_name = coin_settings["watch_wallet_name"]
|
||||||
|
logger.info(
|
||||||
|
f'Creating wallet "{watch_wallet_name}" for {getCoinName(c)}.'
|
||||||
|
)
|
||||||
swap_client.callcoinrpc(
|
swap_client.callcoinrpc(
|
||||||
c,
|
c,
|
||||||
"createwallet",
|
"createwallet",
|
||||||
[
|
[
|
||||||
coin_settings["watch_wallet_name"],
|
watch_wallet_name,
|
||||||
True,
|
True,
|
||||||
True,
|
True,
|
||||||
"",
|
"",
|
||||||
@@ -2596,9 +2606,8 @@ def main():
|
|||||||
coin_settings["wallet_name"] = set_name
|
coin_settings["wallet_name"] = set_name
|
||||||
|
|
||||||
ticker: str = coin_params["ticker"]
|
ticker: str = coin_params["ticker"]
|
||||||
if toBool(os.getenv(ticker + "_USE_DESCRIPTORS", False)):
|
if getDescriptorWalletOption(coin_params):
|
||||||
|
if coin_id not in (Coins.BTC, Coins.NMC):
|
||||||
if coin_id not in (Coins.BTC,):
|
|
||||||
raise ValueError(f"Descriptor wallet unavailable for {coin_name}")
|
raise ValueError(f"Descriptor wallet unavailable for {coin_name}")
|
||||||
|
|
||||||
coin_settings["use_descriptors"] = True
|
coin_settings["use_descriptors"] = True
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ chainparams = {
|
|||||||
"bip44": 44,
|
"bip44": 44,
|
||||||
"min_amount": 100000,
|
"min_amount": 100000,
|
||||||
"max_amount": 10000000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
"ext_public_key_prefix": 0x696E82D1,
|
||||||
|
"ext_secret_key_prefix": 0x8F1DAEB8,
|
||||||
},
|
},
|
||||||
"testnet": {
|
"testnet": {
|
||||||
"rpcport": 51935,
|
"rpcport": 51935,
|
||||||
@@ -69,6 +71,8 @@ chainparams = {
|
|||||||
"bip44": 1,
|
"bip44": 1,
|
||||||
"min_amount": 100000,
|
"min_amount": 100000,
|
||||||
"max_amount": 10000000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
"ext_public_key_prefix": 0xE1427800,
|
||||||
|
"ext_secret_key_prefix": 0x04889478,
|
||||||
},
|
},
|
||||||
"regtest": {
|
"regtest": {
|
||||||
"rpcport": 51936,
|
"rpcport": 51936,
|
||||||
@@ -80,6 +84,8 @@ chainparams = {
|
|||||||
"bip44": 1,
|
"bip44": 1,
|
||||||
"min_amount": 100000,
|
"min_amount": 100000,
|
||||||
"max_amount": 10000000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
"ext_public_key_prefix": 0xE1427800,
|
||||||
|
"ext_secret_key_prefix": 0x04889478,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Coins.BTC: {
|
Coins.BTC: {
|
||||||
|
|||||||
@@ -1862,19 +1862,69 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
"Could not find address with enough funds for proof",
|
"Could not find address with enough funds for proof",
|
||||||
)
|
)
|
||||||
|
|
||||||
self._log.debug("sign_for_addr %s", sign_for_addr)
|
self._log.debug(f"sign_for_addr {sign_for_addr}")
|
||||||
|
|
||||||
|
funds_addr: str = sign_for_addr
|
||||||
if (
|
if (
|
||||||
self.using_segwit()
|
self.using_segwit()
|
||||||
): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
||||||
# 'Address does not refer to key' for non p2pkh
|
# 'Address does not refer to key' for non p2pkh
|
||||||
pkh = self.decodeAddress(sign_for_addr)
|
pkh = self.decodeAddress(sign_for_addr)
|
||||||
sign_for_addr = self.pkh_to_address(pkh)
|
sign_for_addr = self.pkh_to_address(pkh)
|
||||||
self._log.debug("sign_for_addr converted %s", sign_for_addr)
|
self._log.debug(f"sign_for_addr converted {sign_for_addr}")
|
||||||
|
|
||||||
|
if self._use_descriptors:
|
||||||
|
# https://github.com/bitcoin/bitcoin/issues/10542
|
||||||
|
# https://github.com/bitcoin/bitcoin/issues/26046
|
||||||
|
priv_keys = self.rpc_wallet(
|
||||||
|
"listdescriptors",
|
||||||
|
[
|
||||||
|
True,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
addr_info = self.rpc_wallet(
|
||||||
|
"getaddressinfo",
|
||||||
|
[
|
||||||
|
funds_addr,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
hdkeypath = addr_info["hdkeypath"]
|
||||||
|
|
||||||
|
sign_for_address_key = None
|
||||||
|
for descriptor in priv_keys["descriptors"]:
|
||||||
|
if descriptor["active"] is False or descriptor["internal"] is True:
|
||||||
|
continue
|
||||||
|
desc = descriptor["desc"]
|
||||||
|
assert desc.startswith("wpkh(")
|
||||||
|
ext_key = desc[5:].split(")")[0].split("/", 1)[0]
|
||||||
|
ext_key_data = decodeAddress(ext_key)[4:]
|
||||||
|
ci_part = self._sc.ci(Coins.PART)
|
||||||
|
ext_key_data_part = ci_part.encode_secret_extkey(ext_key_data)
|
||||||
|
rv = ci_part.rpc_wallet(
|
||||||
|
"extkey", ["info", ext_key_data_part, hdkeypath]
|
||||||
|
)
|
||||||
|
extkey_derived = rv["key_info"]["result"]
|
||||||
|
ext_key_data = decodeAddress(extkey_derived)[4:]
|
||||||
|
ek = ExtKeyPair()
|
||||||
|
ek.decode(ext_key_data)
|
||||||
|
sign_for_address_key = self.encodeKey(ek._key)
|
||||||
|
break
|
||||||
|
assert sign_for_address_key is not None
|
||||||
|
signature = self.rpc(
|
||||||
|
"signmessagewithprivkey",
|
||||||
|
[
|
||||||
|
sign_for_address_key,
|
||||||
|
sign_for_addr + "_swap_proof_" + extra_commit_bytes.hex(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
del priv_keys
|
||||||
|
else:
|
||||||
signature = self.rpc_wallet(
|
signature = self.rpc_wallet(
|
||||||
"signmessage",
|
"signmessage",
|
||||||
[sign_for_addr, sign_for_addr + "_swap_proof_" + extra_commit_bytes.hex()],
|
[
|
||||||
|
sign_for_addr,
|
||||||
|
sign_for_addr + "_swap_proof_" + extra_commit_bytes.hex(),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
prove_utxos = [] # TODO: Send specific utxos
|
prove_utxos = [] # TODO: Send specific utxos
|
||||||
|
|||||||
@@ -14,57 +14,3 @@ class NMCInterface(BTCInterface):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.NMC
|
return Coins.NMC
|
||||||
|
|
||||||
def lockNonSegwitPrevouts(self) -> None:
|
|
||||||
# For tests
|
|
||||||
# NMC Seems to ignore utxo locks
|
|
||||||
unspent = self.rpc_wallet("listunspent")
|
|
||||||
|
|
||||||
to_lock = []
|
|
||||||
for u in unspent:
|
|
||||||
if u.get("spendable", False) is False:
|
|
||||||
continue
|
|
||||||
if "desc" in u:
|
|
||||||
desc = u["desc"]
|
|
||||||
if self.use_p2shp2wsh():
|
|
||||||
if not desc.startswith("sh(wpkh"):
|
|
||||||
to_lock.append(
|
|
||||||
{
|
|
||||||
"txid": u["txid"],
|
|
||||||
"vout": u["vout"],
|
|
||||||
"amount": u["amount"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if not desc.startswith("wpkh"):
|
|
||||||
to_lock.append(
|
|
||||||
{
|
|
||||||
"txid": u["txid"],
|
|
||||||
"vout": u["vout"],
|
|
||||||
"amount": u["amount"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(to_lock) > 0:
|
|
||||||
self._log.debug(f"Spending {len(to_lock)} non segwit prevouts")
|
|
||||||
addr_out = self.rpc_wallet(
|
|
||||||
"getnewaddress", ["convert non segwit", "bech32"]
|
|
||||||
)
|
|
||||||
prevouts = []
|
|
||||||
sum_amount: int = 0
|
|
||||||
for utxo in to_lock:
|
|
||||||
prevouts.append(
|
|
||||||
{
|
|
||||||
"txid": utxo["txid"],
|
|
||||||
"vout": utxo["vout"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
sum_amount += self.make_int(utxo["amount"])
|
|
||||||
|
|
||||||
fee = 100000 * len(prevouts)
|
|
||||||
funded_tx = self.rpc(
|
|
||||||
"createrawtransaction",
|
|
||||||
[prevouts, {addr_out: self.format_amount(sum_amount - fee)}],
|
|
||||||
)
|
|
||||||
signed_tx = self.rpc_wallet("signrawtransactionwithwallet", [funded_tx])
|
|
||||||
self.rpc("sendrawtransaction", [signed_tx["hex"]])
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ NAMECOIND = os.getenv("NAMECOIND", "namecoind" + cfg.bin_suffix)
|
|||||||
NAMECOIN_CLI = os.getenv("NAMECOIN_CLI", "namecoin-cli" + cfg.bin_suffix)
|
NAMECOIN_CLI = os.getenv("NAMECOIN_CLI", "namecoin-cli" + cfg.bin_suffix)
|
||||||
NAMECOIN_TX = os.getenv("NAMECOIN_TX", "namecoin-tx" + cfg.bin_suffix)
|
NAMECOIN_TX = os.getenv("NAMECOIN_TX", "namecoin-tx" + cfg.bin_suffix)
|
||||||
|
|
||||||
USE_DESCRIPTOR_WALLETS = toBool(os.getenv("USE_DESCRIPTOR_WALLETS", False))
|
NMC_USE_DESCRIPTORS = toBool(os.getenv("NMC_USE_DESCRIPTORS", True))
|
||||||
|
|
||||||
NMC_BASE_PORT = 8136
|
NMC_BASE_PORT = 8136
|
||||||
NMC_BASE_RPC_PORT = 8146
|
NMC_BASE_RPC_PORT = 8146
|
||||||
@@ -112,7 +112,7 @@ class TestNMC(BasicSwapTest):
|
|||||||
base_rpc_port = NMC_BASE_RPC_PORT
|
base_rpc_port = NMC_BASE_RPC_PORT
|
||||||
nmc_addr = None
|
nmc_addr = None
|
||||||
max_fee: int = 200000
|
max_fee: int = 200000
|
||||||
test_fee_rate: int = 10000 # sats/kvB
|
test_fee_rate: int = 100000 # sats/kvB
|
||||||
|
|
||||||
def mineBlock(self, num_blocks: int = 1) -> None:
|
def mineBlock(self, num_blocks: int = 1) -> None:
|
||||||
self.callnoderpc("generatetoaddress", [num_blocks, self.nmc_addr])
|
self.callnoderpc("generatetoaddress", [num_blocks, self.nmc_addr])
|
||||||
@@ -160,7 +160,12 @@ class TestNMC(BasicSwapTest):
|
|||||||
if len(nmc_rpc("listwallets")) < 1:
|
if len(nmc_rpc("listwallets")) < 1:
|
||||||
nmc_rpc(
|
nmc_rpc(
|
||||||
"createwallet",
|
"createwallet",
|
||||||
["wallet.dat", False, False, "", False, USE_DESCRIPTOR_WALLETS],
|
["wallet.dat", False, True, "", False, NMC_USE_DESCRIPTORS],
|
||||||
|
)
|
||||||
|
if NMC_USE_DESCRIPTORS:
|
||||||
|
nmc_rpc(
|
||||||
|
"createwallet",
|
||||||
|
["bsx_watch", True, True, "", False, True],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -180,12 +185,18 @@ class TestNMC(BasicSwapTest):
|
|||||||
"use_csv": True,
|
"use_csv": True,
|
||||||
"use_segwit": True,
|
"use_segwit": True,
|
||||||
"blocks_confirmed": 1,
|
"blocks_confirmed": 1,
|
||||||
|
"use_descriptors": NMC_USE_DESCRIPTORS,
|
||||||
}
|
}
|
||||||
|
if NMC_USE_DESCRIPTORS:
|
||||||
|
settings["chainclients"]["namecoin"]["watch_wallet_name"] = "bsx_watch"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
if not cls.restore_instance:
|
if not cls.restore_instance:
|
||||||
|
for sc in cls.swap_clients:
|
||||||
|
ci = sc.ci(cls.test_coin)
|
||||||
|
ci.initialiseWallet(ci.getNewRandomKey())
|
||||||
cls.nmc_addr = ci0.rpc_wallet("getnewaddress", ["mining_addr", "bech32"])
|
cls.nmc_addr = ci0.rpc_wallet("getnewaddress", ["mining_addr", "bech32"])
|
||||||
else:
|
else:
|
||||||
addrs = ci0.rpc_wallet(
|
addrs = ci0.rpc_wallet(
|
||||||
@@ -214,7 +225,7 @@ class TestNMC(BasicSwapTest):
|
|||||||
new_wallet_name = random.randbytes(10).hex()
|
new_wallet_name = random.randbytes(10).hex()
|
||||||
self.callnoderpc(
|
self.callnoderpc(
|
||||||
"createwallet",
|
"createwallet",
|
||||||
[new_wallet_name, False, False, "", False, USE_DESCRIPTOR_WALLETS],
|
[new_wallet_name, False, False, "", False, NMC_USE_DESCRIPTORS],
|
||||||
)
|
)
|
||||||
self.callnoderpc("sethdseed", [True, test_wif], wallet=new_wallet_name)
|
self.callnoderpc("sethdseed", [True, test_wif], wallet=new_wallet_name)
|
||||||
addr = self.callnoderpc(
|
addr = self.callnoderpc(
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ from basicswap.db import (
|
|||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
make_int,
|
make_int,
|
||||||
)
|
)
|
||||||
|
from basicswap.util.address import (
|
||||||
|
decodeAddress,
|
||||||
|
)
|
||||||
from basicswap.util.extkey import ExtKeyPair
|
from basicswap.util.extkey import ExtKeyPair
|
||||||
from basicswap.interface.base import Curves
|
from basicswap.interface.base import Curves
|
||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
@@ -182,7 +185,7 @@ class TestFunctions(BaseTest):
|
|||||||
bid0 = read_json_api(1800 + id_offerer, f"bids/{bid_id.hex()}")
|
bid0 = read_json_api(1800 + id_offerer, f"bids/{bid_id.hex()}")
|
||||||
bid1 = read_json_api(1800 + id_bidder, f"bids/{bid_id.hex()}")
|
bid1 = read_json_api(1800 + id_bidder, f"bids/{bid_id.hex()}")
|
||||||
|
|
||||||
tolerance = 1
|
tolerance = 2
|
||||||
assert bid0["ticker_from"] == ci_from.ticker()
|
assert bid0["ticker_from"] == ci_from.ticker()
|
||||||
assert bid1["ticker_from"] == ci_from.ticker()
|
assert bid1["ticker_from"] == ci_from.ticker()
|
||||||
assert bid0["ticker_to"] == ci_to.ticker()
|
assert bid0["ticker_to"] == ci_to.ticker()
|
||||||
@@ -1180,6 +1183,10 @@ class BasicSwapTest(TestFunctions):
|
|||||||
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
|
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
|
||||||
ci = self.swap_clients[0].ci(self.test_coin_from)
|
ci = self.swap_clients[0].ci(self.test_coin_from)
|
||||||
|
|
||||||
|
if hasattr(ci, "_use_descriptors") and ci._use_descriptors:
|
||||||
|
logging.warning("Skipping test")
|
||||||
|
return
|
||||||
|
|
||||||
test_wif = (
|
test_wif = (
|
||||||
self.swap_clients[0]
|
self.swap_clients[0]
|
||||||
.ci(self.test_coin_from)
|
.ci(self.test_coin_from)
|
||||||
@@ -1310,7 +1317,7 @@ class BasicSwapTest(TestFunctions):
|
|||||||
|
|
||||||
# Record unspents before createSCLockTx as the used ones will be locked
|
# Record unspents before createSCLockTx as the used ones will be locked
|
||||||
unspents = ci.rpc_wallet("listunspent")
|
unspents = ci.rpc_wallet("listunspent")
|
||||||
|
lockedunspents_before = ci.rpc_wallet("listlockunspent")
|
||||||
a = ci.getNewRandomKey()
|
a = ci.getNewRandomKey()
|
||||||
b = ci.getNewRandomKey()
|
b = ci.getNewRandomKey()
|
||||||
|
|
||||||
@@ -1322,8 +1329,17 @@ class BasicSwapTest(TestFunctions):
|
|||||||
lock_tx = ci.fundSCLockTx(lock_tx, self.test_fee_rate)
|
lock_tx = ci.fundSCLockTx(lock_tx, self.test_fee_rate)
|
||||||
lock_tx = ci.signTxWithWallet(lock_tx)
|
lock_tx = ci.signTxWithWallet(lock_tx)
|
||||||
|
|
||||||
|
# Check that inputs were locked
|
||||||
|
lockedunspents = ci.rpc_wallet("listlockunspent")
|
||||||
|
assert len(lockedunspents) > len(lockedunspents_before)
|
||||||
unspents_after = ci.rpc_wallet("listunspent")
|
unspents_after = ci.rpc_wallet("listunspent")
|
||||||
assert len(unspents) > len(unspents_after)
|
for utxo in unspents_after:
|
||||||
|
for locked_utxo in lockedunspents:
|
||||||
|
if (
|
||||||
|
locked_utxo["txid"] == utxo["txid"]
|
||||||
|
and locked_utxo["vout"] == utxo["vout"]
|
||||||
|
):
|
||||||
|
raise ValueError("Locked utxo in listunspent")
|
||||||
|
|
||||||
tx_decoded = ci.rpc("decoderawtransaction", [lock_tx.hex()])
|
tx_decoded = ci.rpc("decoderawtransaction", [lock_tx.hex()])
|
||||||
txid = tx_decoded["txid"]
|
txid = tx_decoded["txid"]
|
||||||
@@ -1679,6 +1695,49 @@ class BasicSwapTest(TestFunctions):
|
|||||||
wallet=new_wallet_name,
|
wallet=new_wallet_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# https://github.com/bitcoin/bitcoin/issues/10542
|
||||||
|
# https://github.com/bitcoin/bitcoin/issues/26046
|
||||||
|
sign_for_address: str = self.callnoderpc(
|
||||||
|
"getnewaddress",
|
||||||
|
[
|
||||||
|
"sign address",
|
||||||
|
],
|
||||||
|
wallet=new_wallet_name,
|
||||||
|
)
|
||||||
|
priv_keys = self.callnoderpc("listdescriptors", [True], wallet=new_wallet_name)
|
||||||
|
addr_info = self.callnoderpc(
|
||||||
|
"getaddressinfo", [sign_for_address], wallet=new_wallet_name
|
||||||
|
)
|
||||||
|
hdkeypath = addr_info["hdkeypath"]
|
||||||
|
|
||||||
|
sign_for_address_key = None
|
||||||
|
for descriptor in priv_keys["descriptors"]:
|
||||||
|
if descriptor["active"] is False or descriptor["internal"] is True:
|
||||||
|
continue
|
||||||
|
desc = descriptor["desc"]
|
||||||
|
assert desc.startswith("wpkh(")
|
||||||
|
ext_key = desc[5:].split(")")[0].split("/", 1)[0]
|
||||||
|
ext_key_data = decodeAddress(ext_key)[4:]
|
||||||
|
ci_part = self.swap_clients[0].ci(Coins.PART)
|
||||||
|
ext_key_data_part = ci_part.encode_secret_extkey(ext_key_data)
|
||||||
|
rv = ci_part.rpc_wallet("extkey", ["info", ext_key_data_part, hdkeypath])
|
||||||
|
extkey_derived = rv["key_info"]["result"]
|
||||||
|
ext_key_data = decodeAddress(extkey_derived)[4:]
|
||||||
|
ek = ExtKeyPair()
|
||||||
|
ek.decode(ext_key_data)
|
||||||
|
addr = ci.encodeSegwitAddress(ci.getAddressHashFromKey(ek._key))
|
||||||
|
assert addr == sign_for_address
|
||||||
|
sign_for_address_key = ci.encodeKey(ek._key)
|
||||||
|
break
|
||||||
|
assert sign_for_address_key is not None
|
||||||
|
sign_message: str = "Would be better if dumpprivkey or signmessage worked"
|
||||||
|
sig = self.callnoderpc(
|
||||||
|
"signmessagewithprivkey",
|
||||||
|
[sign_for_address_key, sign_message],
|
||||||
|
wallet=new_wallet_name,
|
||||||
|
)
|
||||||
|
assert ci.verifyMessage(sign_for_address, sign_message, sig)
|
||||||
|
|
||||||
self.callnoderpc("unloadwallet", [new_wallet_name])
|
self.callnoderpc("unloadwallet", [new_wallet_name])
|
||||||
self.callnoderpc("unloadwallet", [new_watch_wallet_name])
|
self.callnoderpc("unloadwallet", [new_watch_wallet_name])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user