mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
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:
@@ -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')
|
||||
|
||||
11
tests/basicswap/test_bch.py
Normal file
11
tests/basicswap/test_bch.py
Normal 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())
|
||||
206
tests/basicswap/test_bch_xmr.py
Normal file
206
tests/basicswap/test_bch_xmr.py
Normal 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)
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user