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

@ -16,6 +16,7 @@ test_task:
- BIN_DIR: /tmp/cached_bin - BIN_DIR: /tmp/cached_bin
- PARTICL_BINDIR: ${BIN_DIR}/particl - PARTICL_BINDIR: ${BIN_DIR}/particl
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin - BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
- BITCOINCASH_BINDIR: ${BIN_DIR}/bitcoincash
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin - LITECOIN_BINDIR: ${BIN_DIR}/litecoin
- XMR_BINDIR: ${BIN_DIR}/monero - XMR_BINDIR: ${BIN_DIR}/monero
setup_script: setup_script:
@ -29,7 +30,7 @@ test_task:
fingerprint_script: fingerprint_script:
- basicswap-prepare -v - basicswap-prepare -v
populate_script: populate_script:
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,litecoin,monero - basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,bitcoincash,litecoin,monero
script: script:
- cd "${CIRRUS_WORKING_DIR}" - cd "${CIRRUS_WORKING_DIR}"
- export DATADIRS="${TEST_DIR}" - export DATADIRS="${TEST_DIR}"

3
.gitignore vendored
View file

@ -13,3 +13,6 @@ __pycache__
# geckodriver.log # geckodriver.log
*.log *.log
docker/.env docker/.env
# vscode dev container settings
compose-dev.yaml

View file

@ -251,6 +251,7 @@ class BasicSwap(BaseApp):
protocolInterfaces = { protocolInterfaces = {
SwapTypes.SELLER_FIRST: atomic_swap_1.AtomicSwapInterface(), SwapTypes.SELLER_FIRST: atomic_swap_1.AtomicSwapInterface(),
SwapTypes.XMR_SWAP: xmr_swap_1.XmrSwapInterface(), SwapTypes.XMR_SWAP: xmr_swap_1.XmrSwapInterface(),
SwapTypes.XMR_BCH_SWAP: xmr_swap_1.XmrBchSwapInterface(),
} }
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap', transient_instance=False): def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap', transient_instance=False):
@ -688,6 +689,9 @@ class BasicSwap(BaseApp):
elif coin == Coins.BTC: elif coin == Coins.BTC:
from .interface.btc import BTCInterface from .interface.btc import BTCInterface
return BTCInterface(self.coin_clients[coin], self.chain, self) return BTCInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.BCH:
from .interface.bch import BCHInterface
return BCHInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.LTC: elif coin == Coins.LTC:
from .interface.ltc import LTCInterface, LTCInterfaceMWEB from .interface.ltc import LTCInterface, LTCInterfaceMWEB
interface = LTCInterface(self.coin_clients[coin], self.chain, self) interface = LTCInterface(self.coin_clients[coin], self.chain, self)

View file

@ -64,6 +64,7 @@ class SwapTypes(IntEnum):
SELLER_FIRST_2MSG = auto() SELLER_FIRST_2MSG = auto()
BUYER_FIRST_2MSG = auto() BUYER_FIRST_2MSG = auto()
XMR_SWAP = auto() XMR_SWAP = auto()
XMR_BCH_SWAP = auto()
class OfferStates(IntEnum): class OfferStates(IntEnum):

53
basicswap/bin/prepare.py Executable file → Normal file
View file

@ -49,6 +49,9 @@ LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0') BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0')
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '') BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
BITCOINCASH_VERSION = os.getenv('BITCOIN_VERSION', '27.1.0')
BITCOINCASH_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.4') MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.4')
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '') MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
XMR_SITE_COMMIT = '3751c0d7987a9e78324a718c32c008e2ec91b339' # Lock hashes.txt to monero version XMR_SITE_COMMIT = '3751c0d7987a9e78324a718c32c008e2ec91b339' # Lock hashes.txt to monero version
@ -84,6 +87,7 @@ SKIP_GPG_VALIDATION = toBool(os.getenv('SKIP_GPG_VALIDATION', 'false'))
known_coins = { known_coins = {
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)), 'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)), 'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
'bitcoincash': (BITCOINCASH_VERSION, BITCOINCASH_VERSION_TAG, ('Calin_Culianu',)),
'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)), 'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)),
'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)), 'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)),
'namecoin': ('0.18.0', '', ('JeremyRand',)), 'namecoin': ('0.18.0', '', ('JeremyRand',)),
@ -113,6 +117,7 @@ expected_key_ids = {
'reuben': ('1290A1D0FA7EE109',), 'reuben': ('1290A1D0FA7EE109',),
'nav_builder': ('2782262BF6E7FADB',), 'nav_builder': ('2782262BF6E7FADB',),
'decred_release': ('6D897EDF518A031D',), 'decred_release': ('6D897EDF518A031D',),
'Calin_Culianu': ('21810A542031C02C',),
} }
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system()) USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
@ -186,6 +191,12 @@ BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
BTC_RPC_USER = os.getenv('BTC_RPC_USER', '') BTC_RPC_USER = os.getenv('BTC_RPC_USER', '')
BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '') BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
BCH_RPC_HOST = os.getenv('BCH_RPC_HOST', '127.0.0.1')
BCH_RPC_PORT = int(os.getenv('BCH_RPC_PORT', 19997))
BCH_ONION_PORT = int(os.getenv('BCH_ONION_PORT', 8334))
BCH_RPC_USER = os.getenv('BCH_RPC_USER', '')
BCH_RPC_PWD = os.getenv('BCH_RPC_PWD', '')
DCR_RPC_HOST = os.getenv('DCR_RPC_HOST', '127.0.0.1') DCR_RPC_HOST = os.getenv('DCR_RPC_HOST', '127.0.0.1')
DCR_RPC_PORT = int(os.getenv('DCR_RPC_PORT', 9109)) DCR_RPC_PORT = int(os.getenv('DCR_RPC_PORT', 9109))
DCR_WALLET_RPC_HOST = os.getenv('DCR_WALLET_RPC_HOST', '127.0.0.1') DCR_WALLET_RPC_HOST = os.getenv('DCR_WALLET_RPC_HOST', '127.0.0.1')
@ -513,8 +524,14 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
return return
dir_name = 'dashcore' if coin == 'dash' else coin dir_name = 'dashcore' if coin == 'dash' else coin
dir_name = 'bitcoin-cash-node' if coin == 'bitcoincash' else coin
if coin == 'decred': if coin == 'decred':
bins = ['dcrd', 'dcrwallet'] bins = ['dcrd', 'dcrwallet']
elif coin == 'bitcoincash':
bins = ['bitcoind', 'bitcoin-cli', 'bitcoin-tx']
versions = version.split('.')
if int(versions[0]) >= 22 or int(versions[1]) >= 19:
bins.append('bitcoin-wallet')
else: else:
bins = [coin + 'd', coin + '-cli', coin + '-tx'] bins = [coin + 'd', coin + '-cli', coin + '-tx']
versions = version.split('.') versions = version.split('.')
@ -696,6 +713,11 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
assert_url = f'https://raw.githubusercontent.com/bitcoin-core/guix.sigs/main/{version}/{signing_key_name}/all.SHA256SUMS' assert_url = f'https://raw.githubusercontent.com/bitcoin-core/guix.sigs/main/{version}/{signing_key_name}/all.SHA256SUMS'
else: else:
assert_url = 'https://raw.githubusercontent.com/bitcoin-core/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename) assert_url = 'https://raw.githubusercontent.com/bitcoin-core/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
elif coin == 'bitcoincash':
release_filename = 'bitcoin-cash-node-{}-{}.{}'.format(version, BIN_ARCH, FILE_EXT)
release_url = 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v{}/{}'.format(version, release_filename)
assert_filename = 'SHA256SUMS.{}.asc.Calin_Culianu'.format(version)
assert_url = 'https://gitlab.com/bitcoin-cash-node/announcements/-/raw/master/release-sigs/%s/%s' % (version, assert_filename)
elif coin == 'namecoin': elif coin == 'namecoin':
release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename) release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0]) assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
@ -738,7 +760,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not os.path.exists(assert_path): if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path) downloadFile(assert_url, assert_path)
if coin not in ('firo', ): if coin not in ('firo', 'bitcoincash',):
assert_sig_url = assert_url + ('.asc' if use_guix else '.sig') assert_sig_url = assert_url + ('.asc' if use_guix else '.sig')
if coin not in ('nav', ): if coin not in ('nav', ):
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name) assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
@ -798,11 +820,13 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
pubkeyurls.append('https://git.wownero.com/wownero/wownero/raw/branch/master/utils/gpg_keys/wowario.asc') pubkeyurls.append('https://git.wownero.com/wownero/wownero/raw/branch/master/utils/gpg_keys/wowario.asc')
if coin == 'firo': if coin == 'firo':
pubkeyurls.append('https://firo.org/reuben.asc') pubkeyurls.append('https://firo.org/reuben.asc')
if coin == 'bitcoincash':
pubkeyurls.append('https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/raw/master/contrib/gitian-signing/pubkeys.txt')
if ADD_PUBKEY_URL != '': if ADD_PUBKEY_URL != '':
pubkeyurls.append(ADD_PUBKEY_URL + '/' + pubkey_filename) pubkeyurls.append(ADD_PUBKEY_URL + '/' + pubkey_filename)
if coin in ('monero', 'wownero', 'firo'): if coin in ('monero', 'wownero', 'firo', 'bitcoincash',):
with open(assert_path, 'rb') as fp: with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp) verified = gpg.verify_file(fp)
@ -837,7 +861,6 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
verified = gpg.verify_file(fp, assert_path) verified = gpg.verify_file(fp, assert_path)
ensureValidSignatureBy(verified, signing_key_name) ensureValidSignatureBy(verified, signing_key_name)
extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts) extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts)
@ -1032,6 +1055,10 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write('fallbackfee=0.0002\n') fp.write('fallbackfee=0.0002\n')
if BTC_RPC_USER != '': if BTC_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD))) fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
elif coin == 'bitcoincash':
fp.write('prune=2000\n')
if BCH_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(BCH_RPC_USER, salt, password_to_hmac(salt, BCH_RPC_PWD)))
elif coin == 'namecoin': elif coin == 'namecoin':
fp.write('prune=2000\n') fp.write('prune=2000\n')
elif coin == 'pivx': elif coin == 'pivx':
@ -1182,6 +1209,8 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
default_onionport = 0 default_onionport = 0
if coin == 'bitcoin': if coin == 'bitcoin':
default_onionport = BTC_ONION_PORT default_onionport = BTC_ONION_PORT
if coin == 'bitcoincash':
default_onionport = BCH_ONION_PORT
elif coin == 'particl': elif coin == 'particl':
default_onionport = PART_ONION_PORT default_onionport = PART_ONION_PORT
elif coin == 'litecoin': elif coin == 'litecoin':
@ -1353,7 +1382,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
pass pass
else: else:
if coin_settings['manage_daemon']: if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') filename = (coin_name if not coin_name == "bitcoincash" else "bitcoin") + 'd' + ('.exe' if os.name == 'nt' else '')
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else [] coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else []
if c == Coins.FIRO: if c == Coins.FIRO:
@ -1755,6 +1784,19 @@ def main():
'conf_target': 2, 'conf_target': 2,
'core_version_group': 22, 'core_version_group': 22,
}, },
'bitcoincash': {
'connection_type': 'rpc' if 'bitcoincash' in with_coins else 'none',
'manage_daemon': True if ('bitcoincash' in with_coins and BCH_RPC_HOST == '127.0.0.1') else False,
'rpchost': BCH_RPC_HOST,
'rpcport': BCH_RPC_PORT + port_offset,
'onionport': BCH_ONION_PORT + port_offset,
'datadir': os.getenv('BCH_DATA_DIR', os.path.join(data_dir, 'bitcoincash')),
'bindir': os.path.join(bin_dir, 'bitcoincash'),
'use_segwit': False,
'blocks_confirmed': 1,
'conf_target': 2,
'core_version_group': 22,
},
'litecoin': { 'litecoin': {
'connection_type': 'rpc', 'connection_type': 'rpc',
'manage_daemon': shouldManageDaemon('LTC'), 'manage_daemon': shouldManageDaemon('LTC'),
@ -1917,6 +1959,9 @@ def main():
if BTC_RPC_USER != '': if BTC_RPC_USER != '':
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
if BCH_RPC_USER != '':
chainclients['bitcoin']['rpcuser'] = BCH_RPC_USER
chainclients['bitcoin']['rpcpassword'] = BCH_RPC_PWD
if XMR_RPC_USER != '': if XMR_RPC_USER != '':
chainclients['monero']['rpcuser'] = XMR_RPC_USER chainclients['monero']['rpcuser'] = XMR_RPC_USER
chainclients['monero']['rpcpassword'] = XMR_RPC_PWD chainclients['monero']['rpcpassword'] = XMR_RPC_PWD

View file

@ -214,7 +214,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
if c in ('monero', 'wownero'): if c in ('monero', 'wownero'):
if v['manage_daemon'] is True: if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon') swap_client.log.info(f'Starting {display_name} daemon')
filename = c + 'd' + ('.exe' if os.name == 'nt' else '') filename = (c if not c == "bitcoincash" else "bitcoin") + 'd' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename)) daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename))
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info('Started {} {}'.format(filename, pid))
@ -280,7 +280,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
if v['manage_daemon'] is True: if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon') swap_client.log.info(f'Starting {display_name} daemon')
filename = c + 'd' + ('.exe' if os.name == 'nt' else '') filename = (c if not c == "bitcoincash" else "bitcoin") + 'd' + ('.exe' if os.name == 'nt' else '')
daemons.append(startDaemon(v['datadir'], v['bindir'], filename)) daemons.append(startDaemon(v['datadir'], v['bindir'], filename))
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
pids.append((c, pid)) pids.append((c, pid))

View file

@ -30,6 +30,7 @@ class Coins(IntEnum):
NAV = 14 NAV = 14
LTC_MWEB = 15 LTC_MWEB = 15
# ZANO = 16 # ZANO = 16
BCH = 17
chainparams = { chainparams = {
@ -432,7 +433,45 @@ chainparams = {
'min_amount': 1000, 'min_amount': 1000,
'max_amount': 100000 * COIN, 'max_amount': 100000 * COIN,
} }
},
Coins.BCH: {
'name': 'bitcoincash',
'ticker': 'BCH',
'message_magic': 'Bitcoin Signed Message:\n',
'blocks_target': 60 * 2,
'decimal_places': 8,
'mainnet': {
'rpcport': 8332,
'pubkey_address': 0,
'script_address': 5,
'key_prefix': 128,
'hrp': 'bitcoincash',
'bip44': 0,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'testnet': {
'rpcport': 18332,
'pubkey_address': 111,
'script_address': 196,
'key_prefix': 239,
'hrp': 'bchtest',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
'name': 'testnet3',
},
'regtest': {
'rpcport': 18443,
'pubkey_address': 111,
'script_address': 196,
'key_prefix': 239,
'hrp': 'bchreg',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
} }
},
} }
ticker_map = {} ticker_map = {}

View file

@ -36,3 +36,8 @@ NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero'))) XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix) XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
BITCOINCASH_BINDIR = os.path.expanduser(os.getenv('BITCOINCASH_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'bitcoincash')))
BITCOINCASHD = os.getenv('BITCOINCASHD', 'bitcoind' + bin_suffix)
BITCOINCASH_CLI = os.getenv('BITCOINCASH_CLI', 'bitcoin-cli' + bin_suffix)
BITCOINCASH_TX = os.getenv('BITCOINCASH_TX', 'bitcoin-tx' + bin_suffix)

119
basicswap/interface/bch.py Normal file
View file

@ -0,0 +1,119 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from typing import Union
from basicswap.contrib.test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut
from basicswap.util import ensure, i2h
from .btc import BTCInterface, findOutput
from basicswap.rpc import make_rpc_func
from basicswap.chainparams import Coins, chainparams
from basicswap.interface.contrib.bch_test_framework.cashaddress import Address
from basicswap.util.crypto import hash160, sha256
from basicswap.interface.contrib.bch_test_framework.script import OP_EQUAL, OP_EQUALVERIFY, OP_HASH256, OP_DUP, OP_HASH160, OP_CHECKSIG
from basicswap.contrib.test_framework.script import (
CScript, CScriptOp,
)
class BCHInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.BCH
def __init__(self, coin_settings, network, swap_client=None):
super(BCHInterface, self).__init__(coin_settings, network, swap_client)
def decodeAddress(self, address: str) -> bytes:
return bytes(Address.from_string(address).payload)
def pubkey_to_segwit_address(self, pk: bytes) -> str:
raise NotImplementedError()
def pkh_to_address(self, pkh: bytes) -> str:
# pkh is ripemd160(sha256(pk))
assert (len(pkh) == 20)
prefix = self.chainparams_network()['hrp']
address = Address("P2PKH", b'\x76\xa9\x14' + pkh + b'\x88\xac')
address.prefix = prefix
return address.cash_address()
def getNewAddress(self, use_segwit: bool = False, label: str = 'swap_receive') -> str:
args = [label]
return self.rpc_wallet('getnewaddress', args)
def addressToLockingBytecode(self, address: str) -> bytes:
return b'\x76\xa9\x14' + bytes(Address.from_string(address).payload) + b'\x88\xac'
def getScriptDest(self, script):
return self.scriptToP2SH32LockingBytecode(script)
def scriptToP2SH32LockingBytecode(self, script: Union[bytes, str]) -> bytes:
if isinstance(script, str):
script = bytes.fromhex(script)
return CScript([
CScriptOp(OP_HASH256),
sha256(sha256(script)),
CScriptOp(OP_EQUAL),
])
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
return tx.serialize_without_witness()
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
return CScript([
CScriptOp(OP_DUP),
CScriptOp(OP_HASH160),
pkh,
CScriptOp(OP_EQUALVERIFY),
CScriptOp(OP_CHECKSIG),
])
def getTxSize(self, tx: CTransaction) -> int:
return len(tx.serialize_without_witness())
def getScriptScriptSig(self, script: bytes, ves: bytes) -> bytes:
if ves is not None:
return CScript([ves, script])
else:
return CScript([script])
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, ves=None, fee_info={}):
# tx_fee_rate in this context is equal to `mining_fee` contract param
tx_lock = self.loadTx(tx_lock_bytes)
output_script = self.getScriptDest(script_lock)
locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock.vout[locked_n].nValue
tx_lock.rehash()
tx_lock_id_int = tx_lock.sha256
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
scriptSig=self.getScriptScriptSig(script_lock, ves),
nSequence=0))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
pay_fee = tx_fee_rate
tx.vout[0].nValue = locked_coin - pay_fee
size = self.getTxSize(tx)
fee_info['fee_paid'] = pay_fee
fee_info['rate_used'] = tx_fee_rate
fee_info['size'] = size
tx.rehash()
self._log.info('createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, size, pay_fee)
return tx.serialize_without_witness()

View file

@ -0,0 +1,247 @@
import unittest
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def polymod(values):
chk = 1
generator = [
(0x01, 0x98F2BC8E61),
(0x02, 0x79B76D99E2),
(0x04, 0xF33E5FB3C4),
(0x08, 0xAE2EABE2A8),
(0x10, 0x1E4F43E470),
]
for value in values:
top = chk >> 35
chk = ((chk & 0x07FFFFFFFF) << 5) ^ value
for i in generator:
if top & i[0] != 0:
chk ^= i[1]
return chk ^ 1
def calculate_checksum(prefix, payload):
poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0])
out = list()
for i in range(8):
out.append((poly >> 5 * (7 - i)) & 0x1F)
return out
def verify_checksum(prefix, payload):
return polymod(prefix_expand(prefix) + payload) == 0
def b32decode(inputs):
out = list()
for letter in inputs:
out.append(CHARSET.find(letter))
return out
def b32encode(inputs):
out = ""
for char_code in inputs:
out += CHARSET[char_code]
return out
def convertbits(data, frombits, tobits, pad=True):
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
def prefix_expand(prefix):
return [ord(x) & 0x1F for x in prefix] + [0]
class Address:
"""
Class to handle CashAddr.
:param version: Version of CashAddr
:type version: ``str``
:param payload: Payload of CashAddr as int list of the bytearray
:type payload: ``list`` of ``int``
"""
VERSIONS = {
"P2SH20": {"prefix": "bitcoincash", "version_bit": 8, "network": "mainnet"},
"P2SH32": {"prefix": "bitcoincash", "version_bit": 11, "network": "mainnet"},
"P2PKH": {"prefix": "bitcoincash", "version_bit": 0, "network": "mainnet"},
"P2SH20-TESTNET": {"prefix": "bchtest", "version_bit": 8, "network": "testnet"},
"P2SH32-TESTNET": {
"prefix": "bchtest",
"version_bit": 11,
"network": "testnet",
},
"P2PKH-TESTNET": {"prefix": "bchtest", "version_bit": 0, "network": "testnet"},
"P2SH20-REGTEST": {"prefix": "bchreg", "version_bit": 8, "network": "regtest"},
"P2SH32-REGTEST": {"prefix": "bchreg", "version_bit": 11, "network": "regtest"},
"P2PKH-REGTEST": {"prefix": "bchreg", "version_bit": 0, "network": "regtest"},
"P2SH20-CATKN": {
"prefix": "bitcoincash",
"version_bit": 24,
"network": "mainnet",
},
"P2SH32-CATKN": {
"prefix": "bitcoincash",
"version_bit": 27,
"network": "mainnet",
},
"P2PKH-CATKN": {
"prefix": "bitcoincash",
"version_bit": 16,
"network": "mainnet",
},
"P2SH20-CATKN-TESTNET": {
"prefix": "bchtest",
"version_bit": 24,
"network": "testnet",
},
"P2SH32-CATKN-TESTNET": {
"prefix": "bchtest",
"version_bit": 27,
"network": "testnet",
},
"P2PKH-CATKN-TESTNET": {
"prefix": "bchtest",
"version_bit": 16,
"network": "testnet",
},
"P2SH20-CATKN-REGTEST": {
"prefix": "bchreg",
"version_bit": 24,
"network": "regtest",
},
"P2SH32-CATKN-REGTEST": {
"prefix": "bchreg",
"version_bit": 27,
"network": "regtest",
},
"P2PKH-CATKN-REGTEST": {
"prefix": "bchreg",
"version_bit": 16,
"network": "regtest",
},
}
VERSION_SUFFIXES = {"bitcoincash": "", "bchtest": "-TESTNET", "bchreg": "-REGTEST"}
ADDRESS_TYPES = {
0: "P2PKH",
8: "P2SH20",
11: "P2SH32",
16: "P2PKH-CATKN",
24: "P2SH20-CATKN",
27: "P2SH32-CATKN",
}
def __init__(self, version, payload):
if version not in Address.VERSIONS:
raise ValueError("Invalid address version provided")
self.version = version
self.payload = payload
self.prefix = Address.VERSIONS[self.version]["prefix"]
def __str__(self):
return (
f"version: {self.version}\npayload: {self.payload}\nprefix: {self.prefix}"
)
def __repr__(self):
return f"Address('{self.cash_address()}')"
def __eq__(self, other):
if isinstance(other, str):
return self.cash_address() == other
elif isinstance(other, Address):
return self.cash_address() == other.cash_address()
else:
raise ValueError(
"Address can be compared to a string address"
" or an instance of Address"
)
def cash_address(self):
"""
Generate CashAddr of the Address
:rtype: ``str``
"""
version_bit = Address.VERSIONS[self.version]["version_bit"]
payload = [version_bit] + self.payload
payload = convertbits(payload, 8, 5)
checksum = calculate_checksum(self.prefix, payload)
return self.prefix + ":" + b32encode(payload + checksum)
@staticmethod
def from_string(address):
"""
Generate Address from a cashadress string
:param scriptcode: The cashaddress string
:type scriptcode: ``str``
:returns: Instance of :class:~bitcash.cashaddress.Address
"""
try:
address = str(address)
except Exception:
raise ValueError("Expected string as input")
if address.upper() != address and address.lower() != address:
raise ValueError(
"Cash address contains uppercase and lowercase characters"
)
address = address.lower()
colon_count = address.count(":")
if colon_count == 0:
raise ValueError("Cash address is missing prefix")
if colon_count > 1:
raise ValueError("Cash address contains more than one colon character")
prefix, base32string = address.split(":")
decoded = b32decode(base32string)
if not verify_checksum(prefix, decoded):
raise ValueError(
"Bad cash address checksum for address {}".format(address)
)
converted = convertbits(decoded, 5, 8)
try:
version = Address.ADDRESS_TYPES[converted[0]]
except Exception:
raise ValueError("Could not determine address version")
version += Address.VERSION_SUFFIXES[prefix]
payload = converted[1:-6]
return Address(version, payload)
class TestFrameworkScript(unittest.TestCase):
def test_base58encodedecode(self):
def check_cashaddress(address: str):
self.assertEqual(Address.from_string(address).cash_address(), address)
check_cashaddress("bitcoincash:qzfyvx77v2pmgc0vulwlfkl3uzjgh5gnmqk5hhyaa6")

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
OP_TXINPUTCOUNT = 0xc3
OP_1 = 0x51
OP_NUMEQUALVERIFY = 0x9d
OP_TXOUTPUTCOUNT = 0xc4
OP_0 = 0x00
OP_UTXOVALUE = 0xc6
OP_OUTPUTVALUE = 0xcc
OP_SUB = 0x94
OP_UTXOTOKENCATEGORY = 0xce
OP_OUTPUTTOKENCATEGORY = 0xd1
OP_EQUALVERIFY = 0x88
OP_UTXOTOKENCOMMITMENT = 0xcf
OP_OUTPUTTOKENCOMMITMENT = 0xd2
OP_UTXOTOKENAMOUNT = 0xd0
OP_OUTPUTTOKENAMOUNT = 0xd3
OP_INPUTSEQUENCENUMBER = 0xcb
OP_NOTIF = 0x64
OP_OUTPUTBYTECODE = 0xcd
OP_OVER = 0x78
OP_CHECKDATASIG = 0xba
OP_CHECKDATASIGVERIFY = 0xbb
OP_ELSE = 0x67
OP_CHECKSEQUENCEVERIFY = 0xb2
OP_DROP = 0x75
OP_EQUAL = 0x87
OP_ENDIF = 0x68
OP_HASH256 = 0xaa
OP_PUSHBYTES_32 = 0x20
OP_DUP = 0x76
OP_HASH160 = 0xa9
OP_CHECKSIG = 0xac
OP_SHA256 = 0xa8
OP_VERIFY = 0x69

View file

@ -6,6 +6,34 @@
import traceback import traceback
import unittest
from basicswap.interface.contrib.bch_test_framework.script import (
OP_TXINPUTCOUNT,
OP_1,
OP_NUMEQUALVERIFY,
OP_TXOUTPUTCOUNT,
OP_0,
OP_UTXOVALUE,
OP_OUTPUTVALUE,
OP_SUB,
OP_UTXOTOKENCATEGORY,
OP_OUTPUTTOKENCATEGORY,
OP_EQUALVERIFY,
OP_UTXOTOKENCOMMITMENT,
OP_OUTPUTTOKENCOMMITMENT,
OP_UTXOTOKENAMOUNT,
OP_OUTPUTTOKENAMOUNT,
OP_INPUTSEQUENCENUMBER,
OP_NOTIF,
OP_OUTPUTBYTECODE,
OP_OVER,
OP_CHECKDATASIG,
OP_ELSE,
OP_CHECKSEQUENCEVERIFY,
OP_DROP,
OP_EQUAL,
OP_ENDIF,
)
from basicswap.util import ( from basicswap.util import (
ensure, ensure,
) )
@ -193,3 +221,82 @@ class XmrSwapInterface(ProtocolInterface):
ctx.nLockTime = 0 ctx.nLockTime = 0
return ctx.serialize() return ctx.serialize()
class XmrBchSwapInterface(ProtocolInterface):
swap_type = SwapTypes.XMR_BCH_SWAP
def genScriptLockTxScript(self, mining_fee: int, out_1: bytes, out_2: bytes, public_key: bytes, timelock: int) -> CScript:
return CScript([
# // v4.1.0-CashTokens-Optimized
# // Based on swaplock.cash v4.1.0-CashTokens
#
# // Alice has XMR, wants BCH and/or CashTokens.
# // Bob has BCH and/or CashTokens, wants XMR.
#
# // Verify 1-in-1-out TX form
CScriptOp(OP_TXINPUTCOUNT),
CScriptOp(OP_1), CScriptOp(OP_NUMEQUALVERIFY),
CScriptOp(OP_TXOUTPUTCOUNT),
CScriptOp(OP_1), CScriptOp(OP_NUMEQUALVERIFY),
# // int miningFee
mining_fee,
# // Verify pre-agreed mining fee and that the rest of BCH is forwarded
# // to the output.
CScriptOp(OP_0), CScriptOp(OP_UTXOVALUE),
CScriptOp(OP_0), CScriptOp(OP_OUTPUTVALUE),
CScriptOp(OP_SUB), CScriptOp(OP_NUMEQUALVERIFY),
# # // Verify that any CashTokens are forwarded to the output.
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENCATEGORY),
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENCATEGORY),
CScriptOp(OP_EQUALVERIFY),
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENCOMMITMENT),
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENCOMMITMENT),
CScriptOp(OP_EQUALVERIFY),
CScriptOp(OP_0), CScriptOp(OP_UTXOTOKENAMOUNT),
CScriptOp(OP_0), CScriptOp(OP_OUTPUTTOKENAMOUNT),
CScriptOp(OP_NUMEQUALVERIFY),
# // If sequence is not used then it is a regular swap TX.
CScriptOp(OP_0), CScriptOp(OP_INPUTSEQUENCENUMBER),
CScriptOp(OP_NOTIF),
# // bytes aliceOutput
out_1,
# // Verify that the BCH and/or CashTokens are forwarded to Alice's
# // output.
CScriptOp(OP_0), CScriptOp(OP_OUTPUTBYTECODE),
CScriptOp(OP_OVER), CScriptOp(OP_EQUALVERIFY),
# // pubkey bobPubkeyVES
public_key,
# // Require Alice to decrypt and publish Bob's VES signature.
# // The "message" signed is simply a sha256 hash of Alice's output
# // locking bytecode.
# // By decrypting Bob's VES and publishing it, Alice reveals her
# // XMR key share to Bob.
CScriptOp(OP_CHECKDATASIG),
# // If a TX using this path is mined then Alice gets her BCH.
# // Bob uses the revealed XMR key share to collect his XMR.
# // Refund will become available when timelock expires, and it would
# // expire because Alice didn't collect on time, either of her own accord
# // or because Bob bailed out and witheld the encrypted signature.
CScriptOp(OP_ELSE),
# // int timelock_0
timelock,
# // Verify refund timelock.
CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
# // bytes refundLockingBytecode
out_2,
# // Verify that the BCH and/or CashTokens are forwarded to Refund
# // contract.
CScriptOp(OP_0), CScriptOp(OP_OUTPUTBYTECODE),
CScriptOp(OP_EQUAL),
# // BCH and/or CashTokens are simply forwarded to Refund contract.
CScriptOp(OP_ENDIF)
])

View file

@ -0,0 +1,27 @@
# https://github.com/NicolasDorier/docker-bitcoin/blob/master/README.md
FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoincash --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin .
ENV BITCOIN_DATA /data
RUN groupadd -r bitcoin && useradd -r -m -g bitcoin bitcoin \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir "$BITCOIN_DATA" \
&& chown -R bitcoin:bitcoin "$BITCOIN_DATA" \
&& ln -sfn "$BITCOIN_DATA" /home/bitcoin/.bitcoin \
&& chown -h bitcoin:bitcoin /home/bitcoin/.bitcoin
VOLUME /data
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 8332 8333 18332 18333 18443 18444
CMD ["/bitcoin/bitcoind", "--datadir=/data"]

View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "bitcoin-cli" || "$1" == "bitcoin-tx" || "$1" == "bitcoind" || "$1" == "test_bitcoin" ]]; then
mkdir -p "$BITCOIN_DATA"
chown -h bitcoin:bitcoin /home/bitcoin/.bitcoin
exec gosu bitcoin "$@"
else
exec "$@"
fi

View file

@ -21,6 +21,12 @@ BTC_RPC_PORT=19796
BTC_RPC_USER=bitcoin_user BTC_RPC_USER=bitcoin_user
BTC_RPC_PWD=bitcoin_pwd BTC_RPC_PWD=bitcoin_pwd
BCH_DATA_DIR=/data/bitcoincash
BCH_RPC_HOST=bitcoincash_core
BCH_RPC_PORT=19797
BCH_RPC_USER=bitcoincash_user
BCH_RPC_PWD=bitcoincash_pwd
LTC_DATA_DIR=/data/litecoin LTC_DATA_DIR=/data/litecoin
LTC_RPC_HOST=litecoin_core LTC_RPC_HOST=litecoin_core
LTC_RPC_PORT=19795 LTC_RPC_PORT=19795

View file

@ -31,6 +31,11 @@ BTC_BASE_RPC_PORT = 32792
BTC_BASE_ZMQ_PORT = 33792 BTC_BASE_ZMQ_PORT = 33792
BTC_BASE_TOR_PORT = 33732 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_PORT = 34792
LTC_BASE_RPC_PORT = 35792 LTC_BASE_RPC_PORT = 35792
LTC_BASE_ZMQ_PORT = 36792 LTC_BASE_ZMQ_PORT = 36792
@ -75,6 +80,7 @@ def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_P
fp.write('acceptnonstdtxn=0\n') fp.write('acceptnonstdtxn=0\n')
fp.write('txindex=1\n') fp.write('txindex=1\n')
fp.write('wallet=wallet.dat\n') fp.write('wallet=wallet.dat\n')
if not base_p2p_port == BCH_BASE_PORT:
fp.write('findpeers=0\n') fp.write('findpeers=0\n')
if base_p2p_port == BTC_BASE_PORT: if base_p2p_port == BTC_BASE_PORT:

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, BASE_ZMQ_PORT,
BTC_BASE_PORT, BTC_BASE_PORT,
BTC_BASE_RPC_PORT, BTC_BASE_RPC_PORT,
BCH_BASE_PORT,
BCH_BASE_RPC_PORT,
LTC_BASE_PORT, LTC_BASE_PORT,
LTC_BASE_RPC_PORT, LTC_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST, PREFIX_SECRET_KEY_REGTEST,
@ -99,6 +101,7 @@ logger = logging.getLogger()
NUM_NODES = 3 NUM_NODES = 3
NUM_XMR_NODES = 3 NUM_XMR_NODES = 3
NUM_BTC_NODES = 3 NUM_BTC_NODES = 3
NUM_BCH_NODES = 3
NUM_LTC_NODES = 3 NUM_LTC_NODES = 3
TEST_DIR = cfg.TEST_DATADIRS 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, '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: if cls:
cls.addCoinSettings(settings, datadir, node_id) 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): 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) 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): 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) 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 = [] swap_clients = []
part_daemons = [] part_daemons = []
btc_daemons = [] btc_daemons = []
bch_daemons = []
ltc_daemons = [] ltc_daemons = []
xmr_daemons = [] xmr_daemons = []
xmr_wallet_auth = [] xmr_wallet_auth = []
restore_instance = False restore_instance = False
start_bch_nodes = False
start_ltc_nodes = False start_ltc_nodes = False
start_xmr_nodes = True start_xmr_nodes = True
has_segwit = True has_segwit = True
xmr_addr = None xmr_addr = None
btc_addr = None btc_addr = None
bch_addr = None
ltc_addr = None ltc_addr = None
@classmethod @classmethod
@ -405,6 +425,20 @@ class BaseTest(unittest.TestCase):
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), test_delay_event) 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: if cls.start_ltc_nodes:
for i in range(NUM_LTC_NODES): for i in range(NUM_LTC_NODES):
if not cls.restore_instance: if not cls.restore_instance:
@ -466,6 +500,8 @@ class BaseTest(unittest.TestCase):
start_nodes.add(Coins.LTC) start_nodes.add(Coins.LTC)
if cls.start_xmr_nodes: if cls.start_xmr_nodes:
start_nodes.add(Coins.XMR) start_nodes.add(Coins.XMR)
if cls.start_bch_nodes:
start_nodes.add(Coins.BCH)
if not cls.restore_instance: if not cls.restore_instance:
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes, cls) 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))) 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: if cls.start_ltc_nodes:
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].handle.pid) 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) cls.addPIDInfo(sc, i)
sc.start() 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) cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
if cls.start_xmr_nodes: if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] 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: else:
cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
num_blocks = 400 # Mine enough to activate segwit 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')) 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 num_blocks = 100
if cls.start_xmr_nodes: if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
@ -612,12 +658,14 @@ class BaseTest(unittest.TestCase):
stopDaemons(cls.xmr_daemons) stopDaemons(cls.xmr_daemons)
stopDaemons(cls.part_daemons) stopDaemons(cls.part_daemons)
stopDaemons(cls.btc_daemons) stopDaemons(cls.btc_daemons)
stopDaemons(cls.bch_daemons)
stopDaemons(cls.ltc_daemons) stopDaemons(cls.ltc_daemons)
cls.http_threads.clear() cls.http_threads.clear()
cls.swap_clients.clear() cls.swap_clients.clear()
cls.part_daemons.clear() cls.part_daemons.clear()
cls.btc_daemons.clear() cls.btc_daemons.clear()
cls.bch_daemons.clear()
cls.ltc_daemons.clear() cls.ltc_daemons.clear()
cls.xmr_daemons.clear() cls.xmr_daemons.clear()
@ -643,6 +691,8 @@ class BaseTest(unittest.TestCase):
def coins_loop(cls): def coins_loop(cls):
if cls.btc_addr is not None: if cls.btc_addr is not None:
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr)) 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: if cls.ltc_addr is not None:
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr)) ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
if cls.xmr_addr is not None: if cls.xmr_addr is not None: