mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd4291ab45 | ||
|
|
bae735c144 | ||
|
|
18079457e8 | ||
|
|
ef7791cb14 | ||
|
|
4030dc9858 | ||
|
|
811fa15f26 | ||
|
|
4693d96c52 | ||
|
|
3a5e40187a | ||
|
|
c561efaba0 | ||
|
|
9d7841da46 | ||
|
|
09434f20e6 | ||
|
|
92c7cb7223 | ||
|
|
1a085ec97b | ||
|
|
97fcf177a9 | ||
|
|
b43c159dc4 | ||
|
|
5ee28d0aa3 | ||
|
|
5df9b044ab | ||
|
|
51d9685af0 | ||
|
|
8e00753f97 | ||
|
|
c0b94b3d7b | ||
|
|
8637811c05 | ||
|
|
f1dcef4971 | ||
|
|
aa9b9c1507 | ||
|
|
32df813731 | ||
|
|
ec1671911b | ||
|
|
57513aeb06 | ||
|
|
52e6e2b586 | ||
|
|
68256fdcf7 | ||
|
|
c009d555e7 | ||
|
|
58b42c0d9a | ||
|
|
a4b411f1fd |
@@ -6,7 +6,7 @@ lint_task:
|
||||
- pip install flake8 codespell
|
||||
script:
|
||||
- flake8 --version
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503,E702,E131 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
||||
|
||||
test_task:
|
||||
@@ -16,6 +16,7 @@ test_task:
|
||||
- BIN_DIR: /tmp/cached_bin
|
||||
- PARTICL_BINDIR: ${BIN_DIR}/particl
|
||||
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
|
||||
- BITCOINCASH_BINDIR: ${BIN_DIR}/bitcoincash
|
||||
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin
|
||||
- XMR_BINDIR: ${BIN_DIR}/monero
|
||||
setup_script:
|
||||
@@ -29,7 +30,7 @@ test_task:
|
||||
fingerprint_script:
|
||||
- basicswap-prepare -v
|
||||
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:
|
||||
- cd "${CIRRUS_WORKING_DIR}"
|
||||
- export DATADIRS="${TEST_DIR}"
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
pip install flake8 codespell
|
||||
- name: Running flake8
|
||||
run: |
|
||||
PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
||||
flake8 --ignore=E501,F841,W503 --per-file-ignores="basicswap/interface/bch.py:E131,E702" --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
||||
- name: Running codespell
|
||||
run: |
|
||||
codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,3 +13,6 @@ __pycache__
|
||||
# geckodriver.log
|
||||
*.log
|
||||
docker/.env
|
||||
|
||||
# vscode dev container settings
|
||||
compose-dev.yaml
|
||||
@@ -1,3 +1,3 @@
|
||||
name = "basicswap"
|
||||
|
||||
__version__ = "0.14.1"
|
||||
__version__ = "0.14.2"
|
||||
|
||||
@@ -126,7 +126,8 @@ class BaseApp:
|
||||
def callcoincli(self, coin_type, params, wallet=None, timeout=None):
|
||||
bindir = self.coin_clients[coin_type]['bindir']
|
||||
datadir = self.coin_clients[coin_type]['datadir']
|
||||
command_cli = os.path.join(bindir, chainparams[coin_type]['name'] + '-cli' + ('.exe' if os.name == 'nt' else ''))
|
||||
cli_bin: str = chainparams[coin_type].get('cli_binname', chainparams[coin_type]['name'] + '-cli')
|
||||
command_cli = os.path.join(bindir, cli_bin + ('.exe' if os.name == 'nt' else ''))
|
||||
args = [command_cli, ]
|
||||
if self.chain != 'mainnet':
|
||||
args.append('-' + self.chain)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,7 @@ class SwapTypes(IntEnum):
|
||||
SELLER_FIRST_2MSG = auto()
|
||||
BUYER_FIRST_2MSG = auto()
|
||||
XMR_SWAP = auto()
|
||||
XMR_BCH_SWAP = auto()
|
||||
|
||||
|
||||
class OfferStates(IntEnum):
|
||||
@@ -136,6 +137,8 @@ class TxTypes(IntEnum):
|
||||
|
||||
ITX_PRE_FUNDED = auto()
|
||||
|
||||
BCH_MERCY = auto()
|
||||
|
||||
|
||||
class ActionTypes(IntEnum):
|
||||
ACCEPT_BID = auto()
|
||||
@@ -183,6 +186,8 @@ class EventLogTypes(IntEnum):
|
||||
PTX_REDEEM_PUBLISHED = auto()
|
||||
PTX_REFUND_PUBLISHED = auto()
|
||||
LOCK_TX_B_IN_MEMPOOL = auto()
|
||||
BCH_MERCY_TX_PUBLISHED = auto()
|
||||
BCH_MERCY_TX_FOUND = auto()
|
||||
|
||||
|
||||
class XmrSplitMsgTypes(IntEnum):
|
||||
@@ -194,6 +199,7 @@ class DebugTypes(IntEnum):
|
||||
NONE = 0
|
||||
BID_STOP_AFTER_COIN_A_LOCK = auto()
|
||||
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
|
||||
BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues
|
||||
CREATE_INVALID_COIN_B_LOCK = auto()
|
||||
BUYER_STOP_AFTER_ITX = auto()
|
||||
MAKE_INVALID_PTX = auto()
|
||||
@@ -205,6 +211,9 @@ class DebugTypes(IntEnum):
|
||||
DONT_CONFIRM_PTX = auto()
|
||||
OFFER_LOCK_2_VALUE_INC = auto()
|
||||
BID_STOP_AFTER_COIN_B_LOCK = auto()
|
||||
BID_DONT_SPEND_COIN_B_LOCK = auto()
|
||||
WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND = auto()
|
||||
BID_DONT_SPEND_COIN_A_LOCK = auto()
|
||||
|
||||
|
||||
class NotificationTypes(IntEnum):
|
||||
@@ -356,7 +365,9 @@ def strTxType(tx_type):
|
||||
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
||||
return 'Chain B Lock Tx'
|
||||
if tx_type == TxTypes.ITX_PRE_FUNDED:
|
||||
return 'Funded mock initiate tx'
|
||||
return 'Funded mock initiate Tx'
|
||||
if tx_type == TxTypes.BCH_MERCY:
|
||||
return 'BCH Mercy Tx'
|
||||
return 'Unknown'
|
||||
|
||||
|
||||
@@ -444,6 +455,10 @@ def describeEventEntry(event_type, event_msg):
|
||||
return 'Participate tx redeem tx published'
|
||||
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
|
||||
return 'Participate tx refund tx published'
|
||||
if event_type == EventLogTypes.BCH_MERCY_TX_FOUND:
|
||||
return 'BCH mercy tx found'
|
||||
if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED:
|
||||
return 'Lock tx B mercy tx published'
|
||||
|
||||
|
||||
def getVoutByAddress(txjs, p2sh):
|
||||
|
||||
113
basicswap/bin/prepare.py
Executable file → Normal file
113
basicswap/bin/prepare.py
Executable file → Normal file
@@ -37,7 +37,7 @@ from basicswap.ui.util import getCoinName
|
||||
from basicswap.util import toBool
|
||||
from basicswap.util.network import urlretrieve, make_reporthook
|
||||
from basicswap.util.rfc2440 import rfc2440_hash_password
|
||||
from basicswap.bin.run import startDaemon, startXmrWalletDaemon
|
||||
from basicswap.bin.run import startDaemon, startXmrWalletDaemon, getCoreBinName, getCoreBinArgs, getWalletBinName
|
||||
|
||||
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
|
||||
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
|
||||
@@ -49,6 +49,9 @@ LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
|
||||
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0')
|
||||
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
||||
|
||||
BITCOINCASH_VERSION = os.getenv('BITCOINCASH_VERSION', '27.1.0')
|
||||
BITCOINCASH_VERSION_TAG = os.getenv('BITCOINCASH_VERSION_TAG', '')
|
||||
|
||||
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.4')
|
||||
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
||||
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 = {
|
||||
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
||||
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
|
||||
'bitcoincash': (BITCOINCASH_VERSION, BITCOINCASH_VERSION_TAG, ('Calin_Culianu',)),
|
||||
'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)),
|
||||
'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)),
|
||||
'namecoin': ('0.18.0', '', ('JeremyRand',)),
|
||||
@@ -113,6 +117,7 @@ expected_key_ids = {
|
||||
'reuben': ('1290A1D0FA7EE109',),
|
||||
'nav_builder': ('2782262BF6E7FADB',),
|
||||
'decred_release': ('6D897EDF518A031D',),
|
||||
'Calin_Culianu': ('21810A542031C02C',),
|
||||
}
|
||||
|
||||
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
|
||||
@@ -135,7 +140,8 @@ BIN_ARCH = os.getenv('BIN_ARCH', BIN_ARCH)
|
||||
FILE_EXT = os.getenv('FILE_EXT', FILE_EXT)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.INFO
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
logger.level = LOG_LEVEL
|
||||
if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
@@ -186,6 +192,13 @@ BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
|
||||
BTC_RPC_USER = os.getenv('BTC_RPC_USER', '')
|
||||
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', 8335))
|
||||
BCH_PORT = int(os.getenv('BCH_PORT', 19798))
|
||||
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_PORT = int(os.getenv('DCR_RPC_PORT', 9109))
|
||||
DCR_WALLET_RPC_HOST = os.getenv('DCR_WALLET_RPC_HOST', '127.0.0.1')
|
||||
@@ -308,6 +321,7 @@ def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True):
|
||||
|
||||
# Set low timeout for urlretrieve connections
|
||||
socket.setdefaulttimeout(timeout)
|
||||
logger.level = logging.INFO
|
||||
|
||||
|
||||
def popConnectionParameters() -> None:
|
||||
@@ -315,6 +329,7 @@ def popConnectionParameters() -> None:
|
||||
socket.socket = default_socket
|
||||
socket.getaddrinfo = default_socket_getaddrinfo
|
||||
socket.setdefaulttimeout(default_socket_timeout)
|
||||
logger.level = LOG_LEVEL
|
||||
|
||||
|
||||
def getRemoteFileLength(url: str) -> (int, bool):
|
||||
@@ -513,8 +528,14 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
|
||||
return
|
||||
|
||||
dir_name = 'dashcore' if coin == 'dash' else coin
|
||||
dir_name = 'bitcoin-cash-node' if coin == 'bitcoincash' else coin
|
||||
if coin == 'decred':
|
||||
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:
|
||||
bins = [coin + 'd', coin + '-cli', coin + '-tx']
|
||||
versions = version.split('.')
|
||||
@@ -696,6 +717,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'
|
||||
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)
|
||||
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':
|
||||
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])
|
||||
@@ -738,7 +764,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
||||
if not os.path.exists(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')
|
||||
if coin not in ('nav', ):
|
||||
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
|
||||
@@ -798,11 +824,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')
|
||||
if coin == 'firo':
|
||||
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 != '':
|
||||
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:
|
||||
verified = gpg.verify_file(fp)
|
||||
|
||||
@@ -837,7 +865,6 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
||||
verified = gpg.verify_file(fp, assert_path)
|
||||
|
||||
ensureValidSignatureBy(verified, signing_key_name)
|
||||
|
||||
extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts)
|
||||
|
||||
|
||||
@@ -870,7 +897,8 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
os.makedirs(data_dir)
|
||||
|
||||
if coin in ('wownero', 'monero'):
|
||||
core_conf_path = os.path.join(data_dir, coin + 'd.conf')
|
||||
conf_filename: str = core_settings.get('config_filename', coin + 'd.conf')
|
||||
core_conf_path = os.path.join(data_dir, conf_filename)
|
||||
if os.path.exists(core_conf_path):
|
||||
exitWithError('{} exists'.format(core_conf_path))
|
||||
with open(core_conf_path, 'w') as fp:
|
||||
@@ -907,14 +935,12 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
for opt_line in wownerod_proxy_config:
|
||||
fp.write(opt_line + '\n')
|
||||
|
||||
if coin in ('wownero', 'monero'):
|
||||
wallets_dir = core_settings.get('walletsdir', data_dir)
|
||||
if not os.path.exists(wallets_dir):
|
||||
os.makedirs(wallets_dir)
|
||||
|
||||
wallet_conf_path = os.path.join(wallets_dir, coin + '-wallet-rpc.conf')
|
||||
if coin == 'monero':
|
||||
wallet_conf_path = os.path.join(wallets_dir, 'monero_wallet.conf')
|
||||
wallet_conf_filename: str = core_settings.get('wallet_config_filename', 'monero_wallet.conf' if coin == 'monero' else (coin + '-wallet-rpc.conf'))
|
||||
wallet_conf_path = os.path.join(wallets_dir, wallet_conf_filename)
|
||||
if os.path.exists(wallet_conf_path):
|
||||
exitWithError('{} exists'.format(wallet_conf_path))
|
||||
with open(wallet_conf_path, 'w') as fp:
|
||||
@@ -944,7 +970,8 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
|
||||
if coin == 'decred':
|
||||
chainname = 'simnet' if chain == 'regtest' else chain
|
||||
core_conf_path = os.path.join(data_dir, 'dcrd.conf')
|
||||
conf_filename: str = core_settings.get('config_filename', 'dcrd.conf')
|
||||
core_conf_path = os.path.join(data_dir, conf_filename)
|
||||
if os.path.exists(core_conf_path):
|
||||
exitWithError('{} exists'.format(core_conf_path))
|
||||
with open(core_conf_path, 'w') as fp:
|
||||
@@ -961,7 +988,8 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
if tor_control_password is not None:
|
||||
writeTorSettings(fp, coin, core_settings, tor_control_password)
|
||||
|
||||
wallet_conf_path = os.path.join(data_dir, 'dcrwallet.conf')
|
||||
wallet_conf_filename: str = core_settings.get('wallet_config_filename', 'dcrwallet.conf')
|
||||
wallet_conf_path = os.path.join(data_dir, wallet_conf_filename)
|
||||
if os.path.exists(wallet_conf_path):
|
||||
exitWithError('{} exists'.format(wallet_conf_path))
|
||||
with open(wallet_conf_path, 'w') as fp:
|
||||
@@ -979,7 +1007,8 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
|
||||
return
|
||||
|
||||
core_conf_path = os.path.join(data_dir, coin + '.conf')
|
||||
core_conf_name: str = core_settings.get('config_filename', coin + '.conf')
|
||||
core_conf_path = os.path.join(data_dir, core_conf_name)
|
||||
if os.path.exists(core_conf_path):
|
||||
exitWithError('{} exists'.format(core_conf_path))
|
||||
with open(core_conf_path, 'w') as fp:
|
||||
@@ -1032,6 +1061,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
fp.write('fallbackfee=0.0002\n')
|
||||
if BTC_RPC_USER != '':
|
||||
fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
|
||||
elif coin == 'bitcoincash':
|
||||
fp.write('prune=4000\n')
|
||||
fp.write('pid=bitcoincashd.pid\n')
|
||||
if BCH_RPC_USER != '':
|
||||
fp.write('rpcauth={}:{}${}\n'.format(BCH_RPC_USER, salt, password_to_hmac(salt, BCH_RPC_PWD)))
|
||||
elif coin == 'namecoin':
|
||||
fp.write('prune=2000\n')
|
||||
elif coin == 'pivx':
|
||||
@@ -1108,14 +1142,14 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
|
||||
data_dir = coin_settings['datadir']
|
||||
|
||||
if coin in ('monero', 'wownero'):
|
||||
core_conf_path = os.path.join(data_dir, coin + 'd.conf')
|
||||
core_conf_name: str = coin_settings.get('config_filename', coin + 'd.conf')
|
||||
core_conf_path = os.path.join(data_dir, core_conf_name)
|
||||
if not os.path.exists(core_conf_path):
|
||||
exitWithError('{} does not exist'.format(core_conf_path))
|
||||
|
||||
wallets_dir = coin_settings.get('walletsdir', data_dir)
|
||||
wallet_conf_path = os.path.join(wallets_dir, coin + '-wallet-rpc.conf')
|
||||
if coin == 'monero':
|
||||
wallet_conf_path = os.path.join(wallets_dir, 'monero_wallet.conf')
|
||||
wallet_conf_filename: str = coin_settings.get('wallet_config_filename', 'monero_wallet.conf' if coin == 'monero' else (coin + '-wallet-rpc.conf'))
|
||||
wallet_conf_path = os.path.join(wallets_dir, wallet_conf_filename)
|
||||
if not os.path.exists(wallet_conf_path):
|
||||
exitWithError('{} does not exist'.format(wallet_conf_path))
|
||||
|
||||
@@ -1170,10 +1204,8 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
|
||||
coin_settings['trusted_daemon'] = extra_opts.get('trust_remote_node', 'auto')
|
||||
return
|
||||
|
||||
if coin == 'decred':
|
||||
config_path = os.path.join(data_dir, 'dcrd.conf')
|
||||
else:
|
||||
config_path = os.path.join(data_dir, coin + '.conf')
|
||||
core_conf_name: str = coin_settings.get('config_filename', 'dcrd.conf' if coin == 'decred' else (coin + '.conf'))
|
||||
config_path = os.path.join(data_dir, core_conf_name)
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
exitWithError('{} does not exist'.format(config_path))
|
||||
@@ -1182,6 +1214,8 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
|
||||
default_onionport = 0
|
||||
if coin == 'bitcoin':
|
||||
default_onionport = BTC_ONION_PORT
|
||||
if coin == 'bitcoincash':
|
||||
default_onionport = BCH_ONION_PORT
|
||||
elif coin == 'particl':
|
||||
default_onionport = PART_ONION_PORT
|
||||
elif coin == 'litecoin':
|
||||
@@ -1292,9 +1326,11 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
|
||||
c = Coins.PART
|
||||
coin_name = 'particl'
|
||||
coin_settings = settings['chainclients'][coin_name]
|
||||
daemon_args += getCoreBinArgs(c, coin_settings)
|
||||
extra_config = {'stdout_to_file': True}
|
||||
if coin_settings['manage_daemon']:
|
||||
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args))
|
||||
filename: str = getCoreBinName(c, coin_settings, coin_name + 'd')
|
||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args, extra_config=extra_config))
|
||||
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
||||
swap_client.setCoinRunParams(c)
|
||||
swap_client.createCoinInterface(c)
|
||||
@@ -1344,22 +1380,25 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
if c == Coins.XMR:
|
||||
if coin_settings['manage_wallet_daemon']:
|
||||
filename = coin_name + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getWalletBinName(c, coin_settings, coin_name + '-wallet-rpc')
|
||||
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
|
||||
elif c == Coins.WOW:
|
||||
if coin_settings['manage_wallet_daemon']:
|
||||
filename = coin_name + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getWalletBinName(c, coin_settings, coin_name + '-wallet-rpc')
|
||||
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
|
||||
elif c == Coins.DCR:
|
||||
pass
|
||||
else:
|
||||
if coin_settings['manage_daemon']:
|
||||
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getCoreBinName(c, coin_settings, coin_name + 'd')
|
||||
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else []
|
||||
coin_args += getCoreBinArgs(c, coin_settings)
|
||||
|
||||
if c == Coins.FIRO:
|
||||
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
|
||||
|
||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
|
||||
extra_config = {'stdout_to_file': True}
|
||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args, extra_config=extra_config))
|
||||
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
||||
swap_client.setCoinRunParams(c)
|
||||
swap_client.createCoinInterface(c)
|
||||
@@ -1375,7 +1414,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
'--pass={}'.format(dcr_password),
|
||||
]
|
||||
|
||||
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getWalletBinName(c, coin_settings, 'dcrwallet')
|
||||
args = [os.path.join(coin_settings['bindir'], filename), '--create'] + extra_opts
|
||||
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
|
||||
createDCRWallet(args, hex_seed, logger, threading.Event())
|
||||
@@ -1755,6 +1794,21 @@ def main():
|
||||
'conf_target': 2,
|
||||
'core_version_group': 22,
|
||||
},
|
||||
'bitcoincash': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': shouldManageDaemon('BCH'),
|
||||
'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'),
|
||||
'port': BCH_PORT + port_offset,
|
||||
'config_filename': 'bitcoin.conf',
|
||||
'use_segwit': False,
|
||||
'blocks_confirmed': 1,
|
||||
'conf_target': 2,
|
||||
'core_version_group': 22,
|
||||
},
|
||||
'litecoin': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': shouldManageDaemon('LTC'),
|
||||
@@ -1787,6 +1841,7 @@ def main():
|
||||
'blocks_confirmed': 2,
|
||||
'conf_target': 2,
|
||||
'core_type_group': 'dcr',
|
||||
'config_filename': 'dcrd.conf',
|
||||
'min_relay_fee': 0.00001,
|
||||
},
|
||||
'namecoin': {
|
||||
@@ -1823,6 +1878,7 @@ def main():
|
||||
'rpctimeout': 60,
|
||||
'walletrpctimeout': 120,
|
||||
'walletrpctimeoutlong': 600,
|
||||
'wallet_config_filename': 'monero_wallet.conf',
|
||||
'core_type_group': 'xmr',
|
||||
},
|
||||
'pivx': {
|
||||
@@ -1917,6 +1973,9 @@ def main():
|
||||
if BTC_RPC_USER != '':
|
||||
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
|
||||
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
|
||||
if BCH_RPC_USER != '':
|
||||
chainclients['bitcoincash']['rpcuser'] = BCH_RPC_USER
|
||||
chainclients['bitcoincash']['rpcpassword'] = BCH_RPC_PWD
|
||||
if XMR_RPC_USER != '':
|
||||
chainclients['monero']['rpcuser'] = XMR_RPC_USER
|
||||
chainclients['monero']['rpcpassword'] = XMR_RPC_PWD
|
||||
|
||||
@@ -55,10 +55,10 @@ def signal_handler(sig, frame):
|
||||
|
||||
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||
|
||||
datadir_path = os.path.expanduser(node_dir)
|
||||
|
||||
# Rewrite litecoin.conf for 0.21.3
|
||||
# TODO: Remove
|
||||
ltc_conf_path = os.path.join(datadir_path, 'litecoin.conf')
|
||||
if os.path.exists(ltc_conf_path):
|
||||
config_to_add = ['blockfilterindex=0', 'peerblockfilters=0']
|
||||
@@ -69,7 +69,7 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||
config_to_add.remove(line)
|
||||
|
||||
if len(config_to_add) > 0:
|
||||
logging.info('Rewriting litecoin.conf')
|
||||
logger.info('Rewriting litecoin.conf')
|
||||
shutil.copyfile(ltc_conf_path, ltc_conf_path + '.last')
|
||||
with open(ltc_conf_path, 'a') as fp:
|
||||
for line in config_to_add:
|
||||
@@ -80,7 +80,8 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||
if add_datadir:
|
||||
args.append('-datadir=' + datadir_path)
|
||||
args += opts
|
||||
logging.info('Starting node ' + daemon_bin + ' ' + (('-datadir=' + node_dir) if add_datadir else ''))
|
||||
logger.info('Starting node {}'.format(daemon_bin))
|
||||
logger.debug('Arguments {}'.format(' '.join(args)))
|
||||
|
||||
opened_files = []
|
||||
if extra_config.get('stdout_to_file', False):
|
||||
@@ -91,10 +92,11 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||
stdout_dest = subprocess.PIPE
|
||||
stderr_dest = subprocess.PIPE
|
||||
|
||||
shell: bool = False
|
||||
if extra_config.get('use_shell', False):
|
||||
str_args = ' '.join(args)
|
||||
return Daemon(subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
||||
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
||||
args = ' '.join(args)
|
||||
shell = True
|
||||
return Daemon(subprocess.Popen(args, shell=shell, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
||||
|
||||
|
||||
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
||||
@@ -103,7 +105,8 @@ def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
||||
datadir_path = os.path.expanduser(node_dir)
|
||||
config_filename = 'wownerod.conf' if daemon_bin.startswith('wow') else 'monerod.conf'
|
||||
args = [daemon_path, '--non-interactive', '--config-file=' + os.path.join(datadir_path, config_filename)] + opts
|
||||
logging.info('Starting node {} --data-dir={}'.format(daemon_path, node_dir))
|
||||
logger.info('Starting node {}'.format(daemon_bin))
|
||||
logger.debug('Arguments {}'.format(' '.join(args)))
|
||||
|
||||
# return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
file_stdout = open(os.path.join(datadir_path, 'core_stdout.log'), 'w')
|
||||
@@ -112,8 +115,8 @@ def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
||||
|
||||
|
||||
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
|
||||
daemon_bin = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
|
||||
args = [daemon_bin, '--non-interactive']
|
||||
daemon_path = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
|
||||
args = [daemon_path, '--non-interactive']
|
||||
|
||||
needs_rewrite: bool = False
|
||||
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy=']
|
||||
@@ -127,19 +130,20 @@ def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
|
||||
with open(config_path) as fp:
|
||||
for line in fp:
|
||||
if any(line.startswith(config_line) for config_line in config_to_remove):
|
||||
logging.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
|
||||
logger.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
|
||||
needs_rewrite = True
|
||||
args += opts
|
||||
|
||||
if needs_rewrite:
|
||||
logging.info('Rewriting wallet config')
|
||||
logger.info('Rewriting wallet config')
|
||||
shutil.copyfile(config_path, config_path + '.last')
|
||||
with open(config_path + '.last') as fp_from, open(config_path, 'w') as fp_to:
|
||||
for line in fp_from:
|
||||
if not any(line.startswith(config_line) for config_line in config_to_remove):
|
||||
fp_to.write(line)
|
||||
|
||||
logging.info('Starting wallet daemon {} --wallet-dir={}'.format(daemon_bin, node_dir))
|
||||
logger.info('Starting wallet daemon {}'.format(wallet_bin))
|
||||
logger.debug('Arguments {}'.format(' '.join(args)))
|
||||
|
||||
# TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir)
|
||||
wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w')
|
||||
@@ -166,8 +170,25 @@ def ws_message_received(client, server, message):
|
||||
swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
|
||||
|
||||
|
||||
def getCoreBinName(coin_id: int, coin_settings, default_name: str) -> str:
|
||||
return coin_settings.get('core_binname', chainparams[coin_id].get('core_binname', default_name)) + ('.exe' if os.name == 'nt' else '')
|
||||
|
||||
|
||||
def getWalletBinName(coin_id: int, coin_settings, default_name: str) -> str:
|
||||
return coin_settings.get('wallet_binname', chainparams[coin_id].get('wallet_binname', default_name)) + ('.exe' if os.name == 'nt' else '')
|
||||
|
||||
|
||||
def getCoreBinArgs(coin_id: int, coin_settings):
|
||||
extra_args = []
|
||||
if 'config_filename' in coin_settings:
|
||||
extra_args.append('--conf=' + coin_settings['config_filename'])
|
||||
if 'port' in coin_settings:
|
||||
extra_args.append('--port=' + str(int(coin_settings['port'])))
|
||||
return extra_args
|
||||
|
||||
|
||||
def runClient(fp, data_dir, chain, start_only_coins):
|
||||
global swap_client
|
||||
global swap_client, logger
|
||||
daemons = []
|
||||
pids = []
|
||||
threads = []
|
||||
@@ -188,6 +209,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
settings = json.load(fs)
|
||||
|
||||
swap_client = BasicSwap(fp, data_dir, settings, chain)
|
||||
logger = swap_client.log
|
||||
|
||||
if os.path.exists(pids_path):
|
||||
with open(pids_path) as fd:
|
||||
@@ -214,7 +236,8 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
if c in ('monero', 'wownero'):
|
||||
if v['manage_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} daemon')
|
||||
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getCoreBinName(coin_id, v, c + 'd')
|
||||
|
||||
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename))
|
||||
pid = daemons[-1].handle.pid
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
@@ -240,7 +263,8 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
|
||||
|
||||
opts.append('--trusted-daemon' if trusted_daemon else '--untrusted-daemon')
|
||||
filename = c + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getWalletBinName(coin_id, v, c + '-wallet-rpc')
|
||||
|
||||
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
|
||||
pid = daemons[-1].handle.pid
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
@@ -253,7 +277,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
use_shell: bool = True if os.name == 'nt' else False
|
||||
if v['manage_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} daemon')
|
||||
filename = 'dcrd' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getCoreBinName(coin_id, v, 'dcrd')
|
||||
|
||||
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrd_stdout.log', 'use_shell': use_shell}
|
||||
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
|
||||
@@ -262,7 +286,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
|
||||
if v['manage_wallet_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} wallet daemon')
|
||||
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
|
||||
filename: str = getWalletBinName(coin_id, v, 'dcrwallet')
|
||||
|
||||
wallet_pwd = v['wallet_pwd']
|
||||
if wallet_pwd == '':
|
||||
@@ -280,8 +304,9 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
if v['manage_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} daemon')
|
||||
|
||||
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], filename))
|
||||
filename: str = getCoreBinName(coin_id, v, c + 'd')
|
||||
extra_opts = getCoreBinArgs(coin_id, v)
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], filename, opts=extra_opts))
|
||||
pid = daemons[-1].handle.pid
|
||||
pids.append((c, pid))
|
||||
swap_client.setDaemonPID(c, pid)
|
||||
|
||||
@@ -30,6 +30,7 @@ class Coins(IntEnum):
|
||||
NAV = 14
|
||||
LTC_MWEB = 15
|
||||
# ZANO = 16
|
||||
BCH = 17
|
||||
|
||||
|
||||
chainparams = {
|
||||
@@ -279,13 +280,13 @@ chainparams = {
|
||||
Coins.PIVX: {
|
||||
'name': 'pivx',
|
||||
'ticker': 'PIVX',
|
||||
'display_name': 'PIVX',
|
||||
'message_magic': 'DarkNet Signed Message:\n',
|
||||
'blocks_target': 60 * 1,
|
||||
'decimal_places': 8,
|
||||
'has_cltv': True,
|
||||
'has_csv': False,
|
||||
'has_segwit': False,
|
||||
'use_ticker_as_name': True,
|
||||
'mainnet': {
|
||||
'rpcport': 51473,
|
||||
'pubkey_address': 30,
|
||||
@@ -432,7 +433,51 @@ chainparams = {
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
}
|
||||
},
|
||||
Coins.BCH: {
|
||||
'name': 'bitcoincash',
|
||||
'ticker': 'BCH',
|
||||
'display_name': 'Bitcoin Cash',
|
||||
'message_magic': 'Bitcoin Signed Message:\n',
|
||||
'blocks_target': 60 * 2,
|
||||
'decimal_places': 8,
|
||||
'has_cltv': True,
|
||||
'has_csv': True,
|
||||
'has_segwit': False,
|
||||
'cli_binname': 'bitcoin-cli',
|
||||
'core_binname': 'bitcoind',
|
||||
'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 = {}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019-2023 tecnovert
|
||||
# Copyright (c) 2019-2024 The Basicswap developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -36,3 +36,5 @@ 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')))
|
||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
||||
|
||||
# NOTE: Adding coin definitions here is deprecated. Please add in coin test file.
|
||||
|
||||
@@ -52,6 +52,7 @@ class CoinInterface:
|
||||
self.setDefaults()
|
||||
self._network = network
|
||||
self._mx_wallet = threading.Lock()
|
||||
self._altruistic = True
|
||||
|
||||
def setDefaults(self):
|
||||
self._unknown_wallet_seed = True
|
||||
@@ -66,8 +67,8 @@ class CoinInterface:
|
||||
|
||||
def coin_name(self) -> str:
|
||||
coin_chainparams = chainparams[self.coin_type()]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker']
|
||||
if 'display_name' in coin_chainparams:
|
||||
return coin_chainparams['display_name']
|
||||
return coin_chainparams['name'].capitalize()
|
||||
|
||||
def ticker(self) -> str:
|
||||
@@ -166,6 +167,9 @@ class CoinInterface:
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def altruistic(self) -> bool:
|
||||
return self._altruistic
|
||||
|
||||
|
||||
class AdaptorSigInterface():
|
||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||
|
||||
843
basicswap/interface/bch.py
Normal file
843
basicswap/interface/bch.py
Normal file
@@ -0,0 +1,843 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 The Basicswap developers
|
||||
# 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
|
||||
from basicswap.util import b2h, b2i, ensure, i2h
|
||||
from basicswap.util.script import decodePushData, decodeScriptNum
|
||||
from .btc import BTCInterface, ensure_op, findOutput
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.chainparams import Coins
|
||||
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_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,
|
||||
OP_HASH160,
|
||||
OP_DUP,
|
||||
OP_CHECKSIG,
|
||||
OP_HASH256,
|
||||
)
|
||||
from basicswap.contrib.test_framework.script import OP_RETURN, CScript
|
||||
from coincurve.keys import (
|
||||
PrivateKey,
|
||||
PublicKey,
|
||||
)
|
||||
from coincurve.ecdsaotves import (
|
||||
ecdsaotves_enc_sign,
|
||||
ecdsaotves_enc_verify,
|
||||
ecdsaotves_dec_sig,
|
||||
ecdsaotves_rec_enc_key,
|
||||
)
|
||||
|
||||
|
||||
class BCHInterface(BTCInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.BCH
|
||||
|
||||
@staticmethod
|
||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||
return 302
|
||||
|
||||
@staticmethod
|
||||
def watch_blocks_for_scripts() -> bool:
|
||||
# TODO: BCH Watchonly: Remove when BCH watchonly works.
|
||||
return True
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super(BCHInterface, self).__init__(coin_settings, network, swap_client)
|
||||
# No multiwallet support
|
||||
self.swap_client = swap_client
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def has_segwit(self) -> bool:
|
||||
# bch does not have segwit, but we return true here to avoid extra checks in basicswap.py
|
||||
return True
|
||||
|
||||
def getExchangeName(self, exchange_name: str) -> str:
|
||||
return 'bitcoin-cash'
|
||||
|
||||
def getNewAddress(self, use_segwit: bool = False, label: str = 'swap_receive') -> str:
|
||||
args = [label]
|
||||
return self.rpc_wallet('getnewaddress', args)
|
||||
|
||||
def getUnspentsByAddr(self):
|
||||
unspent_addr = dict()
|
||||
unspent = self.rpc_wallet('listunspent')
|
||||
for u in unspent:
|
||||
if u.get('spendable', False) is False:
|
||||
continue
|
||||
if 'address' not in u:
|
||||
continue
|
||||
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
|
||||
return unspent_addr
|
||||
|
||||
# returns pkh
|
||||
def decodeAddress(self, address: str) -> bytes:
|
||||
return bytes(Address.from_string(address).payload)
|
||||
|
||||
def encodeSegwitAddress(self, script):
|
||||
raise ValueError('Segwit not supported')
|
||||
|
||||
def decodeSegwitAddress(self, addr):
|
||||
raise ValueError('Segwit not supported')
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
address = self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
if not self.isAddressMine(address, or_watch_only=True):
|
||||
# Expects P2WSH nested in BIP16_P2SH
|
||||
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
||||
addr_info = self.rpc('validateaddress', [address])
|
||||
|
||||
return address
|
||||
|
||||
def importWatchOnlyAddress(self, address: str, label: str):
|
||||
self.rpc_wallet('importaddress', [address, label, False, True])
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
|
||||
options = {
|
||||
'lockUnspents': lock_unspents,
|
||||
# 'conf_target': self._conf_target,
|
||||
}
|
||||
if sub_fee:
|
||||
options['subtractFeeFromOutputs'] = [0,]
|
||||
return self.rpc_wallet('fundrawtransaction', [txn, options])['hex']
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||
# Return P2PKH
|
||||
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||
|
||||
def encodeScriptDest(self, script_dest: bytes) -> str:
|
||||
# Extract hash from script
|
||||
script_hash = script_dest[2:-1]
|
||||
return self.sh_to_address(script_hash)
|
||||
|
||||
def sh_to_address(self, sh: bytes) -> str:
|
||||
assert (len(sh) == 20 or len(sh) == 32)
|
||||
network = self._network.upper()
|
||||
address = None
|
||||
if len(sh) == 20:
|
||||
address = Address("P2SH20" if network == "MAINNET" else "P2SH20-" + network, sh)
|
||||
else:
|
||||
address = Address("P2SH32" if network == "MAINNET" else "P2SH32-" + network, sh)
|
||||
|
||||
return address.cash_address()
|
||||
|
||||
def getDestForScriptHash(self, script_hash):
|
||||
assert (len(script_hash) == 20 or len(script_hash) == 32)
|
||||
if len(script_hash) == 20:
|
||||
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||
else:
|
||||
return CScript([OP_HASH256, script_hash, OP_EQUAL])
|
||||
|
||||
def withdrawCoin(self, value: float, addr_to: str, subfee: bool):
|
||||
params = [addr_to, value, '', '', subfee, 0, False]
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||
add_bytes = 107
|
||||
size = len(tx.serialize_with_witness()) + add_bytes
|
||||
pay_fee = round(fee_rate * size / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
||||
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
||||
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
||||
return None
|
||||
|
||||
def getLockTxHeight(self, txid: bytes, dest_address: str, bid_amount: int, rescan_from: int, find_index: bool = False, vout: int = -1):
|
||||
|
||||
'''
|
||||
TODO: BCH Watchonly
|
||||
Replace with importWatchOnlyAddress when it works again
|
||||
Currently importing the watchonly address only works if rescanblockchain is run on every iteration
|
||||
'''
|
||||
if txid is None:
|
||||
self._log.debug('TODO: getLockTxHeight')
|
||||
return None
|
||||
|
||||
found_vout = None
|
||||
# Search for txo at vout 0 and 1 if vout is not known
|
||||
if vout is None:
|
||||
test_range = range(2)
|
||||
else:
|
||||
test_range = (vout, )
|
||||
for try_vout in test_range:
|
||||
try:
|
||||
txout = self.rpc('gettxout', [txid.hex(), try_vout, True])
|
||||
addresses = txout['scriptPubKey']['addresses']
|
||||
if len(addresses) != 1 or addresses[0] != dest_address:
|
||||
continue
|
||||
if self.make_int(txout['value']) != bid_amount:
|
||||
self._log.warning('getLockTxHeight found txout {} with incorrect amount {}'.format(txid.hex(), txout['value']))
|
||||
continue
|
||||
found_vout = try_vout
|
||||
break
|
||||
except Exception as e:
|
||||
# self._log.warning('gettxout {}'.format(e))
|
||||
return None
|
||||
|
||||
if found_vout is None:
|
||||
return None
|
||||
|
||||
block_height: int = 0
|
||||
confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations']
|
||||
|
||||
# TODO: Better way?
|
||||
if confirmations > 0:
|
||||
block_height = self.getChainHeight() - confirmations
|
||||
|
||||
rv = {
|
||||
'txid': txid.hex(),
|
||||
'depth': confirmations,
|
||||
'index': found_vout,
|
||||
'height': block_height}
|
||||
|
||||
return rv
|
||||
|
||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
||||
mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000
|
||||
out_1: bytes = kwargs['out_1']
|
||||
out_2: bytes = kwargs['out_2']
|
||||
public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal
|
||||
timelock: int = kwargs['timelock']
|
||||
|
||||
# fmt: off
|
||||
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
|
||||
OP_TXINPUTCOUNT,
|
||||
OP_1, OP_NUMEQUALVERIFY,
|
||||
OP_TXOUTPUTCOUNT,
|
||||
OP_1, OP_NUMEQUALVERIFY,
|
||||
|
||||
# // int miningFee
|
||||
mining_fee,
|
||||
# // Verify pre-agreed mining fee and that the rest of BCH is forwarded
|
||||
# // to the output.
|
||||
OP_0, OP_UTXOVALUE,
|
||||
OP_0, OP_OUTPUTVALUE,
|
||||
OP_SUB, OP_NUMEQUALVERIFY,
|
||||
|
||||
# # // Verify that any CashTokens are forwarded to the output.
|
||||
OP_0, OP_UTXOTOKENCATEGORY,
|
||||
OP_0, OP_OUTPUTTOKENCATEGORY,
|
||||
OP_EQUALVERIFY,
|
||||
OP_0, OP_UTXOTOKENCOMMITMENT,
|
||||
OP_0, OP_OUTPUTTOKENCOMMITMENT,
|
||||
OP_EQUALVERIFY,
|
||||
OP_0, OP_UTXOTOKENAMOUNT,
|
||||
OP_0, OP_OUTPUTTOKENAMOUNT,
|
||||
OP_NUMEQUALVERIFY,
|
||||
|
||||
# // If sequence is not used then it is a regular swap TX.
|
||||
OP_0, OP_INPUTSEQUENCENUMBER,
|
||||
OP_NOTIF,
|
||||
# // bytes aliceOutput
|
||||
out_1,
|
||||
# // Verify that the BCH and/or CashTokens are forwarded to Alice's
|
||||
# // output.
|
||||
OP_0, OP_OUTPUTBYTECODE,
|
||||
OP_OVER, 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.
|
||||
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 withheld the encrypted signature.
|
||||
OP_ELSE,
|
||||
# // int timelock_0
|
||||
timelock,
|
||||
# // Verify refund timelock.
|
||||
OP_CHECKSEQUENCEVERIFY, OP_DROP,
|
||||
|
||||
# // bytes refundLockingBytecode
|
||||
out_2,
|
||||
|
||||
# // Verify that the BCH and/or CashTokens are forwarded to Refund
|
||||
# // contract.
|
||||
OP_0, OP_OUTPUTBYTECODE,
|
||||
OP_EQUAL,
|
||||
|
||||
# // BCH and/or CashTokens are simply forwarded to Refund contract.
|
||||
OP_ENDIF
|
||||
])
|
||||
# fmt: on
|
||||
|
||||
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)
|
||||
network = self._network.upper()
|
||||
address = Address("P2PKH" if network == "MAINNET" else "P2PKH-" + network, pkh)
|
||||
|
||||
return address.cash_address()
|
||||
|
||||
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
||||
return self.pkh_to_address(hash160(Kbs))
|
||||
|
||||
def addressToLockingBytecode(self, address: str) -> bytes:
|
||||
return b'\x76\xa9\x14' + bytes(Address.from_string(address).payload) + b'\x88\xac'
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_wallet('getbalance', ["*", 1, False]))
|
||||
|
||||
def getScriptDest(self, script):
|
||||
return self.scriptToP2SH32LockingBytecode(script)
|
||||
|
||||
def scriptToP2SH32LockingBytecode(self, script: Union[bytes, str]) -> bytes:
|
||||
return CScript([
|
||||
OP_HASH256,
|
||||
sha256(sha256(script)),
|
||||
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 getTxSize(self, tx: CTransaction) -> int:
|
||||
return len(tx.serialize_without_witness())
|
||||
|
||||
def getScriptScriptSig(self, script: bytes, ves: bytes = None) -> 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, vkbv=None, fee_info={}, **kwargs):
|
||||
# tx_fee_rate in this context is equal to `mining_fee` contract param
|
||||
ves = kwargs['ves'] if 'ves' in kwargs else None
|
||||
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
|
||||
# vsize is the same as size for BCH
|
||||
fee_info['vsize'] = 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()
|
||||
|
||||
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None, **kwargs):
|
||||
tx_lock = CTransaction()
|
||||
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
|
||||
|
||||
refund_script = kwargs['refund_lock_tx_script']
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
|
||||
nSequence=kwargs['timelock'] if 'timelock' in kwargs else lock1_value,
|
||||
scriptSig=self.getScriptScriptSig(script_lock, None)))
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
|
||||
|
||||
pay_fee = kwargs['mining_fee'] if 'mining_fee' in kwargs else tx_fee_rate
|
||||
tx.vout[0].nValue = locked_coin - pay_fee
|
||||
|
||||
size = self.getTxSize(tx)
|
||||
vsize = size
|
||||
|
||||
tx.rehash()
|
||||
self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||
|
||||
return tx.serialize_without_witness(), refund_script, tx.vout[0].nValue
|
||||
|
||||
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None, **kwargs):
|
||||
# it is not possible to create the refund spend tx without the prior knowledge of the VES which is part of transaction preimage
|
||||
# but it is better and more secure to create a lock spend transaction committing to zero VES than returning static data
|
||||
kwargs['ves'] = bytes(73)
|
||||
return self.createSCLockSpendTx(tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv, **kwargs)
|
||||
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
|
||||
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||
|
||||
output_script = self.getScriptDest(script_lock_refund)
|
||||
locked_n = findOutput(tx_lock_refund, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||
|
||||
mining_fee, out_1, out_2, public_key, timelock = self.extractScriptLockScriptValues(script_lock_refund)
|
||||
|
||||
tx_lock_refund.rehash()
|
||||
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
|
||||
nSequence=timelock,
|
||||
scriptSig=self.getScriptScriptSig(script_lock_refund, None)))
|
||||
|
||||
tx.vout.append(self.txoType()(locked_coin, CScript(out_2)))
|
||||
|
||||
size = self.getTxSize(tx)
|
||||
vsize = size
|
||||
|
||||
pay_fee = mining_fee
|
||||
tx.vout[0].nValue = locked_coin - pay_fee
|
||||
|
||||
tx.rehash()
|
||||
self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||
|
||||
return tx.serialize_without_witness()
|
||||
|
||||
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||
# simply sign the entire tx data, as this is not a preimage signature
|
||||
eck = PrivateKey(key_bytes)
|
||||
return eck.sign(sha256(tx_bytes), hasher=None)
|
||||
|
||||
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||
# simple ecdsa signature verification
|
||||
return self.verifyDataSig(tx_bytes, sig, K)
|
||||
|
||||
def verifyDataSig(self, data: bytes, sig: bytes, K: bytes) -> bool:
|
||||
# simple ecdsa signature verification
|
||||
pubkey = PublicKey(K)
|
||||
return pubkey.verify(sig, sha256(data), hasher=None)
|
||||
|
||||
def setTxSignature(self, tx_bytes: bytes, stack) -> bytes:
|
||||
return tx_bytes
|
||||
|
||||
def extractScriptLockScriptValuesFromScriptSig(self, script_bytes):
|
||||
signature, nb = decodePushData(script_bytes, 0)
|
||||
if nb == len(script_bytes):
|
||||
unlock_script = signature[:]
|
||||
signature = None
|
||||
else:
|
||||
unlock_script, _ = decodePushData(script_bytes, nb)
|
||||
mining_fee, out_1, out_2, public_key, timelock = self.extractScriptLockScriptValues(unlock_script)
|
||||
|
||||
return signature, mining_fee, out_1, out_2, public_key, timelock
|
||||
|
||||
def extractScriptLockScriptValues(self, script_bytes):
|
||||
# see BCHInterface.genScriptLockTxScript for reference
|
||||
|
||||
o = 0
|
||||
|
||||
script_len = len(script_bytes)
|
||||
# TODO: stricter script_len checks
|
||||
|
||||
ensure_op(script_bytes[o] == OP_TXINPUTCOUNT); o += 1
|
||||
ensure_op(script_bytes[o] == OP_1); o += 1
|
||||
ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1
|
||||
ensure_op(script_bytes[o] == OP_TXOUTPUTCOUNT); o += 1
|
||||
ensure_op(script_bytes[o] == OP_1); o += 1
|
||||
ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1
|
||||
mining_fee, nb = decodeScriptNum(script_bytes, o); o += nb
|
||||
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_UTXOVALUE); o += 1
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OUTPUTVALUE); o += 1
|
||||
ensure_op(script_bytes[o] == OP_SUB); o += 1
|
||||
ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1
|
||||
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_UTXOTOKENCATEGORY); o += 1
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OUTPUTTOKENCATEGORY); o += 1
|
||||
|
||||
ensure_op(script_bytes[o] == OP_EQUALVERIFY); o += 1
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_UTXOTOKENCOMMITMENT); o += 1
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OUTPUTTOKENCOMMITMENT); o += 1
|
||||
ensure_op(script_bytes[o] == OP_EQUALVERIFY); o += 1
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_UTXOTOKENAMOUNT); o += 1
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OUTPUTTOKENAMOUNT); o += 1
|
||||
ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1
|
||||
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_INPUTSEQUENCENUMBER); o += 1
|
||||
ensure_op(script_bytes[o] == OP_NOTIF); o += 1
|
||||
out_1, nb = decodePushData(script_bytes, o); o += nb
|
||||
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OUTPUTBYTECODE); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OVER); o += 1
|
||||
ensure_op(script_bytes[o] == OP_EQUALVERIFY); o += 1
|
||||
public_key, nb = decodePushData(script_bytes, o); o += nb
|
||||
ensure_op(script_bytes[o] == OP_CHECKDATASIG); o += 1
|
||||
|
||||
ensure_op(script_bytes[o] == OP_ELSE); o += 1
|
||||
timelock, nb = decodeScriptNum(script_bytes, o); o += nb
|
||||
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY); o += 1
|
||||
ensure_op(script_bytes[o] == OP_DROP); o += 1
|
||||
|
||||
out_2, nb = decodePushData(script_bytes, o); o += nb
|
||||
|
||||
ensure_op(script_bytes[o] == OP_0); o += 1
|
||||
ensure_op(script_bytes[o] == OP_OUTPUTBYTECODE); o += 1
|
||||
ensure_op(script_bytes[o] == OP_EQUAL); o += 1
|
||||
|
||||
ensure_op(script_bytes[o] == OP_ENDIF); o += 1
|
||||
|
||||
ensure(o == script_len, 'Unexpected script length')
|
||||
|
||||
ensure(mining_fee >= 700 and mining_fee <= 10000, 'Bad mining_fee')
|
||||
ensure(len(out_1) == 25, 'Bad out_1')
|
||||
ensure(len(out_2) == 25 or len(out_2) == 35, 'Bad out_2')
|
||||
ensure(len(public_key) == 33, 'Bad public_key')
|
||||
ensure(timelock >= 0, 'Bad timelock')
|
||||
|
||||
return mining_fee, out_1, out_2, public_key, timelock
|
||||
|
||||
def verifySCLockTx(self, tx_bytes, script_out,
|
||||
swap_value,
|
||||
Kal, Kaf,
|
||||
feerate,
|
||||
check_lock_tx_inputs, vkbv=None,
|
||||
**kwargs):
|
||||
|
||||
# Verify:
|
||||
#
|
||||
|
||||
# Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm
|
||||
# However by checking early we can avoid wasting time processing unmineable txns
|
||||
# Check fee is reasonable
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores
|
||||
|
||||
script_pk = self.getScriptDest(script_out)
|
||||
locked_n = findOutput(tx, script_pk)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx.vout[locked_n].nValue
|
||||
|
||||
# Check value
|
||||
ensure(locked_coin == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script
|
||||
mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000
|
||||
out_1: bytes = kwargs['out_1']
|
||||
out_2: bytes = kwargs['out_2']
|
||||
public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal
|
||||
timelock: int = kwargs['timelock']
|
||||
|
||||
_mining_fee, _out_1, _out_2, _public_key, _timelock = self.extractScriptLockScriptValues(script_out)
|
||||
ensure(mining_fee == _mining_fee, 'mining mismatch fee')
|
||||
ensure(out_1 == _out_1, 'out_1 mismatch')
|
||||
ensure(out_2 == _out_2, 'out_2 mismatch')
|
||||
ensure(public_key == _public_key, 'public_key mismatch')
|
||||
ensure(timelock == _timelock, 'timelock mismatch')
|
||||
|
||||
return txid, locked_n
|
||||
|
||||
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
|
||||
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None, **kwargs):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout and sequence
|
||||
# Must have only one output to the p2wsh of the lock refund script
|
||||
# Output value must be locked_coin - lock tx fee
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence')
|
||||
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script, None), 'Input scriptsig mismatch')
|
||||
ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
|
||||
script_pk = self.getScriptDest(script_out)
|
||||
locked_n = findOutput(tx, script_pk)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx.vout[locked_n].nValue
|
||||
|
||||
# Check script
|
||||
mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000
|
||||
out_1: bytes = kwargs['out_1']
|
||||
out_2: bytes = kwargs['out_2']
|
||||
public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal
|
||||
timelock: int = kwargs['timelock']
|
||||
|
||||
_mining_fee, _out_1, _out_2, _public_key, _timelock = self.extractScriptLockScriptValues(script_out)
|
||||
ensure(mining_fee == _mining_fee, 'mining mismatch fee')
|
||||
ensure(out_1 == _out_1, 'out_1 mismatch')
|
||||
ensure(out_2 == _out_2, 'out_2 mismatch')
|
||||
ensure(public_key == _public_key, 'public_key mismatch')
|
||||
ensure(timelock == _timelock, 'timelock mismatch')
|
||||
|
||||
fee_paid = locked_coin - mining_fee
|
||||
assert (fee_paid > 0)
|
||||
|
||||
size = self.getTxSize(tx)
|
||||
vsize = size
|
||||
|
||||
self._log.info('tx amount, vsize, fee: %ld, %ld, %ld', locked_coin, vsize, fee_paid)
|
||||
|
||||
return txid, locked_coin, locked_n
|
||||
|
||||
def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
|
||||
lock_refund_tx_id, prevout_script,
|
||||
Kal,
|
||||
prevout_n, prevout_value, feerate, vkbv=None, **kwargs):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script, bytes(73)), 'Input scriptsig mismatch')
|
||||
ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
|
||||
# Check script
|
||||
mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000
|
||||
out_1: bytes = kwargs['out_1']
|
||||
out_2: bytes = kwargs['out_2']
|
||||
public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal
|
||||
timelock: int = kwargs['timelock']
|
||||
|
||||
_mining_fee, _out_1, _out_2, _public_key, _timelock = self.extractScriptLockScriptValues(prevout_script)
|
||||
ensure(mining_fee == _mining_fee, 'mining mismatch fee')
|
||||
ensure(out_1 == _out_1, 'out_1 mismatch')
|
||||
ensure(out_2 == _out_2, 'out_2 mismatch')
|
||||
ensure(public_key == _public_key, 'public_key mismatch')
|
||||
ensure(timelock == _timelock, 'timelock mismatch')
|
||||
|
||||
tx_value = tx.vout[0].nValue
|
||||
fee_paid = tx_value - mining_fee
|
||||
assert (fee_paid > 0)
|
||||
|
||||
size = self.getTxSize(tx)
|
||||
vsize = size
|
||||
|
||||
self._log.info('tx amount, vsize, fee: %ld, %ld, %ld', tx_value, vsize, fee_paid)
|
||||
|
||||
return True
|
||||
|
||||
def verifySCLockSpendTx(self, tx_bytes,
|
||||
lock_tx_bytes, lock_tx_script,
|
||||
a_pkhash_f, feerate, vkbv=None):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||
# Must have only one output with destination and amount
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.nVersion == self.txVersion(), 'Bad version')
|
||||
ensure(tx.nLockTime == 0, 'nLockTime not 0')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
lock_tx = self.loadTx(lock_tx_bytes)
|
||||
lock_tx_id = self.getTxid(lock_tx)
|
||||
|
||||
output_script = self.getScriptDest(lock_tx_script)
|
||||
locked_n = findOutput(lock_tx, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = lock_tx.vout[locked_n].nValue
|
||||
|
||||
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch')
|
||||
|
||||
# allow for this mismatch in BCH, since the lock txid will get changed after signing
|
||||
# ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
p2pkh = self.getScriptForPubkeyHash(a_pkhash_f)
|
||||
ensure(tx.vout[0].scriptPubKey == p2pkh, 'Bad output destination')
|
||||
|
||||
# The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount
|
||||
fee_paid = locked_coin - tx.vout[0].nValue
|
||||
assert (fee_paid > 0)
|
||||
|
||||
size = self.getTxSize(tx)
|
||||
vsize = size
|
||||
|
||||
self._log.info('tx amount, vsize, fee: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_paid)
|
||||
|
||||
return True
|
||||
|
||||
def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||
_, out_1, _, _, _ = self.extractScriptLockScriptValues(prevout_script)
|
||||
msg = sha256(out_1)
|
||||
|
||||
return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, msg)
|
||||
|
||||
def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
|
||||
return ecdsaotves_dec_sig(k, esig)
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig)
|
||||
|
||||
def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value):
|
||||
_, out_1, _, _, _ = self.extractScriptLockScriptValues(prevout_script)
|
||||
msg = sha256(out_1)
|
||||
|
||||
return ecdsaotves_enc_verify(Ks, Ke, msg, ct)
|
||||
|
||||
def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(tx.vin[0].scriptSig)
|
||||
return signature
|
||||
|
||||
def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(tx.vin[0].scriptSig)
|
||||
return signature
|
||||
|
||||
def isSpendingLockTx(self, spend_tx: CTransaction) -> bool:
|
||||
signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(spend_tx.vin[0].scriptSig)
|
||||
return spend_tx.vin[0].nSequence == 0 and signature is not None
|
||||
|
||||
def isSpendingLockRefundTx(self, spend_tx: CTransaction) -> bool:
|
||||
signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(spend_tx.vin[0].scriptSig)
|
||||
return spend_tx.vin[0].nSequence == 0 and signature is not None
|
||||
|
||||
def isTxExistsError(self, err_str: str) -> bool:
|
||||
return 'transaction already in block chain' in err_str
|
||||
|
||||
def getRefundOutputScript(self, xmr_swap) -> bytes:
|
||||
_, out_1, _, _, _ = self.extractScriptLockScriptValues(xmr_swap.a_lock_refund_tx_script)
|
||||
return out_1
|
||||
|
||||
def createMercyTx(self, refund_swipe_tx_bytes: bytes, refund_swipe_tx_id: bytes, lock_refund_tx_script: bytes, keyshare: bytes) -> str:
|
||||
refund_swipe_tx = self.loadTx(refund_swipe_tx_bytes)
|
||||
refund_output_value = refund_swipe_tx.vout[0].nValue
|
||||
refund_output_script = refund_swipe_tx.vout[0].scriptPubKey
|
||||
|
||||
# mercy transaction size consisting of one input of freshly received funds,
|
||||
# one op_return with mercy information, a dust output to the leader and change back to the follower
|
||||
tx_size = 275
|
||||
dust_limit = 546
|
||||
|
||||
outValue = refund_output_value - tx_size - dust_limit
|
||||
|
||||
_, out_1, _, _, _ = self.extractScriptLockScriptValues(lock_refund_tx_script)
|
||||
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(b2i(refund_swipe_tx_id), 0),
|
||||
nSequence=0,
|
||||
scriptSig=CScript(out_1)))
|
||||
|
||||
tx.vout.append(self.txoType()(0, CScript([OP_RETURN, b'XBSW', keyshare])))
|
||||
tx.vout.append(self.txoType()(dust_limit, CScript(out_1)))
|
||||
tx.vout.append(self.txoType()(outValue, refund_output_script))
|
||||
|
||||
size = tx_size
|
||||
vsize = size
|
||||
|
||||
pay_fee = size
|
||||
|
||||
tx.rehash()
|
||||
self._log.info('createMercyTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||
i2h(tx.sha256), 1, vsize, pay_fee)
|
||||
|
||||
txHex = tx.serialize_without_witness()
|
||||
return self.signTxWithWallet(txHex)
|
||||
@@ -75,6 +75,7 @@ from basicswap.contrib.test_framework.script import (
|
||||
OP_CHECKSEQUENCEVERIFY,
|
||||
OP_DROP,
|
||||
OP_HASH160, OP_EQUAL,
|
||||
OP_RETURN,
|
||||
SIGHASH_ALL,
|
||||
SegwitV0SignatureHash,
|
||||
)
|
||||
@@ -258,6 +259,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
self._sc = swap_client
|
||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self._expect_seedid_hex = None
|
||||
self._altruistic = coin_settings.get('altruistic', True)
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
@@ -601,7 +603,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
return tx.serialize()
|
||||
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
|
||||
@@ -625,6 +627,12 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
|
||||
|
||||
if self.altruistic() and kbsf:
|
||||
# Add mercy_keyshare
|
||||
tx.vout.append(self.txoType()(0, CScript([OP_RETURN, b'XBSW', kbsf])))
|
||||
else:
|
||||
self._log.debug('Not attaching mercy output, have kbsf {}.'.format('true' if kbsf else 'false'))
|
||||
|
||||
dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
@@ -1086,14 +1094,18 @@ class BTCInterface(Secp256k1Interface):
|
||||
return pay_fee
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
self._log.info('spendBLockTx: {} {}\n'.format(chain_b_lock_txid.hex(), lock_tx_vout))
|
||||
locked_n = lock_tx_vout
|
||||
|
||||
Kbs = self.getPubkey(kbs)
|
||||
script_pk = self.getPkDest(Kbs)
|
||||
locked_n = findOutput(lock_tx, script_pk)
|
||||
|
||||
if locked_n is None:
|
||||
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
locked_n = findOutput(lock_tx, script_pk)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
|
||||
pkh_to = self.decodeAddress(address_to)
|
||||
|
||||
tx = CTransaction()
|
||||
@@ -1148,7 +1160,8 @@ class BTCInterface(Secp256k1Interface):
|
||||
return None
|
||||
|
||||
try:
|
||||
tx = self.rpc_wallet('gettransaction', [txid.hex()])
|
||||
# set `include_watchonly` explicitly to `True` to get transactions for watchonly addresses also in BCH
|
||||
tx = self.rpc_wallet('gettransaction', [txid.hex(), True])
|
||||
|
||||
block_height = 0
|
||||
if 'blockhash' in tx:
|
||||
@@ -1491,6 +1504,18 @@ class BTCInterface(Secp256k1Interface):
|
||||
'amount': txjs['vout'][n]['value']
|
||||
}
|
||||
|
||||
def inspectSwipeTx(self, tx: dict):
|
||||
mercy_keyshare = None
|
||||
for vout in tx['vout']:
|
||||
script_bytes = bytes.fromhex(vout['scriptPubKey']['hex'])
|
||||
if len(script_bytes) < 39:
|
||||
continue
|
||||
if script_bytes[0] != OP_RETURN:
|
||||
continue
|
||||
script_bytes[0]
|
||||
return script_bytes[7: 7 + 32]
|
||||
return None
|
||||
|
||||
def isTxExistsError(self, err_str: str) -> bool:
|
||||
return 'Transaction already in block chain' in err_str
|
||||
|
||||
|
||||
247
basicswap/interface/contrib/bch_test_framework/cashaddress.py
Normal file
247
basicswap/interface/contrib/bch_test_framework/cashaddress.py
Normal 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] + list(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 = 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")
|
||||
43
basicswap/interface/contrib/bch_test_framework/script.py
Normal file
43
basicswap/interface/contrib/bch_test_framework/script.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
from basicswap.contrib.test_framework.script import CScriptOp
|
||||
|
||||
|
||||
OP_TXINPUTCOUNT = CScriptOp(0xc3)
|
||||
OP_1 = CScriptOp(0x51)
|
||||
OP_NUMEQUALVERIFY = CScriptOp(0x9d)
|
||||
OP_TXOUTPUTCOUNT = CScriptOp(0xc4)
|
||||
OP_0 = CScriptOp(0x00)
|
||||
OP_UTXOVALUE = CScriptOp(0xc6)
|
||||
OP_OUTPUTVALUE = CScriptOp(0xcc)
|
||||
OP_SUB = CScriptOp(0x94)
|
||||
OP_UTXOTOKENCATEGORY = CScriptOp(0xce)
|
||||
OP_OUTPUTTOKENCATEGORY = CScriptOp(0xd1)
|
||||
OP_EQUALVERIFY = CScriptOp(0x88)
|
||||
OP_UTXOTOKENCOMMITMENT = CScriptOp(0xcf)
|
||||
OP_OUTPUTTOKENCOMMITMENT = CScriptOp(0xd2)
|
||||
OP_UTXOTOKENAMOUNT = CScriptOp(0xd0)
|
||||
OP_OUTPUTTOKENAMOUNT = CScriptOp(0xd3)
|
||||
OP_INPUTSEQUENCENUMBER = CScriptOp(0xcb)
|
||||
OP_NOTIF = CScriptOp(0x64)
|
||||
OP_OUTPUTBYTECODE = CScriptOp(0xcd)
|
||||
OP_OVER = CScriptOp(0x78)
|
||||
OP_CHECKDATASIG = CScriptOp(0xba)
|
||||
OP_CHECKDATASIGVERIFY = CScriptOp(0xbb)
|
||||
OP_ELSE = CScriptOp(0x67)
|
||||
OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
|
||||
OP_DROP = CScriptOp(0x75)
|
||||
OP_EQUAL = CScriptOp(0x87)
|
||||
OP_ENDIF = CScriptOp(0x68)
|
||||
OP_HASH256 = CScriptOp(0xaa)
|
||||
OP_PUSHBYTES_32 = CScriptOp(0x20)
|
||||
OP_DUP = CScriptOp(0x76)
|
||||
OP_HASH160 = CScriptOp(0xa9)
|
||||
OP_CHECKSIG = CScriptOp(0xac)
|
||||
OP_SHA256 = CScriptOp(0xa8)
|
||||
OP_VERIFY = CScriptOp(0x69)
|
||||
@@ -262,6 +262,7 @@ class DCRInterface(Secp256k1Interface):
|
||||
|
||||
self._use_segwit = True # Decred is natively segwit
|
||||
self._connection_type = coin_settings['connection_type']
|
||||
self._altruistic = coin_settings.get('altruistic', True)
|
||||
|
||||
def open_rpc(self):
|
||||
return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
@@ -1232,7 +1233,7 @@ class DCRInterface(Secp256k1Interface):
|
||||
|
||||
return True
|
||||
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class FIROInterface(BTCInterface):
|
||||
# No multiwallet support
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def getExchangeName(self, exchange_name):
|
||||
def getExchangeName(self, exchange_name: str) -> str:
|
||||
return 'zcoin'
|
||||
|
||||
def initialiseWallet(self, key):
|
||||
|
||||
@@ -95,8 +95,6 @@ class LTCInterfaceMWEB(LTCInterface):
|
||||
|
||||
def coin_name(self) -> str:
|
||||
coin_chainparams = chainparams[Coins.LTC]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker'] + ' MWEB'
|
||||
return coin_chainparams['name'].capitalize() + ' MWEB'
|
||||
|
||||
def ticker(self) -> str:
|
||||
|
||||
@@ -668,7 +668,7 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
return tx.serialize()
|
||||
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
|
||||
|
||||
@@ -622,7 +622,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
|
||||
return True
|
||||
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv, kbsf=None):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
|
||||
@@ -364,7 +364,8 @@ def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
||||
bid_id = bytes.fromhex(url_split[3])
|
||||
assert (len(bid_id) == 28)
|
||||
|
||||
show_txns = False
|
||||
show_txns: bool = False
|
||||
with_events: bool = False
|
||||
if post_string != '':
|
||||
post_data = getFormData(post_string, is_json)
|
||||
if have_data_entry(post_data, 'accept'):
|
||||
@@ -376,6 +377,8 @@ def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
||||
|
||||
if have_data_entry(post_data, 'show_extra'):
|
||||
show_txns = True
|
||||
if have_data_entry(post_data, 'with_events'):
|
||||
with_events = True
|
||||
|
||||
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
|
||||
assert (bid), 'Unknown bid ID'
|
||||
@@ -389,6 +392,17 @@ def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
||||
|
||||
if len(url_split) > 4 and url_split[4] == 'states':
|
||||
old_states = listOldBidStates(bid)
|
||||
|
||||
if with_events:
|
||||
new_list = []
|
||||
for entry in old_states:
|
||||
entry_list = list(entry)
|
||||
entry_list.insert(1, 'state')
|
||||
new_list.append(entry_list)
|
||||
for event in events:
|
||||
new_list.append([event['at'], 'event', event['desc']])
|
||||
old_states = sorted(new_list, key=lambda x: x[0])
|
||||
|
||||
return bytes(json.dumps(old_states), 'UTF-8')
|
||||
|
||||
edit_bid = False
|
||||
|
||||
@@ -163,7 +163,11 @@ def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
|
||||
class XmrSwapInterface(ProtocolInterface):
|
||||
swap_type = SwapTypes.XMR_SWAP
|
||||
|
||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes) -> CScript:
|
||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
||||
# fallthrough to ci if genScriptLockTxScript is implemented there
|
||||
if hasattr(ci, 'genScriptLockTxScript') and callable(ci.genScriptLockTxScript):
|
||||
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
|
||||
|
||||
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
|
||||
Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
|
||||
|
||||
|
||||
BIN
basicswap/static/images/coins/Bitcoin%20Cash.png
Normal file
BIN
basicswap/static/images/coins/Bitcoin%20Cash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
@@ -15,6 +15,7 @@ const coinNameToSymbol = {
|
||||
'Decred': 'decred',
|
||||
'Zano': 'zano',
|
||||
'Dogecoin': 'dogecoin',
|
||||
'Bitcoin Cash': 'bitcoincash'
|
||||
};
|
||||
|
||||
function makePostRequest(url, headers = {}) {
|
||||
@@ -166,7 +167,7 @@ const itemsPerPage = 100;
|
||||
const coinIdToName = {
|
||||
1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred',
|
||||
6: 'monero', 7: 'particl blind', 8: 'particl anon',
|
||||
9: 'wownero', 11: 'pivx', 13: 'firo'
|
||||
9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash'
|
||||
};
|
||||
|
||||
// DOM
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
document.getElementById('get_rate_inferred_button').addEventListener('click', getRateInferred);
|
||||
|
||||
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
|
||||
const adaptor_sig_only_coins = ['6' /* XMR */,'9' /* WOW */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */];
|
||||
const adaptor_sig_only_coins = ['6' /* XMR */,'9' /* WOW */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */, '17' /* BCH */];
|
||||
const secret_hash_only_coins = ['11' /* PIVX */, '12' /* DASH */];
|
||||
let make_hidden = false;
|
||||
if (adaptor_sig_only_coins.includes(coin_from) || adaptor_sig_only_coins.includes(coin_to)) {
|
||||
|
||||
@@ -878,6 +878,7 @@ const coinNameToSymbol = {
|
||||
'PIVX': 'PIVX',
|
||||
'Decred': 'DCR',
|
||||
'Zano': 'ZANO',
|
||||
'Bitcoin Cash': 'BCH',
|
||||
};
|
||||
|
||||
const getUsdValue = (cryptoValue, coinSymbol) => {
|
||||
|
||||
@@ -257,6 +257,7 @@ const coinNameToSymbol = {
|
||||
'PIVX': 'PIVX',
|
||||
'Decred': 'DCR',
|
||||
'Zano': 'ZANO',
|
||||
'Bitcoin Cash': 'BCH',
|
||||
};
|
||||
|
||||
const getUsdValue = async (cryptoValue, coinSymbol) => {
|
||||
|
||||
@@ -161,7 +161,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
||||
page_data['swap_type'] = get_data_entry(form_data, 'swap_type')
|
||||
parsed_data['swap_type'] = page_data['swap_type']
|
||||
swap_type = swap_type_from_string(parsed_data['swap_type'])
|
||||
elif parsed_data['coin_from'] in swap_client.scriptless_coins or parsed_data['coin_to'] in swap_client.scriptless_coins:
|
||||
elif parsed_data['coin_from'] in swap_client.adaptor_swap_only_coins or parsed_data['coin_to'] in swap_client.adaptor_swap_only_coins:
|
||||
parsed_data['swap_type'] = strSwapType(SwapTypes.XMR_SWAP)
|
||||
swap_type = SwapTypes.XMR_SWAP
|
||||
else:
|
||||
@@ -224,7 +224,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
||||
|
||||
try:
|
||||
if len(errors) == 0 and page_data['swap_style'] == 'xmr':
|
||||
reverse_bid: bool = swap_client.is_reverse_ads_bid(coin_from)
|
||||
reverse_bid: bool = swap_client.is_reverse_ads_bid(coin_from, coin_to)
|
||||
ci_leader = ci_to if reverse_bid else ci_from
|
||||
ci_follower = ci_from if reverse_bid else ci_to
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import json
|
||||
import struct
|
||||
from basicswap.util import (
|
||||
hex_or_none,
|
||||
make_int,
|
||||
format_timestamp,
|
||||
)
|
||||
@@ -157,7 +158,7 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
|
||||
ci_from = swap_client.ci(Coins(offer.coin_from))
|
||||
ci_to = swap_client.ci(Coins(offer.coin_to))
|
||||
|
||||
reverse_bid: bool = swap_client.is_reverse_ads_bid(offer.coin_from)
|
||||
reverse_bid: bool = swap_client.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
|
||||
ci_leader = ci_to if reverse_bid else ci_from
|
||||
ci_follower = ci_from if reverse_bid else ci_to
|
||||
|
||||
@@ -303,7 +304,7 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
|
||||
confirms = None
|
||||
if swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height:
|
||||
confirms = (swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] - bid.xmr_a_lock_tx.chain_height) + 1
|
||||
txns.append({'type': 'Chain A Lock', 'txid': bid.xmr_a_lock_tx.txid.hex(), 'confirms': confirms})
|
||||
txns.append({'type': 'Chain A Lock', 'txid': hex_or_none(bid.xmr_a_lock_tx.txid), 'confirms': confirms})
|
||||
if bid.xmr_a_lock_spend_tx:
|
||||
txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()})
|
||||
if bid.xmr_b_lock_tx:
|
||||
@@ -435,8 +436,8 @@ def getCoinName(c):
|
||||
return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB'
|
||||
|
||||
coin_chainparams = chainparams[c]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker']
|
||||
if 'display_name' in coin_chainparams:
|
||||
return coin_chainparams['display_name']
|
||||
return coin_chainparams['name'].capitalize()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2018-2023 tecnovert
|
||||
# Copyright (c) 2024 The Basicswap developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -215,3 +216,9 @@ def zeroIfNone(value) -> int:
|
||||
if value is None:
|
||||
return 0
|
||||
return value
|
||||
|
||||
|
||||
def hex_or_none(value: bytes) -> str:
|
||||
if value is None:
|
||||
return 'None'
|
||||
return value.hex()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import struct
|
||||
import hashlib
|
||||
from basicswap.contrib.test_framework.script import OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4, CScriptInvalidError, CScriptTruncatedPushDataError
|
||||
from basicswap.script import OpCodes
|
||||
|
||||
|
||||
@@ -32,6 +33,51 @@ def decodeScriptNum(script_bytes, o):
|
||||
return (v, 1 + num_len)
|
||||
|
||||
|
||||
def decodePushData(script_bytes, o):
|
||||
datasize = None
|
||||
pushdata_type = None
|
||||
i = o
|
||||
opcode = script_bytes[i]
|
||||
i += 1
|
||||
|
||||
if opcode < OP_PUSHDATA1:
|
||||
pushdata_type = 'PUSHDATA(%d)' % opcode
|
||||
datasize = opcode
|
||||
|
||||
elif opcode == OP_PUSHDATA1:
|
||||
pushdata_type = 'PUSHDATA1'
|
||||
if i >= len(script_bytes):
|
||||
raise CScriptInvalidError('PUSHDATA1: missing data length')
|
||||
datasize = script_bytes[i]
|
||||
i += 1
|
||||
|
||||
elif opcode == OP_PUSHDATA2:
|
||||
pushdata_type = 'PUSHDATA2'
|
||||
if i + 1 >= len(script_bytes):
|
||||
raise CScriptInvalidError('PUSHDATA2: missing data length')
|
||||
datasize = script_bytes[i] + (script_bytes[i + 1] << 8)
|
||||
i += 2
|
||||
|
||||
elif opcode == OP_PUSHDATA4:
|
||||
pushdata_type = 'PUSHDATA4'
|
||||
if i + 3 >= len(script_bytes):
|
||||
raise CScriptInvalidError('PUSHDATA4: missing data length')
|
||||
datasize = script_bytes[i] + (script_bytes[i + 1] << 8) + (script_bytes[i + 2] << 16) + (script_bytes[i + 3] << 24)
|
||||
i += 4
|
||||
|
||||
else:
|
||||
assert False # shouldn't happen
|
||||
|
||||
data = bytes(script_bytes[i:i + datasize])
|
||||
|
||||
# Check for truncation
|
||||
if len(data) < datasize:
|
||||
raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data)
|
||||
|
||||
# return data and the number of bytes to skip forward
|
||||
return (data, i + datasize - o)
|
||||
|
||||
|
||||
def getP2SHScriptForHash(p2sh):
|
||||
return bytes((OpCodes.OP_HASH160, 0x14)) \
|
||||
+ p2sh \
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
|
||||
0.14.2
|
||||
==============
|
||||
|
||||
- BCH support.
|
||||
- Mercy outputs on swipe txns of BTC descended coins.
|
||||
- Disable by setting 'altruistic' to false in basicswap.json
|
||||
|
||||
|
||||
|
||||
0.13.2
|
||||
==============
|
||||
|
||||
|
||||
27
docker/production/bitcoincash/Dockerfile
Normal file
27
docker/production/bitcoincash/Dockerfile
Normal 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 BITCOINCASH_DATA /data
|
||||
|
||||
RUN groupadd -r bitcoincash && useradd -r -m -g bitcoincash bitcoincash \
|
||||
&& apt-get update \
|
||||
&& apt-get install -qq --no-install-recommends gosu \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir "$BITCOINCASH_DATA" \
|
||||
&& chown -R bitcoincash:bitcoincash "$BITCOINCASH_DATA" \
|
||||
&& ln -sfn "$BITCOINCASH_DATA" /home/bitcoincash/.bitcoincash \
|
||||
&& chown -h bitcoincash:bitcoincash /home/bitcoincash/.bitcoincash
|
||||
VOLUME /data
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
EXPOSE 8332 8333 18332 18333 18443 18444
|
||||
CMD ["/bitcoincash/bitcoind", "--datadir=/data"]
|
||||
11
docker/production/bitcoincash/entrypoint.sh
Executable file
11
docker/production/bitcoincash/entrypoint.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ "$1" == "bitcoin-cli" || "$1" == "bitcoin-tx" || "$1" == "bitcoind" || "$1" == "test_bitcoin" ]]; then
|
||||
mkdir -p "$BITCOINCASH_DATA"
|
||||
|
||||
chown -h bitcoincash:bitcoincash /home/bitcoincash/.bitcoincash
|
||||
exec gosu bitcoincash "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
16
docker/production/compose-fragments/1_bitcoincash.yml
Normal file
16
docker/production/compose-fragments/1_bitcoincash.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
bitcoincash_core:
|
||||
image: i_bitcoincash
|
||||
build:
|
||||
context: bitcoincash
|
||||
dockerfile: Dockerfile
|
||||
container_name: bitcoincash_core
|
||||
volumes:
|
||||
- ${DATA_PATH}/bitcoincash:/data
|
||||
expose:
|
||||
- ${BCH_RPC_PORT}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
||||
@@ -7,7 +7,7 @@
|
||||
volumes:
|
||||
- ${DATA_PATH}/wownero_daemon:/data
|
||||
expose:
|
||||
- ${BASE_WOW_RPC_PORT}
|
||||
- ${WOW_RPC_PORT}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
- ${DATA_PATH}/pivx:/data/pivx
|
||||
- ${DATA_PATH}/dash:/data/dash
|
||||
- ${DATA_PATH}/firo:/data/firo
|
||||
- ${DATA_PATH}/bitcoincash:/data/bitcoincash
|
||||
environment:
|
||||
- TZ
|
||||
- BSX_DOCKER_MODE
|
||||
@@ -50,8 +51,8 @@
|
||||
- DEFAULT_XMR_RESTORE_HEIGHT
|
||||
- WOW_DATA_DIR
|
||||
- WOW_RPC_HOST
|
||||
- BASE_WOW_RPC_PORT
|
||||
- BASE_WOW_ZMQ_PORT
|
||||
- WOW_RPC_PORT
|
||||
- WOW_ZMQ_PORT
|
||||
- WOW_WALLETS_DIR
|
||||
- WOW_WALLET_RPC_HOST
|
||||
- WOW_WALLET_RPC_PORT
|
||||
@@ -73,4 +74,9 @@
|
||||
- FIRO_RPC_PORT
|
||||
- FIRO_RPC_USER
|
||||
- FIRO_RPC_PWD
|
||||
- BCH_DATA_DIR
|
||||
- BCH_RPC_HOST
|
||||
- BCH_RPC_PORT
|
||||
- BCH_RPC_USER
|
||||
- BCH_RPC_PWD
|
||||
restart: "no"
|
||||
|
||||
@@ -72,3 +72,9 @@ FIRO_RPC_HOST=firo_core
|
||||
FIRO_RPC_PORT=8888
|
||||
FIRO_RPC_USER=firo_user
|
||||
FIRO_RPC_PWD=firo_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
|
||||
|
||||
@@ -165,7 +165,7 @@ Prepare config files:
|
||||
|
||||
export ADD_COIN=monero
|
||||
docker-compose -f docker-compose-prepare.yml run --rm swapprepare \
|
||||
basicswap-prepare --nocores --usecontainers --addcoin=${ADD_COIN} --htmlhost="0.0.0.0" --particl_mnemonic=none
|
||||
basicswap-prepare --nocores --usecontainers --addcoin=${ADD_COIN} --particl_mnemonic=none
|
||||
|
||||
|
||||
Prepare wallet:
|
||||
@@ -177,7 +177,7 @@ Prepare wallet:
|
||||
|
||||
docker-compose -f docker-compose-prepare.yml run -e WALLET_ENCRYPTION_PWD=walletpass \
|
||||
--rm swapprepare \
|
||||
basicswap-prepare --initwalletsonly --withoutcoin=particl --withcoin=monero
|
||||
basicswap-prepare --initwalletsonly --withoutcoin=particl --withcoin=${ADD_COIN}
|
||||
|
||||
docker-compose -f docker-compose-prepare.yml stop
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ ENV LANG=C.UTF-8 \
|
||||
|
||||
RUN apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata;
|
||||
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata wget unzip;
|
||||
|
||||
ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip
|
||||
ARG BASICSWAP_DIR=basicswap-master
|
||||
|
||||
@@ -35,10 +35,6 @@ LTC_BASE_PORT = 34792
|
||||
LTC_BASE_RPC_PORT = 35792
|
||||
LTC_BASE_ZMQ_PORT = 36792
|
||||
|
||||
DCR_BASE_PORT = 18555
|
||||
DCR_BASE_RPC_PORT = 9110
|
||||
|
||||
|
||||
PIVX_BASE_PORT = 34892
|
||||
PIVX_BASE_RPC_PORT = 35892
|
||||
PIVX_BASE_ZMQ_PORT = 36892
|
||||
@@ -46,7 +42,7 @@ PIVX_BASE_ZMQ_PORT = 36892
|
||||
PREFIX_SECRET_KEY_REGTEST = 0x2e
|
||||
|
||||
|
||||
def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_PORT, base_rpc_port=BASE_RPC_PORT, num_nodes=3):
|
||||
def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_PORT, base_rpc_port=BASE_RPC_PORT, num_nodes=3, extra_opts=[]):
|
||||
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
|
||||
if not os.path.exists(node_dir):
|
||||
os.makedirs(node_dir)
|
||||
@@ -75,8 +71,12 @@ 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')
|
||||
|
||||
for opt in extra_opts:
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if base_p2p_port == BTC_BASE_PORT:
|
||||
fp.write('deprecatedrpc=create_bdb\n')
|
||||
elif base_p2p_port == BASE_PORT: # Particl
|
||||
@@ -141,7 +141,7 @@ def wait_for_bid(delay_event, swap_client, bid_id, state=None, sent: bool = Fals
|
||||
assert (len(bids) < 2)
|
||||
for bid in bids:
|
||||
if bid[2] == bid_id:
|
||||
if isinstance(state, list):
|
||||
if isinstance(state, (list, tuple)):
|
||||
if bid[5] in state:
|
||||
return
|
||||
else:
|
||||
|
||||
@@ -29,9 +29,15 @@ from tests.basicswap.common import (
|
||||
BASE_PORT, BASE_RPC_PORT,
|
||||
BTC_BASE_PORT, BTC_BASE_RPC_PORT, BTC_BASE_TOR_PORT,
|
||||
LTC_BASE_PORT, LTC_BASE_RPC_PORT,
|
||||
DCR_BASE_PORT, DCR_BASE_RPC_PORT,
|
||||
PIVX_BASE_PORT,
|
||||
)
|
||||
from tests.basicswap.extended.test_dcr import (
|
||||
DCR_BASE_PORT, DCR_BASE_RPC_PORT,
|
||||
)
|
||||
from tests.basicswap.test_bch_xmr import (
|
||||
BCH_BASE_PORT, BCH_BASE_RPC_PORT,
|
||||
)
|
||||
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
|
||||
import basicswap.config as cfg
|
||||
@@ -48,6 +54,8 @@ BITCOIN_TOR_PORT_BASE = int(os.getenv('BITCOIN_TOR_PORT_BASE', BTC_BASE_TOR_PORT
|
||||
|
||||
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
|
||||
DECRED_RPC_PORT_BASE = int(os.getenv('DECRED_RPC_PORT_BASE', DCR_BASE_RPC_PORT))
|
||||
BITCOINCASH_RPC_PORT_BASE = int(os.getenv('BITCOINCASH_RPC_PORT_BASE', BCH_BASE_RPC_PORT))
|
||||
|
||||
|
||||
FIRO_BASE_PORT = 34832
|
||||
FIRO_BASE_RPC_PORT = 35832
|
||||
@@ -97,6 +105,8 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
|
||||
os.environ['BTC_RPC_PORT'] = str(BITCOIN_RPC_PORT_BASE)
|
||||
os.environ['LTC_RPC_PORT'] = str(LITECOIN_RPC_PORT_BASE)
|
||||
os.environ['DCR_RPC_PORT'] = str(DECRED_RPC_PORT_BASE)
|
||||
os.environ['BCH_PORT'] = str(BCH_BASE_PORT)
|
||||
os.environ['BCH_RPC_PORT'] = str(BITCOINCASH_RPC_PORT_BASE)
|
||||
os.environ['FIRO_RPC_PORT'] = str(FIRO_RPC_PORT_BASE)
|
||||
|
||||
os.environ['XMR_RPC_USER'] = 'xmr_user'
|
||||
@@ -117,6 +127,7 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
|
||||
'-regtest',
|
||||
f'-withcoins={with_coins}',
|
||||
'-noextractover',
|
||||
'-noreleasesizecheck',
|
||||
'-xmrrestoreheight=0']
|
||||
if mnemonic_in:
|
||||
testargs.append(f'-particl_mnemonic="{mnemonic_in}"')
|
||||
@@ -311,6 +322,33 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
|
||||
if ip != node_id:
|
||||
fp.write('add-exclusive-node=127.0.0.1:{}\n'.format(XMR_BASE_P2P_PORT + ip + port_ofs))
|
||||
|
||||
if 'bitcoincash' in coins_array:
|
||||
config_filename = os.path.join(datadir_path, 'bitcoincash', 'bitcoin.conf')
|
||||
with open(config_filename, 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(config_filename, 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('prune'):
|
||||
fp.write(line)
|
||||
# NOTE: port is set (when starting daemon) from basicswap.json
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('dnsseed=0\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('upnp=0\n')
|
||||
if use_rpcauth:
|
||||
salt = generate_salt(16)
|
||||
rpc_user = 'test_bch_' + str(node_id)
|
||||
rpc_pass = 'test_bch_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['bitcoincash']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['bitcoincash']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(BCH_BASE_PORT + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('bch{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
with open(config_path) as fs:
|
||||
settings = json.load(fs)
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ from tests.basicswap.common import (
|
||||
BTC_BASE_RPC_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
)
|
||||
from tests.basicswap.test_bch_xmr import (
|
||||
BCH_BASE_RPC_PORT,
|
||||
)
|
||||
from tests.basicswap.util import (
|
||||
make_boolean,
|
||||
read_json_api,
|
||||
@@ -66,6 +69,7 @@ UI_PORT = 12700 + PORT_OFS
|
||||
PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT))
|
||||
BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT))
|
||||
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
|
||||
BITCOINCASH_RPC_PORT_BASE = int(os.getenv('BITCOINCASH_RPC_PORT_BASE', BCH_BASE_RPC_PORT))
|
||||
DECRED_WALLET_RPC_PORT_BASE = int(os.getenv('DECRED_WALLET_RPC_PORT_BASE', 9210))
|
||||
XMR_BASE_RPC_PORT = int(os.getenv('XMR_BASE_RPC_PORT', XMR_BASE_RPC_PORT))
|
||||
TEST_COINS_LIST = os.getenv('TEST_COINS_LIST', 'bitcoin,monero')
|
||||
@@ -99,6 +103,11 @@ def calldcrrpc(node_id, method, params=[], wallet=None, base_rpc_port=DECRED_WAL
|
||||
return callrpc_dcr(base_rpc_port + node_id, auth, method, params)
|
||||
|
||||
|
||||
def callbchrpc(node_id, method, params=[], wallet=None, base_rpc_port=BITCOINCASH_RPC_PORT_BASE + PORT_OFS):
|
||||
auth = 'test_bch_{0}:test_bch_pwd_{0}'.format(node_id)
|
||||
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
||||
|
||||
|
||||
def updateThread(cls):
|
||||
while not cls.delay_event.is_set():
|
||||
try:
|
||||
@@ -106,6 +115,8 @@ def updateThread(cls):
|
||||
callbtcrpc(0, 'generatetoaddress', [1, cls.btc_addr])
|
||||
if cls.ltc_addr is not None:
|
||||
callltcrpc(0, 'generatetoaddress', [1, cls.ltc_addr])
|
||||
if cls.bch_addr is not None:
|
||||
callbchrpc(0, 'generatetoaddress', [1, cls.bch_addr])
|
||||
except Exception as e:
|
||||
print('updateThread error', str(e))
|
||||
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
|
||||
@@ -174,6 +185,7 @@ class Test(unittest.TestCase):
|
||||
cls.processes = []
|
||||
cls.btc_addr = None
|
||||
cls.ltc_addr = None
|
||||
cls.bch_addr = None
|
||||
cls.xmr_addr = None
|
||||
cls.dcr_addr = 'SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH'
|
||||
cls.dcr_acc = None
|
||||
@@ -257,6 +269,14 @@ class Test(unittest.TestCase):
|
||||
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
|
||||
self.update_thread_dcr.start()
|
||||
|
||||
if 'bitcoincash' in TEST_COINS_LIST:
|
||||
self.bch_addr = callbchrpc(0, 'getnewaddress', ['mining_addr'], wallet='wallet.dat')
|
||||
num_blocks: int = 200
|
||||
have_blocks: int = callbchrpc(0, 'getblockcount')
|
||||
if have_blocks < num_blocks:
|
||||
logging.info('Mining %d Bitcoincash blocks to %s', num_blocks - have_blocks, self.bch_addr)
|
||||
callbchrpc(0, 'generatetoaddress', [num_blocks - have_blocks, self.bch_addr], wallet='wallet.dat')
|
||||
|
||||
if RESET_TEST:
|
||||
# Lower output split threshold for more stakeable outputs
|
||||
for i in range(NUM_NODES):
|
||||
|
||||
624
tests/basicswap/test_bch_xmr.py
Normal file
624
tests/basicswap/test_bch_xmr.py
Normal file
@@ -0,0 +1,624 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 The Basicswap developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import unittest
|
||||
|
||||
import basicswap.config as cfg
|
||||
from basicswap.basicswap import (
|
||||
Coins,
|
||||
SwapTypes,
|
||||
)
|
||||
from basicswap.bin.run import startDaemon
|
||||
from basicswap.util.crypto import sha256
|
||||
from tests.basicswap.test_btc_xmr import BasicSwapTest
|
||||
from tests.basicswap.common import (
|
||||
make_rpc_func,
|
||||
prepareDataDir,
|
||||
stopDaemons,
|
||||
waitForRPC,
|
||||
)
|
||||
from basicswap.contrib.test_framework.messages import (
|
||||
ToHex,
|
||||
CTxIn,
|
||||
COutPoint,
|
||||
CTransaction,
|
||||
)
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript,
|
||||
OP_EQUAL,
|
||||
OP_CHECKLOCKTIMEVERIFY,
|
||||
OP_CHECKSEQUENCEVERIFY,
|
||||
)
|
||||
from basicswap.interface.bch import BCHInterface
|
||||
from basicswap.rpc import (
|
||||
callrpc_cli,
|
||||
)
|
||||
from basicswap.util import ensure
|
||||
from .test_xmr import test_delay_event, callnoderpc
|
||||
|
||||
from coincurve.ecdsaotves import (
|
||||
ecdsaotves_enc_sign,
|
||||
ecdsaotves_enc_verify,
|
||||
ecdsaotves_dec_sig
|
||||
)
|
||||
|
||||
BITCOINCASH_BINDIR = os.path.expanduser(os.getenv('BITCOINCASH_BINDIR', os.path.join(cfg.DEFAULT_TEST_BINDIR, 'bitcoincash')))
|
||||
BITCOINCASHD = os.getenv('BITCOINCASHD', 'bitcoind' + cfg.bin_suffix)
|
||||
BITCOINCASH_CLI = os.getenv('BITCOINCASH_CLI', 'bitcoin-cli' + cfg.bin_suffix)
|
||||
BITCOINCASH_TX = os.getenv('BITCOINCASH_TX', 'bitcoin-tx' + cfg.bin_suffix)
|
||||
|
||||
BCH_BASE_PORT = 41792
|
||||
BCH_BASE_RPC_PORT = 42792
|
||||
BCH_BASE_ZMQ_PORT = 43792
|
||||
BCH_BASE_TOR_PORT = 43732
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
bch_lock_spend_tx = '0200000001bfc6bbb47851441c7827059ae337a06aa9064da7f9537eb9243e45766c3dd34c00000000d8473045022100a0161ea14d3b41ed41250c8474fc8ec6ce1cab8df7f401e69ecf77c2ab63d82102207a2a57ddf2ea400e09ea059f3b261da96f5098858b17239931f3cc2fb929bb2a4c8ec3519dc4519d02e80300c600cc949d00ce00d18800cf00d28800d000d39d00cb641976a91481ec21969399d15c26af089d5db437ead066c5ba88ac00cd788821024ffcc0481629866671d89f05f3da813a2aacec1b52e69b8c0c586b665f5d4574ba6752b27523aa20df65a90e9becc316ff5aca44d4e06dfaade56622f32bafa197aba706c5e589758700cd87680000000001251cde06000000001976a91481ec21969399d15c26af089d5db437ead066c5ba88ac00000000'
|
||||
bch_lock_script = 'c3519dc4519d02e80300c600cc949d00ce00d18800cf00d28800d000d39d00cb641976a91481ec21969399d15c26af089d5db437ead066c5ba88ac00cd788821024ffcc0481629866671d89f05f3da813a2aacec1b52e69b8c0c586b665f5d4574ba6752b27523aa20df65a90e9becc316ff5aca44d4e06dfaade56622f32bafa197aba706c5e589758700cd8768'
|
||||
bch_lock_spend_script = '473045022100a0161ea14d3b41ed41250c8474fc8ec6ce1cab8df7f401e69ecf77c2ab63d82102207a2a57ddf2ea400e09ea059f3b261da96f5098858b17239931f3cc2fb929bb2a4c8ec3519dc4519d02e80300c600cc949d00ce00d18800cf00d28800d000d39d00cb641976a91481ec21969399d15c26af089d5db437ead066c5ba88ac00cd788821024ffcc0481629866671d89f05f3da813a2aacec1b52e69b8c0c586b665f5d4574ba6752b27523aa20df65a90e9becc316ff5aca44d4e06dfaade56622f32bafa197aba706c5e589758700cd8768'
|
||||
bch_lock_swipe_script = '4c8fc3519dc4519d02e80300c600cc949d00ce00d18800cf00d28800d000d39d00cb641976a9141ab50aedd2e48297073f0f6eef46f97b37c9354e88ac00cd7888210234fe304a5b129b8265c177c92aa40b7840e8303f8b0fcca2359023163c7c2768ba670120b27523aa20191b09e40d1277fa14fea1e9b41e4fcc4528c9cb77e39e1b7b1a0b3332180cb78700cd8768'
|
||||
|
||||
coin_settings = {'rpcport': 0, 'rpcauth': 'none', 'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': False, 'connection_type': 'rpc'}
|
||||
|
||||
|
||||
class TestXmrBchSwapInterface(unittest.TestCase):
|
||||
def test_extractScriptLockScriptValues(self):
|
||||
ci = BCHInterface(coin_settings, "regtest")
|
||||
|
||||
script_bytes = CScript(bytes.fromhex(bch_lock_script))
|
||||
ci.extractScriptLockScriptValues(script_bytes)
|
||||
|
||||
script_bytes = CScript(bytes.fromhex(bch_lock_spend_script))
|
||||
signature, mining_fee, out_1, out_2, public_key, timelock = ci.extractScriptLockScriptValuesFromScriptSig(script_bytes)
|
||||
ensure(signature is not None, 'signature not present')
|
||||
|
||||
script_bytes = CScript(bytes.fromhex(bch_lock_swipe_script))
|
||||
signature, mining_fee, out_1, out_2, public_key, timelock = ci.extractScriptLockScriptValuesFromScriptSig(script_bytes)
|
||||
ensure(signature is None, 'signature present')
|
||||
|
||||
|
||||
class TestBCH(BasicSwapTest):
|
||||
__test__ = True
|
||||
test_coin = Coins.BCH
|
||||
test_coin_from = Coins.BCH
|
||||
base_rpc_port = BCH_BASE_RPC_PORT
|
||||
|
||||
bch_daemons = []
|
||||
start_ltc_nodes = False
|
||||
bch_addr = None
|
||||
|
||||
@classmethod
|
||||
def prepareExtraDataDir(cls, i):
|
||||
if not cls.restore_instance:
|
||||
data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'bitcoin.conf', 'bch_', base_p2p_port=BCH_BASE_PORT, base_rpc_port=BCH_BASE_RPC_PORT)
|
||||
|
||||
# Rewrite conf file
|
||||
config_filename: str = os.path.join(cfg.TEST_DATADIRS, 'bch_' + str(i), 'bitcoin.conf')
|
||||
with open(config_filename, 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(config_filename, 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('findpeers'):
|
||||
fp.write(line)
|
||||
|
||||
if os.path.exists(os.path.join(BITCOINCASH_BINDIR, 'bitcoin-wallet')):
|
||||
try:
|
||||
callrpc_cli(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(cfg.TEST_DATADIRS, 'bch_' + str(i)), BITCOINCASH_BINDIR, BITCOINCASHD))
|
||||
logging.info('BCH: Started %s %d', BITCOINCASHD, cls.bch_daemons[-1].handle.pid)
|
||||
waitForRPC(make_rpc_func(i, base_rpc_port=BCH_BASE_RPC_PORT), test_delay_event)
|
||||
|
||||
@classmethod
|
||||
def addPIDInfo(cls, sc, i):
|
||||
sc.setDaemonPID(Coins.BCH, cls.bch_daemons[i].handle.pid)
|
||||
|
||||
@classmethod
|
||||
def prepareExtraCoins(cls):
|
||||
cls.bch_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=BCH_BASE_RPC_PORT, wallet='wallet.dat')
|
||||
if not cls.restore_instance:
|
||||
num_blocks: int = 200
|
||||
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')
|
||||
|
||||
@classmethod
|
||||
def addCoinSettings(cls, settings, datadir, node_id):
|
||||
|
||||
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': BITCOINCASH_BINDIR,
|
||||
'use_segwit': False,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def coins_loop(cls):
|
||||
super(TestBCH, cls).coins_loop()
|
||||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||
try:
|
||||
if cls.bch_addr is not None:
|
||||
ci0.rpc_wallet('generatetoaddress', [1, cls.bch_addr])
|
||||
except Exception as e:
|
||||
logging.warning('coins_loop generate {}'.format(e))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info('Finalising Bitcoincash Test')
|
||||
super(TestBCH, cls).tearDownClass()
|
||||
|
||||
stopDaemons(cls.bch_daemons)
|
||||
cls.bch_daemons.clear()
|
||||
|
||||
def mineBlock(self, num_blocks=1):
|
||||
self.callnoderpc('generatetoaddress', [num_blocks, self.bch_addr])
|
||||
|
||||
def check_softfork_active(self, feature_name):
|
||||
return True
|
||||
|
||||
def test_001_nested_segwit(self):
|
||||
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin.name))
|
||||
logging.info('Skipped')
|
||||
|
||||
def test_002_native_segwit(self):
|
||||
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin.name))
|
||||
logging.info('Skipped')
|
||||
|
||||
def test_003_cltv(self):
|
||||
logging.info('---------- Test {} cltv'.format(self.test_coin.name))
|
||||
|
||||
ci = self.swap_clients[0].ci(self.test_coin)
|
||||
|
||||
self.check_softfork_active('bip65')
|
||||
|
||||
chain_height = self.callnoderpc('getblockcount')
|
||||
script = CScript([chain_height + 3, OP_CHECKLOCKTIMEVERIFY, ])
|
||||
|
||||
script_dest = ci.getScriptDest(script)
|
||||
tx = CTransaction()
|
||||
tx.nVersion = ci.txVersion()
|
||||
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
|
||||
tx_hex = ToHex(tx)
|
||||
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
|
||||
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
|
||||
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
|
||||
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
|
||||
|
||||
addr_out = ci.rpc_wallet('getnewaddress', ['cltv test'])
|
||||
pkh = ci.decodeAddress(addr_out)
|
||||
script_out = ci.getScriptForPubkeyHash(pkh)
|
||||
|
||||
tx_spend = CTransaction()
|
||||
tx_spend.nVersion = ci.txVersion()
|
||||
tx_spend.nLockTime = chain_height + 3
|
||||
tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos), scriptSig=CScript([script,])))
|
||||
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
|
||||
tx_spend_hex = ToHex(tx_spend)
|
||||
|
||||
tx_spend.nLockTime = chain_height + 2
|
||||
tx_spend_invalid_hex = ToHex(tx_spend)
|
||||
|
||||
for tx_hex in [tx_spend_invalid_hex, tx_spend_hex]:
|
||||
try:
|
||||
txid = self.callnoderpc('sendrawtransaction', [tx_hex, ])
|
||||
except Exception as e:
|
||||
assert ('non-final' in str(e))
|
||||
else:
|
||||
assert False, 'Should fail'
|
||||
|
||||
self.mineBlock(5)
|
||||
try:
|
||||
txid = ci.rpc('sendrawtransaction', [tx_spend_invalid_hex, ])
|
||||
except Exception as e:
|
||||
assert ('Locktime requirement not satisfied' in str(e))
|
||||
else:
|
||||
assert False, 'Should fail'
|
||||
|
||||
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
|
||||
self.mineBlock()
|
||||
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
|
||||
sum_addr = 0
|
||||
for entry in ro:
|
||||
if entry['address'] == addr_out:
|
||||
sum_addr += entry['amount']
|
||||
assert (sum_addr == 1.0999)
|
||||
|
||||
# Ensure tx was mined
|
||||
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
|
||||
assert (len(tx_wallet['blockhash']) == 64)
|
||||
|
||||
def test_004_csv(self):
|
||||
logging.info('---------- Test {} csv'.format(self.test_coin.name))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
ci = self.swap_clients[0].ci(self.test_coin)
|
||||
|
||||
self.check_softfork_active('csv')
|
||||
|
||||
script = CScript([3, OP_CHECKSEQUENCEVERIFY, ])
|
||||
|
||||
script_dest = ci.getScriptDest(script)
|
||||
tx = CTransaction()
|
||||
tx.nVersion = ci.txVersion()
|
||||
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
|
||||
tx_hex = ToHex(tx)
|
||||
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
|
||||
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
|
||||
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
|
||||
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
|
||||
|
||||
addr_out = ci.rpc_wallet('getnewaddress', ['csv test'])
|
||||
pkh = ci.decodeAddress(addr_out)
|
||||
script_out = ci.getScriptForPubkeyHash(pkh)
|
||||
|
||||
# Double check output type
|
||||
prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ])
|
||||
assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash')
|
||||
|
||||
tx_spend = CTransaction()
|
||||
tx_spend.nVersion = ci.txVersion()
|
||||
tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos),
|
||||
nSequence=3,
|
||||
scriptSig=CScript([script,])))
|
||||
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
|
||||
tx_spend_hex = ToHex(tx_spend)
|
||||
try:
|
||||
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
|
||||
except Exception as e:
|
||||
assert ('non-BIP68-final' in str(e))
|
||||
else:
|
||||
assert False, 'Should fail'
|
||||
|
||||
self.mineBlock(3)
|
||||
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
|
||||
self.mineBlock(1)
|
||||
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
|
||||
sum_addr = 0
|
||||
for entry in ro:
|
||||
if entry['address'] == addr_out:
|
||||
sum_addr += entry['amount']
|
||||
assert (sum_addr == 1.0999)
|
||||
|
||||
# Ensure tx was mined
|
||||
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
|
||||
assert (len(tx_wallet['blockhash']) == 64)
|
||||
|
||||
def test_005_watchonly(self):
|
||||
logging.info('---------- Test {} watchonly'.format(self.test_coin.name))
|
||||
ci = self.swap_clients[0].ci(self.test_coin)
|
||||
ci1 = self.swap_clients[1].ci(self.test_coin)
|
||||
|
||||
addr = ci.rpc_wallet('getnewaddress', ['watchonly test'])
|
||||
ro = ci1.rpc_wallet('importaddress', [addr, '', False])
|
||||
txid = ci.rpc_wallet('sendtoaddress', [addr, 1.0])
|
||||
tx_hex = ci.rpc('getrawtransaction', [txid, ])
|
||||
ci1.rpc_wallet('sendrawtransaction', [tx_hex, ])
|
||||
ro = ci1.rpc_wallet('gettransaction', [txid, ])
|
||||
assert (ro['txid'] == txid)
|
||||
|
||||
def test_006_getblock_verbosity(self):
|
||||
super().test_006_getblock_verbosity()
|
||||
|
||||
def test_007_hdwallet(self):
|
||||
logging.info('---------- Test {} hdwallet'.format(self.test_coin.name))
|
||||
|
||||
test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b'
|
||||
test_wif = self.swap_clients[0].ci(self.test_coin).encodeKey(bytes.fromhex(test_seed))
|
||||
new_wallet_name = random.randbytes(10).hex()
|
||||
self.callnoderpc('createwallet', [new_wallet_name])
|
||||
self.callnoderpc('sethdseed', [True, test_wif], wallet=new_wallet_name)
|
||||
addr = self.callnoderpc('getnewaddress', wallet=new_wallet_name)
|
||||
self.callnoderpc('unloadwallet', [new_wallet_name])
|
||||
assert (addr == 'bchreg:qqxr67wf5ltty5jvm44zryywmpt7ntdaa50carjt59')
|
||||
|
||||
def test_008_gettxout(self):
|
||||
super().test_008_gettxout()
|
||||
|
||||
def test_009_scantxoutset(self):
|
||||
super().test_009_scantxoutset()
|
||||
|
||||
def test_010_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_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
|
||||
b_receive = ci.getNewAddress()
|
||||
a_refund = ci.getNewAddress()
|
||||
|
||||
refundExtraArgs = dict()
|
||||
lockExtraArgs = dict()
|
||||
|
||||
refundExtraArgs['mining_fee'] = 1000
|
||||
refundExtraArgs['out_1'] = ci.addressToLockingBytecode(a_refund)
|
||||
refundExtraArgs['out_2'] = ci.addressToLockingBytecode(b_receive)
|
||||
refundExtraArgs['public_key'] = B
|
||||
refundExtraArgs['timelock'] = 5
|
||||
|
||||
refund_lock_tx_script = pi.genScriptLockTxScript(ci, A, B, **refundExtraArgs)
|
||||
# will make use of this in `createSCLockRefundTx`
|
||||
refundExtraArgs['refund_lock_tx_script'] = refund_lock_tx_script
|
||||
|
||||
# lock script
|
||||
lockExtraArgs['mining_fee'] = 1000
|
||||
lockExtraArgs['out_1'] = ci.addressToLockingBytecode(b_receive)
|
||||
lockExtraArgs['out_2'] = ci.scriptToP2SH32LockingBytecode(refund_lock_tx_script)
|
||||
lockExtraArgs['public_key'] = A
|
||||
lockExtraArgs['timelock'] = 2
|
||||
|
||||
lock_tx_script = pi.genScriptLockTxScript(ci, A, B, **lockExtraArgs)
|
||||
|
||||
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()])
|
||||
txid = tx_decoded['txid']
|
||||
|
||||
vsize = tx_decoded['size']
|
||||
expect_fee_int = round(fee_rate * vsize)
|
||||
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])
|
||||
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(b_receive)
|
||||
|
||||
msg = sha256(ci.addressToLockingBytecode(b_receive))
|
||||
|
||||
# leader creates an adaptor signature for follower and transmits it to the follower
|
||||
aAdaptorSig = ecdsaotves_enc_sign(a, B, msg)
|
||||
|
||||
# alice verifies the adaptor signature
|
||||
assert (ecdsaotves_enc_verify(A, B, msg, aAdaptorSig))
|
||||
|
||||
# alice decrypts the adaptor signature
|
||||
aAdaptorSig_dec = ecdsaotves_dec_sig(b, aAdaptorSig)
|
||||
|
||||
fee_info = {}
|
||||
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, mining_fee, fee_info=fee_info, ves=aAdaptorSig_dec)
|
||||
vsize_estimated: int = fee_info['vsize']
|
||||
|
||||
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
|
||||
print('lock_spend_tx', lock_spend_tx.hex(), '\n', 'tx_decoded', tx_decoded)
|
||||
txid = tx_decoded['txid']
|
||||
|
||||
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
|
||||
vsize_actual: int = tx_decoded['size']
|
||||
|
||||
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_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 >= vsize_actual)
|
||||
assert (expect_size - vsize_actual < 10)
|
||||
|
||||
def test_011_p2sh(self):
|
||||
# Not used in bsx for native-segwit coins
|
||||
logging.info('---------- Test {} p2sh'.format(self.test_coin.name))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
ci = self.swap_clients[0].ci(self.test_coin)
|
||||
|
||||
script = CScript([2, 2, OP_EQUAL, ])
|
||||
|
||||
script_dest = ci.get_p2sh_script_pubkey(script)
|
||||
tx = CTransaction()
|
||||
tx.nVersion = ci.txVersion()
|
||||
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
|
||||
tx_hex = ToHex(tx)
|
||||
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
|
||||
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
|
||||
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
|
||||
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
|
||||
|
||||
addr_out = ci.rpc_wallet('getnewaddress', ['csv test'])
|
||||
pkh = ci.decodeAddress(addr_out)
|
||||
script_out = ci.getScriptForPubkeyHash(pkh)
|
||||
|
||||
# Double check output type
|
||||
prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ])
|
||||
assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash')
|
||||
|
||||
tx_spend = CTransaction()
|
||||
tx_spend.nVersion = ci.txVersion()
|
||||
tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos),
|
||||
scriptSig=CScript([script,])))
|
||||
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
|
||||
tx_spend_hex = ToHex(tx_spend)
|
||||
|
||||
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
|
||||
self.mineBlock(1)
|
||||
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
|
||||
sum_addr = 0
|
||||
for entry in ro:
|
||||
if entry['address'] == addr_out:
|
||||
sum_addr += entry['amount']
|
||||
assert (sum_addr == 1.0999)
|
||||
|
||||
# Ensure tx was mined
|
||||
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
|
||||
assert (len(tx_wallet['blockhash']) == 64)
|
||||
|
||||
def test_011_p2sh32(self):
|
||||
# Not used in bsx for native-segwit coins
|
||||
logging.info('---------- Test {} p2sh32'.format(self.test_coin.name))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
ci = self.swap_clients[0].ci(self.test_coin)
|
||||
|
||||
script = CScript([2, 2, OP_EQUAL, ])
|
||||
|
||||
script_dest = ci.scriptToP2SH32LockingBytecode(script)
|
||||
tx = CTransaction()
|
||||
tx.nVersion = ci.txVersion()
|
||||
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
|
||||
tx_hex = ToHex(tx)
|
||||
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
|
||||
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
|
||||
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
|
||||
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
|
||||
|
||||
addr_out = ci.rpc_wallet('getnewaddress', ['csv test'])
|
||||
pkh = ci.decodeAddress(addr_out)
|
||||
script_out = ci.getScriptForPubkeyHash(pkh)
|
||||
|
||||
# Double check output type
|
||||
prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ])
|
||||
assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash')
|
||||
|
||||
tx_spend = CTransaction()
|
||||
tx_spend.nVersion = ci.txVersion()
|
||||
tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos),
|
||||
scriptSig=CScript([script,])))
|
||||
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
|
||||
tx_spend_hex = ToHex(tx_spend)
|
||||
|
||||
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
|
||||
self.mineBlock(1)
|
||||
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
|
||||
sum_addr = 0
|
||||
for entry in ro:
|
||||
if entry['address'] == addr_out:
|
||||
sum_addr += entry['amount']
|
||||
assert (sum_addr == 1.0999)
|
||||
|
||||
# Ensure tx was mined
|
||||
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
|
||||
assert (len(tx_wallet['blockhash']) == 64)
|
||||
|
||||
def test_012_p2sh_p2wsh(self):
|
||||
logging.info('---------- Test {} p2sh-p2wsh'.format(self.test_coin.name))
|
||||
logging.info('Skipped')
|
||||
|
||||
def test_01_a_full_swap(self):
|
||||
super().test_01_a_full_swap()
|
||||
|
||||
def test_01_b_full_swap_reverse(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_01_b_full_swap_reverse()
|
||||
|
||||
def test_01_c_full_swap_to_part(self):
|
||||
super().test_01_c_full_swap_to_part()
|
||||
|
||||
def test_01_d_full_swap_from_part(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_01_d_full_swap_from_part()
|
||||
|
||||
def test_02_a_leader_recover_a_lock_tx(self):
|
||||
super().test_02_a_leader_recover_a_lock_tx()
|
||||
|
||||
def test_03_a_follower_recover_a_lock_tx(self):
|
||||
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.XMR, with_mercy=True)
|
||||
|
||||
def test_03_b_follower_recover_a_lock_tx_reverse(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
self.prepare_balance(Coins.XMR, 100.0, 1800, 1801)
|
||||
self.do_test_03_follower_recover_a_lock_tx(Coins.XMR, self.test_coin_from, lock_value=12, with_mercy=True)
|
||||
|
||||
def test_03_c_follower_recover_a_lock_tx_to_part(self):
|
||||
super().test_03_c_follower_recover_a_lock_tx_to_part()
|
||||
|
||||
def test_03_d_follower_recover_a_lock_tx_from_part(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_03_d_follower_recover_a_lock_tx_from_part()
|
||||
|
||||
def test_04_a_follower_recover_b_lock_tx(self):
|
||||
super().test_04_a_follower_recover_b_lock_tx()
|
||||
|
||||
def test_04_b_follower_recover_b_lock_tx_reverse(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_04_b_follower_recover_b_lock_tx_reverse()
|
||||
|
||||
def test_04_c_follower_recover_b_lock_tx_to_part(self):
|
||||
super().test_04_c_follower_recover_b_lock_tx_to_part()
|
||||
|
||||
def test_04_d_follower_recover_b_lock_tx_from_part(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_04_d_follower_recover_b_lock_tx_from_part()
|
||||
|
||||
def test_05_self_bid(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_05_self_bid()
|
||||
|
||||
def test_05_self_bid_to_part(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_05_self_bid_to_part()
|
||||
|
||||
def test_05_self_bid_from_part(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_05_self_bid_from_part()
|
||||
|
||||
def test_05_self_bid_rev(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_05_self_bid_rev()
|
||||
|
||||
def test_06_preselect_inputs(self):
|
||||
tla_from = self.test_coin.name
|
||||
logging.info('---------- Test {} Preselected inputs'.format(tla_from))
|
||||
logging.info('Skipped')
|
||||
|
||||
def test_07_expire_stuck_accepted(self):
|
||||
super().test_07_expire_stuck_accepted()
|
||||
|
||||
def test_08_insufficient_funds(self):
|
||||
super().test_08_insufficient_funds()
|
||||
|
||||
def test_08_insufficient_funds_rev(self):
|
||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||
super().test_08_insufficient_funds_rev()
|
||||
@@ -105,7 +105,7 @@ class TestFunctions(BaseTest):
|
||||
id_bidder: int = self.node_b_id
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_bidder].ci(coin_to)
|
||||
ci_part0 = swap_clients[id_offerer].ci(Coins.PART)
|
||||
@@ -229,7 +229,7 @@ class TestFunctions(BaseTest):
|
||||
id_bidder: int = self.node_b_id
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_offerer].ci(coin_to)
|
||||
|
||||
@@ -264,17 +264,17 @@ class TestFunctions(BaseTest):
|
||||
# TODO: Discard block rewards
|
||||
# assert (node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to, lock_value: int = 32):
|
||||
logging.info('---------- Test {} to {} follower recovers coin a lock tx'.format(coin_from.name, coin_to.name))
|
||||
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to, lock_value: int = 32, with_mercy: bool = False):
|
||||
logging.info('---------- Test {} to {} follower recovers coin a lock tx{}'.format(coin_from.name, coin_to.name, ' (with mercy tx)' if with_mercy else ''))
|
||||
|
||||
# Leader is too slow to recover the coin a lock tx and follower swipes it
|
||||
# coin b lock tx remains unspent
|
||||
# Coin B lock tx remains unspent unless a mercy output revealing the follower's keyshare is sent
|
||||
|
||||
id_offerer: int = self.node_a_id
|
||||
id_bidder: int = self.node_b_id
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_offerer].ci(coin_to)
|
||||
|
||||
@@ -282,6 +282,8 @@ class TestFunctions(BaseTest):
|
||||
id_follower: int = id_offerer if reverse_bid else id_bidder
|
||||
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
|
||||
|
||||
swap_clients[id_follower].ci(coin_from if reverse_bid else coin_to)._altruistic = with_mercy
|
||||
|
||||
js_w0_before = read_json_api(1800 + id_offerer, 'wallets')
|
||||
js_w1_before = read_json_api(1800 + id_bidder, 'wallets')
|
||||
|
||||
@@ -296,13 +298,20 @@ class TestFunctions(BaseTest):
|
||||
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
|
||||
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||
swap_clients[id_leader].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
|
||||
debug_type = DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2 if with_mercy else DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND
|
||||
swap_clients[id_leader].setBidDebugInd(bid_id, debug_type)
|
||||
debug_type = DebugTypes.BID_DONT_SPEND_COIN_B_LOCK if with_mercy else DebugTypes.BID_STOP_AFTER_COIN_A_LOCK
|
||||
swap_clients[id_follower].setBidDebugInd(bid_id, debug_type)
|
||||
|
||||
swap_clients[id_leader].setBidDebugInd(bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, False)
|
||||
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, False)
|
||||
|
||||
swap_clients[id_offerer].acceptBid(bid_id)
|
||||
|
||||
leader_sent_bid: bool = True if reverse_bid else False
|
||||
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=(self.extra_wait_time + 180), sent=leader_sent_bid)
|
||||
|
||||
expect_state = (BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED, BidStates.SWAP_COMPLETED) if with_mercy else BidStates.BID_STALLED_FOR_TEST
|
||||
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, expect_state, wait_for=(self.extra_wait_time + 180), sent=leader_sent_bid)
|
||||
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=(self.extra_wait_time + 80), sent=(not leader_sent_bid))
|
||||
|
||||
js_w1_after = read_json_api(1800 + id_bidder, 'wallets')
|
||||
@@ -325,7 +334,7 @@ class TestFunctions(BaseTest):
|
||||
id_bidder: int = self.node_b_id
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_offerer].ci(coin_to)
|
||||
|
||||
@@ -340,6 +349,7 @@ class TestFunctions(BaseTest):
|
||||
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
logging.info(f'amount from, rate, amount to: {amt_swap}, {rate_swap}, {amt_swap * rate_swap}')
|
||||
offer_id = swap_clients[id_offerer].postOffer(
|
||||
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=lock_value)
|
||||
@@ -913,6 +923,7 @@ class BasicSwapTest(TestFunctions):
|
||||
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.PART)
|
||||
|
||||
def test_02_leader_recover_a_lock_tx_from_part(self):
|
||||
self.prepare_balance(self.test_coin_from, 100.0, 1801, 1800)
|
||||
self.do_test_02_leader_recover_a_lock_tx(Coins.PART, self.test_coin_from)
|
||||
|
||||
def test_03_a_follower_recover_a_lock_tx(self):
|
||||
@@ -934,6 +945,18 @@ class BasicSwapTest(TestFunctions):
|
||||
def test_03_d_follower_recover_a_lock_tx_from_part(self):
|
||||
self.do_test_03_follower_recover_a_lock_tx(Coins.PART, self.test_coin_from)
|
||||
|
||||
def test_03_e_follower_recover_a_lock_tx_mercy_release(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.XMR, with_mercy=True)
|
||||
|
||||
def test_03_f_follower_recover_a_lock_tx_mercy_release_reverse(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.prepare_balance(Coins.XMR, 100.0, 1800, 1801)
|
||||
self.prepare_balance(self.test_coin_from, 100.0, 1801, 1800)
|
||||
self.do_test_03_follower_recover_a_lock_tx(Coins.XMR, self.test_coin_from, with_mercy=True)
|
||||
|
||||
def test_04_a_follower_recover_b_lock_tx(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
|
||||
@@ -1031,6 +1031,8 @@ class Test(BaseTest):
|
||||
logging.info('---------- Test PART to XMR follower recovers coin a lock tx')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
swap_clients[1].ci(Coins.PART)._altruistic = False
|
||||
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
Coins.PART, Coins.XMR, 101 * COIN, 0.13 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=16)
|
||||
@@ -1062,6 +1064,39 @@ class Test(BaseTest):
|
||||
bidder_states = [s for s in bidder_states if s[1] != 'Bid Stalled (debug)']
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder[2]) is True)
|
||||
|
||||
def test_03b_follower_recover_a_lock_tx_with_mercy(self):
|
||||
logging.info('---------- Test PART to XMR follower recovers coin a lock tx with mercy output')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
swap_clients[1].ci(Coins.PART)._altruistic = True
|
||||
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
Coins.PART, Coins.XMR, 101 * COIN, 0.13 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=16)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert (xmr_swap)
|
||||
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_B_LOCK)
|
||||
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2)
|
||||
|
||||
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, False)
|
||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, False)
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, (BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED, BidStates.SWAP_COMPLETED), wait_for=220)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=120, sent=True)
|
||||
|
||||
wait_for_none_active(test_delay_event, 1800)
|
||||
wait_for_none_active(test_delay_event, 1801)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx(self):
|
||||
logging.info('---------- Test PART to XMR follower recovers coin b lock tx')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user