Add bitcoincash support for prepare and run scripts, add bitcoincash to testing suite, groundwork for bch-xmr atomic swap protocol

This commit is contained in:
mainnet-pat
2024-10-07 16:48:24 +00:00
committed by tecnovert
parent 745d1460e5
commit 1b43806d51
20 changed files with 937 additions and 9 deletions

View File

@@ -31,6 +31,11 @@ BTC_BASE_RPC_PORT = 32792
BTC_BASE_ZMQ_PORT = 33792
BTC_BASE_TOR_PORT = 33732
BCH_BASE_PORT = 41792
BCH_BASE_RPC_PORT = 42792
BCH_BASE_ZMQ_PORT = 43792
BCH_BASE_TOR_PORT = 43732
LTC_BASE_PORT = 34792
LTC_BASE_RPC_PORT = 35792
LTC_BASE_ZMQ_PORT = 36792
@@ -75,7 +80,8 @@ def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_P
fp.write('acceptnonstdtxn=0\n')
fp.write('txindex=1\n')
fp.write('wallet=wallet.dat\n')
fp.write('findpeers=0\n')
if not base_p2p_port == BCH_BASE_PORT:
fp.write('findpeers=0\n')
if base_p2p_port == BTC_BASE_PORT:
fp.write('deprecatedrpc=create_bdb\n')

View File

@@ -0,0 +1,11 @@
import unittest
from basicswap.protocols.xmr_swap_1 import XmrBchSwapInterface
class TestXmrBchSwapInterface(unittest.TestCase):
def test_generate_script(self):
out_1 = bytes.fromhex('a9147171b53baf87efc9c78ffc0e37a78859cebaae4a87')
out_2 = bytes.fromhex('a9147171b53baf87efc9c78ffc0e37a78859cebaae4a87')
public_key = bytes.fromhex('03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556')
print(XmrBchSwapInterface().genScriptLockTxScript(None, 1000, out_1, out_2, public_key, 2).hex())

View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2021-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import random
import logging
import unittest
from basicswap.db import (
Concepts,
)
from basicswap.basicswap import (
BidStates,
Coins,
DebugTypes,
SwapTypes,
)
from basicswap.basicswap_util import (
TxLockTypes,
EventLogTypes,
)
from basicswap.util import (
make_int,
format_amount,
)
from basicswap.interface.base import Curves
from basicswap.util.crypto import sha256
from tests.basicswap.util import (
read_json_api,
)
from tests.basicswap.common import (
abandon_all_swaps,
wait_for_bid,
wait_for_event,
wait_for_offer,
wait_for_balance,
wait_for_unspent,
wait_for_none_active,
BTC_BASE_RPC_PORT,
)
from basicswap.contrib.test_framework.messages import (
ToHex,
FromHex,
CTxIn,
COutPoint,
CTransaction,
CTxInWitness,
)
from basicswap.contrib.test_framework.script import (
CScript,
OP_EQUAL,
OP_CHECKLOCKTIMEVERIFY,
OP_CHECKSEQUENCEVERIFY,
)
from .test_xmr import BaseTest, test_delay_event, callnoderpc
from coincurve.ecdsaotves import (
ecdsaotves_enc_sign,
ecdsaotves_enc_verify,
ecdsaotves_dec_sig,
ecdsaotves_rec_enc_key
)
logger = logging.getLogger()
class TestFunctions(BaseTest):
__test__ = True
start_bch_nodes = True
base_rpc_port = None
extra_wait_time = 0
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr])
def check_softfork_active(self, feature_name):
deploymentinfo = self.callnoderpc('getdeploymentinfo')
assert (deploymentinfo['deployments'][feature_name]['active'] is True)
def test_010_bch_txn_size(self):
logging.info('---------- Test {} txn_size'.format(Coins.BCH))
swap_clients = self.swap_clients
ci = swap_clients[0].ci(Coins.BCH)
pi = swap_clients[0].pi(SwapTypes.XMR_BCH_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/B
fee_rate: int = 1
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
mining_fee = 1000
timelock = 2
a_receive = ci.getNewAddress()
b_receive = ci.getNewAddress()
b_refund = ci.getNewAddress()
print(pi)
refund_lock_tx_script = pi.genScriptLockTxScript(mining_fee=mining_fee, out_1=ci.addressToLockingBytecode(b_refund), out_2=ci.addressToLockingBytecode(a_receive), public_key=A, timelock=timelock)
addr_out = ci.getNewAddress()
lock_tx_script = pi.genScriptLockTxScript(mining_fee=mining_fee, out_1=ci.addressToLockingBytecode(a_receive), out_2=ci.scriptToP2SH32LockingBytecode(refund_lock_tx_script), public_key=B, timelock=timelock)
lock_tx = ci.createSCLockTx(amount, lock_tx_script)
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate)
lock_tx = ci.signTxWithWallet(lock_tx)
print(lock_tx.hex())
unspents_after = ci.rpc('listunspent')
assert (len(unspents) > len(unspents_after))
tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()])
print(tx_decoded)
txid = tx_decoded['txid']
size = tx_decoded['size']
expect_fee_int = round(fee_rate * size)
expect_fee = ci.format_amount(expect_fee_int)
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])
print(rv)
wallet_tx_fee = -ci.make_int(rv['fee'])
assert (wallet_tx_fee == fee_value)
assert (wallet_tx_fee == expect_fee_int)
pkh_out = ci.decodeAddress(a_receive)
msg = sha256(ci.addressToLockingBytecode(a_receive))
# bob creates an adaptor signature for alice and transmits it to her
bAdaptorSig = ecdsaotves_enc_sign(b, A, msg)
# alice verifies the adaptor signature
assert (ecdsaotves_enc_verify(B, A, msg, bAdaptorSig))
# alice decrypts the adaptor signature
bAdaptorSig_dec = ecdsaotves_dec_sig(a, bAdaptorSig)
print("\nbAdaptorSig_dec", bAdaptorSig_dec.hex())
print(ci.addressToLockingBytecode(a_receive).hex(), msg.hex(), bAdaptorSig_dec.hex(), B.hex())
fee_info = {}
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, mining_fee, ves=bAdaptorSig_dec, fee_info=fee_info)
print(lock_spend_tx.hex())
size_estimated: int = fee_info['size']
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
print(tx_decoded)
txid = tx_decoded['txid']
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
size_actual: int = tx_decoded['size']
assert (size_actual <= size_estimated and size_estimated - size_actual < 4)
assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
expect_size: int = ci.xmr_swap_a_lock_spend_tx_vsize()
assert (expect_size >= size_actual)
assert (expect_size - size_actual < 10)
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
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_size: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_size >= lock_tx_b_spend_decoded['size'])
assert (expect_size - lock_tx_b_spend_decoded['size'] < 10)

View File

@@ -84,6 +84,8 @@ from tests.basicswap.common import (
BASE_ZMQ_PORT,
BTC_BASE_PORT,
BTC_BASE_RPC_PORT,
BCH_BASE_PORT,
BCH_BASE_RPC_PORT,
LTC_BASE_PORT,
LTC_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST,
@@ -99,6 +101,7 @@ logger = logging.getLogger()
NUM_NODES = 3
NUM_XMR_NODES = 3
NUM_BTC_NODES = 3
NUM_BCH_NODES = 3
NUM_LTC_NODES = 3
TEST_DIR = cfg.TEST_DATADIRS
@@ -216,6 +219,18 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_c
'use_segwit': True,
}
if Coins.BCH in with_coins:
settings['chainclients']['bitcoincash'] = {
'connection_type': 'rpc',
'manage_daemon': False,
'rpcport': BCH_BASE_RPC_PORT + node_id,
'rpcuser': 'test' + str(node_id),
'rpcpassword': 'test_pass' + str(node_id),
'datadir': os.path.join(datadir, 'bch_' + str(node_id)),
'bindir': cfg.BITCOINCASH_BINDIR,
'use_segwit': False,
}
if cls:
cls.addCoinSettings(settings, datadir, node_id)
@@ -226,6 +241,8 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_c
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 bchCli(cmd, node_id=0):
return callrpc_cli(cfg.BITCOINCASH_BINDIR, os.path.join(TEST_DIR, 'bch_' + str(node_id)), 'regtest', cmd, cfg.BITCOINCASH_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)
@@ -294,17 +311,20 @@ class BaseTest(unittest.TestCase):
swap_clients = []
part_daemons = []
btc_daemons = []
bch_daemons = []
ltc_daemons = []
xmr_daemons = []
xmr_wallet_auth = []
restore_instance = False
start_bch_nodes = False
start_ltc_nodes = False
start_xmr_nodes = True
has_segwit = True
xmr_addr = None
btc_addr = None
bch_addr = None
ltc_addr = None
@classmethod
@@ -405,6 +425,20 @@ class BaseTest(unittest.TestCase):
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), test_delay_event)
for i in range(NUM_BCH_NODES):
if not cls.restore_instance:
data_dir = prepareDataDir(TEST_DIR, i, 'bitcoin.conf', 'bch_', base_p2p_port=BCH_BASE_PORT, base_rpc_port=BCH_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.BITCOINCASH_BINDIR, 'bitcoin-wallet')):
try:
callrpc_cli(cfg.BITCOINCASH_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
except Exception as e:
logging.warning('bch: bitcoin-wallet create failed')
raise e
cls.bch_daemons.append(startDaemon(os.path.join(TEST_DIR, 'bch_' + str(i)), cfg.BITCOINCASH_BINDIR, cfg.BITCOINCASHD))
logging.info('Bch: Started %s %d', cfg.BITCOINCASHD, cls.part_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=BCH_BASE_RPC_PORT), test_delay_event)
if cls.start_ltc_nodes:
for i in range(NUM_LTC_NODES):
if not cls.restore_instance:
@@ -466,6 +500,8 @@ class BaseTest(unittest.TestCase):
start_nodes.add(Coins.LTC)
if cls.start_xmr_nodes:
start_nodes.add(Coins.XMR)
if cls.start_bch_nodes:
start_nodes.add(Coins.BCH)
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)))
@@ -483,6 +519,8 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes:
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].handle.pid)
if cls.start_bch_nodes:
sc.setDaemonPID(Coins.BCH, cls.bch_daemons[i].handle.pid)
cls.addPIDInfo(sc, i)
sc.start()
@@ -502,6 +540,8 @@ class BaseTest(unittest.TestCase):
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']
if cls.start_bch_nodes:
cls.bch_addr = cls.swap_clients[0].ci(Coins.BCH).pubkey_to_address(void_block_rewards_pubkey)
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
@@ -550,6 +590,12 @@ class BaseTest(unittest.TestCase):
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat'))
if cls.start_bch_nodes:
num_blocks = 200
cls.bch_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=BCH_BASE_RPC_PORT, wallet='wallet.dat')
logging.info('Mining %d BitcoinCash blocks to %s', num_blocks, cls.bch_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.bch_addr], base_rpc_port=BCH_BASE_RPC_PORT, wallet='wallet.dat')
num_blocks = 100
if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
@@ -612,12 +658,14 @@ class BaseTest(unittest.TestCase):
stopDaemons(cls.xmr_daemons)
stopDaemons(cls.part_daemons)
stopDaemons(cls.btc_daemons)
stopDaemons(cls.bch_daemons)
stopDaemons(cls.ltc_daemons)
cls.http_threads.clear()
cls.swap_clients.clear()
cls.part_daemons.clear()
cls.btc_daemons.clear()
cls.bch_daemons.clear()
cls.ltc_daemons.clear()
cls.xmr_daemons.clear()
@@ -643,6 +691,8 @@ class BaseTest(unittest.TestCase):
def coins_loop(cls):
if cls.btc_addr is not None:
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
if cls.bch_addr is not None:
ltcCli('generatetoaddress 1 {}'.format(cls.bch_addr))
if cls.ltc_addr is not None:
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
if cls.xmr_addr is not None: