mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-03 09:19:26 +00:00
coins: Add PIVX
No CSV or segwit. sethdseed requires a fully synced chain, manual intervention required to set a key derived from the master mnemonic. Requires a pivxd version with a backported scantxoutset command.
This commit is contained in:
parent
48e5dcbcc9
commit
e1528c9d63
23 changed files with 3969 additions and 38 deletions
|
@ -7,8 +7,8 @@ lint_task:
|
|||
- pip install codespell
|
||||
script:
|
||||
- flake8 --version
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/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,*mnemonics.py,bin/install_certifi.py
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --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
|
||||
|
||||
test_task:
|
||||
environment:
|
||||
|
|
|
@ -52,8 +52,8 @@ jobs:
|
|||
- travis_retry pip install codespell==1.15.0
|
||||
before_script:
|
||||
script:
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/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,*mnemonics.py,bin/install_certifi.py
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --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
|
||||
after_success:
|
||||
- echo "End lint"
|
||||
- stage: test
|
||||
|
|
|
@ -32,6 +32,7 @@ from .interface.btc import BTCInterface
|
|||
from .interface.ltc import LTCInterface
|
||||
from .interface.nmc import NMCInterface
|
||||
from .interface.xmr import XMRInterface
|
||||
from .interface.pivx import PIVXInterface
|
||||
from .interface.passthrough_btc import PassthroughBTCInterface
|
||||
|
||||
from . import __version__
|
||||
|
@ -174,6 +175,7 @@ def threadPollChainState(swap_client, coin_type):
|
|||
with swap_client.mxDB:
|
||||
cc['chain_height'] = chain_state['blocks']
|
||||
cc['chain_best_block'] = chain_state['bestblockhash']
|
||||
if 'mediantime' in chain_state:
|
||||
cc['chain_median_time'] = chain_state['mediantime']
|
||||
except Exception as e:
|
||||
swap_client.log.warning('threadPollChainState {}, error: {}'.format(str(coin_type), str(e)))
|
||||
|
@ -380,21 +382,24 @@ class BasicSwap(BaseApp):
|
|||
session.close()
|
||||
session.remove()
|
||||
|
||||
coin_chainparams = chainparams[coin]
|
||||
default_segwit = coin_chainparams.get('has_segwit', False)
|
||||
default_csv = coin_chainparams.get('has_csv', True)
|
||||
self.coin_clients[coin] = {
|
||||
'coin': coin,
|
||||
'name': chainparams[coin]['name'],
|
||||
'name': coin_chainparams['name'],
|
||||
'connection_type': connection_type,
|
||||
'bindir': bindir,
|
||||
'datadir': datadir,
|
||||
'rpchost': chain_client_settings.get('rpchost', '127.0.0.1'),
|
||||
'rpcport': chain_client_settings.get('rpcport', chainparams[coin][self.chain]['rpcport']),
|
||||
'rpcport': chain_client_settings.get('rpcport', coin_chainparams[self.chain]['rpcport']),
|
||||
'rpcauth': rpcauth,
|
||||
'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6),
|
||||
'conf_target': chain_client_settings.get('conf_target', 2),
|
||||
'watched_outputs': [],
|
||||
'last_height_checked': last_height_checked,
|
||||
'use_segwit': chain_client_settings.get('use_segwit', False),
|
||||
'use_csv': chain_client_settings.get('use_csv', True),
|
||||
'use_segwit': chain_client_settings.get('use_segwit', default_segwit),
|
||||
'use_csv': chain_client_settings.get('use_csv', default_csv),
|
||||
'core_version_group': chain_client_settings.get('core_version_group', 0),
|
||||
'pid': None,
|
||||
'core_version': None,
|
||||
|
@ -482,6 +487,8 @@ class BasicSwap(BaseApp):
|
|||
chain_client_settings = self.getChainClientSettings(coin)
|
||||
xmr_i.setWalletFilename(chain_client_settings['walletfile'])
|
||||
return xmr_i
|
||||
elif coin == Coins.PIVX:
|
||||
return PIVXInterface(self.coin_clients[coin], self.chain, self)
|
||||
else:
|
||||
raise ValueError('Unknown coin type')
|
||||
|
||||
|
@ -926,6 +933,8 @@ class BasicSwap(BaseApp):
|
|||
raise ValueError('Invalid swap type for PART_ANON')
|
||||
if (coin_from == Coins.PART_BLIND or coin_to == Coins.PART_BLIND) and swap_type != SwapTypes.XMR_SWAP:
|
||||
raise ValueError('Invalid swap type for PART_BLIND')
|
||||
if coin_from == Coins.PIVX and swap_type == SwapTypes.XMR_SWAP:
|
||||
raise ValueError('TODO: PIVX -> XMR')
|
||||
|
||||
def notify(self, event_type, event_data):
|
||||
if event_type == NT.OFFER_RECEIVED:
|
||||
|
@ -958,19 +967,23 @@ class BasicSwap(BaseApp):
|
|||
ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain')
|
||||
|
||||
def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
|
||||
|
||||
coin_from_has_csv = self.coin_clients[coin_from]['use_csv']
|
||||
coin_to_has_csv = self.coin_clients[coin_to]['use_csv']
|
||||
|
||||
if lock_type == OfferMessage.SEQUENCE_LOCK_TIME:
|
||||
ensure(lock_value >= self.min_sequence_lock_seconds and lock_value <= self.max_sequence_lock_seconds, 'Invalid lock_value time')
|
||||
ensure(self.coin_clients[coin_from]['use_csv'] and self.coin_clients[coin_to]['use_csv'], 'Both coins need CSV activated.')
|
||||
ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.')
|
||||
elif lock_type == OfferMessage.SEQUENCE_LOCK_BLOCKS:
|
||||
ensure(lock_value >= 5 and lock_value <= 1000, 'Invalid lock_value blocks')
|
||||
ensure(self.coin_clients[coin_from]['use_csv'] and self.coin_clients[coin_to]['use_csv'], 'Both coins need CSV activated.')
|
||||
ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.')
|
||||
elif lock_type == TxLockTypes.ABS_LOCK_TIME:
|
||||
# TODO: range?
|
||||
ensure(not self.coin_clients[coin_from]['use_csv'] or not self.coin_clients[coin_to]['use_csv'], 'Should use CSV.')
|
||||
ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.')
|
||||
ensure(lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60, 'Invalid lock_value time')
|
||||
elif lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
|
||||
# TODO: range?
|
||||
ensure(not self.coin_clients[coin_from]['use_csv'] or not self.coin_clients[coin_to]['use_csv'], 'Should use CSV.')
|
||||
ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.')
|
||||
ensure(lock_value >= 10 and lock_value <= 1000, 'Invalid lock_value blocks')
|
||||
else:
|
||||
raise ValueError('Unknown locktype')
|
||||
|
@ -2569,10 +2582,14 @@ class BasicSwap(BaseApp):
|
|||
|
||||
if self.debug:
|
||||
# Check fee
|
||||
if self.coin_clients[coin_type]['connection_type'] == 'rpc':
|
||||
if ci.get_connection_type() == 'rpc':
|
||||
redeem_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [redeem_txn])
|
||||
if ci.using_segwit():
|
||||
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize'])
|
||||
ensure(tx_vsize >= redeem_txjs['vsize'], 'Underpaid fee')
|
||||
ensure(tx_vsize >= redeem_txjs['vsize'], 'underpaid fee')
|
||||
else:
|
||||
self.log.debug('size paid, actual size %d %d', tx_vsize, redeem_txjs['size'])
|
||||
ensure(tx_vsize >= redeem_txjs['size'], 'underpaid fee')
|
||||
|
||||
redeem_txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [redeem_txn])
|
||||
self.log.debug('Have valid redeem txn %s for contract %s tx %s', redeem_txjs['txid'], for_txn_type, prev_txnid)
|
||||
|
@ -2669,10 +2686,14 @@ class BasicSwap(BaseApp):
|
|||
|
||||
if self.debug:
|
||||
# Check fee
|
||||
if self.coin_clients[coin_type]['connection_type'] == 'rpc':
|
||||
if ci.get_connection_type() == 'rpc':
|
||||
refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn])
|
||||
if ci.using_segwit():
|
||||
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, refund_txjs['vsize'])
|
||||
ensure(tx_vsize >= refund_txjs['vsize'], 'underpaid fee')
|
||||
else:
|
||||
self.log.debug('size paid, actual size %d %d', tx_vsize, refund_txjs['size'])
|
||||
ensure(tx_vsize >= refund_txjs['size'], 'underpaid fee')
|
||||
|
||||
refund_txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [refund_txn])
|
||||
self.log.debug('Have valid refund txn %s for contract tx %s', refund_txjs['txid'], txjs['txid'])
|
||||
|
@ -3496,13 +3517,14 @@ class BasicSwap(BaseApp):
|
|||
spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
|
||||
self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
|
||||
else:
|
||||
chain_blocks = self.callcoinrpc(coin_type, 'getblockcount')
|
||||
ci = self.ci(coin_type)
|
||||
chain_blocks = ci.getChainHeight()
|
||||
last_height_checked = c['last_height_checked']
|
||||
self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
|
||||
while last_height_checked < chain_blocks:
|
||||
block_hash = self.callcoinrpc(coin_type, 'getblockhash', [last_height_checked + 1])
|
||||
try:
|
||||
block = self.callcoinrpc(coin_type, 'getblock', [block_hash, 2])
|
||||
block = ci.getBlockWithTxns(block_hash)
|
||||
except Exception as e:
|
||||
if 'Block not available (pruned data)' in str(e):
|
||||
# TODO: Better solution?
|
||||
|
@ -3510,6 +3532,9 @@ class BasicSwap(BaseApp):
|
|||
self.log.error('Coin %s last_height_checked %d set to pruneheight %d', self.ci(coin_type).coin_name(), last_height_checked, bci['pruneheight'])
|
||||
last_height_checked = bci['pruneheight']
|
||||
continue
|
||||
else:
|
||||
self.log.error(f'getblock error {e}')
|
||||
break
|
||||
|
||||
for tx in block['tx']:
|
||||
for i, inp in enumerate(tx['vin']):
|
||||
|
|
|
@ -26,6 +26,9 @@ class Coins(IntEnum):
|
|||
XMR = 6
|
||||
PART_BLIND = 7
|
||||
PART_ANON = 8
|
||||
# ZANO = 9
|
||||
# NDAU = 10
|
||||
PIVX = 11
|
||||
|
||||
|
||||
chainparams = {
|
||||
|
@ -206,10 +209,45 @@ chainparams = {
|
|||
'min_amount': 100000,
|
||||
'max_amount': 10000 * XMR_COIN,
|
||||
}
|
||||
},
|
||||
Coins.PIVX: {
|
||||
'name': 'pivx',
|
||||
'ticker': 'PIVX',
|
||||
'message_magic': 'DarkNet Signed Message:\n',
|
||||
'blocks_target': 60 * 1,
|
||||
'decimal_places': 8,
|
||||
'has_csv': False,
|
||||
'has_segwit': False,
|
||||
'mainnet': {
|
||||
'rpcport': 51473,
|
||||
'pubkey_address': 30,
|
||||
'script_address': 13,
|
||||
'key_prefix': 212,
|
||||
'bip44': 119,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'testnet': {
|
||||
'rpcport': 51475,
|
||||
'pubkey_address': 139,
|
||||
'script_address': 19,
|
||||
'key_prefix': 239,
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
'name': 'testnet4',
|
||||
},
|
||||
'regtest': {
|
||||
'rpcport': 51477,
|
||||
'pubkey_address': 139,
|
||||
'script_address': 19,
|
||||
'key_prefix': 239,
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
ticker_map = {}
|
||||
|
||||
|
||||
|
|
|
@ -36,3 +36,8 @@ NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
|
|||
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
|
||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
||||
|
||||
PIVX_BINDIR = os.path.expanduser(os.getenv('PIVX_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'namecoin')))
|
||||
PIVXD = os.getenv('PIVXD', 'pivxd' + bin_suffix)
|
||||
PIVX_CLI = os.getenv('PIVX_CLI', 'pivx-cli' + bin_suffix)
|
||||
PIVX_TX = os.getenv('PIVX_TX', 'pivx-tx' + bin_suffix)
|
||||
|
|
|
@ -185,9 +185,16 @@ class BTCInterface(CoinInterface):
|
|||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
self.setConfTarget(coin_settings['conf_target'])
|
||||
self._use_segwit = coin_settings['use_segwit']
|
||||
self._connection_type = coin_settings['connection_type']
|
||||
self._sc = swap_client
|
||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
|
||||
def using_segwit(self):
|
||||
return self._use_segwit
|
||||
|
||||
def get_connection_type(self):
|
||||
return self._connection_type
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
|
||||
|
@ -285,12 +292,14 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
def get_fee_rate(self, conf_target=2):
|
||||
try:
|
||||
return self.rpc_callback('estimatesmartfee', [conf_target])['feerate'], 'estimatesmartfee'
|
||||
fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate']
|
||||
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||
return fee_rate, 'estimatesmartfee'
|
||||
except Exception:
|
||||
try:
|
||||
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'], 'paytxfee'
|
||||
assert (fee_rate > 0.0), '0 feerate'
|
||||
return fee_rate
|
||||
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
|
||||
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||
return fee_rate, 'paytxfee'
|
||||
except Exception:
|
||||
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
||||
|
||||
|
@ -1161,6 +1170,9 @@ class BTCInterface(CoinInterface):
|
|||
txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
|
||||
return txn_signed
|
||||
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
return self.rpc_callback('getblock', [block_hash, 2])
|
||||
|
||||
|
||||
def testBTCInterface():
|
||||
print('testBTCInterface')
|
||||
|
|
0
basicswap/interface/contrib/__init__.py
Normal file
0
basicswap/interface/contrib/__init__.py
Normal file
180
basicswap/interface/contrib/pivx_test_framework/authproxy.py
Normal file
180
basicswap/interface/contrib/pivx_test_framework/authproxy.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
# Copyright (c) 2011 Jeff Garzik
|
||||
#
|
||||
# Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
|
||||
#
|
||||
# Copyright (c) 2007 Jan-Klaas Kollhof
|
||||
#
|
||||
# This file is part of jsonrpc.
|
||||
#
|
||||
# jsonrpc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""HTTP proxy for opening RPC connection to pivxd.
|
||||
|
||||
AuthServiceProxy has the following improvements over python-jsonrpc's
|
||||
ServiceProxy class:
|
||||
|
||||
- HTTP connections persist for the life of the AuthServiceProxy object
|
||||
(if server supports HTTP/1.1)
|
||||
- sends protocol 'version', per JSON-RPC 1.1
|
||||
- sends proper, incrementing 'id'
|
||||
- sends Basic HTTP authentication headers
|
||||
- parses all JSON numbers that look like floats as Decimal
|
||||
- uses standard Python json lib
|
||||
"""
|
||||
|
||||
import base64
|
||||
import decimal
|
||||
import http.client
|
||||
import json
|
||||
import logging
|
||||
import socket
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
HTTP_TIMEOUT = 300
|
||||
USER_AGENT = "AuthServiceProxy/0.1"
|
||||
|
||||
log = logging.getLogger("BitcoinRPC")
|
||||
|
||||
class JSONRPCException(Exception):
|
||||
def __init__(self, rpc_error):
|
||||
try:
|
||||
errmsg = '%(message)s (%(code)i)' % rpc_error
|
||||
except (KeyError, TypeError):
|
||||
errmsg = ''
|
||||
super().__init__(errmsg)
|
||||
self.error = rpc_error
|
||||
|
||||
|
||||
def EncodeDecimal(o):
|
||||
if isinstance(o, decimal.Decimal):
|
||||
return str(o)
|
||||
raise TypeError(repr(o) + " is not JSON serializable")
|
||||
|
||||
class AuthServiceProxy():
|
||||
__id_count = 0
|
||||
|
||||
# ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
|
||||
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
|
||||
self.__service_url = service_url
|
||||
self._service_name = service_name
|
||||
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
|
||||
self.__url = urllib.parse.urlparse(service_url)
|
||||
port = 80 if self.__url.port is None else self.__url.port
|
||||
user = None if self.__url.username is None else self.__url.username.encode('utf8')
|
||||
passwd = None if self.__url.password is None else self.__url.password.encode('utf8')
|
||||
authpair = user + b':' + passwd
|
||||
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
|
||||
|
||||
if connection:
|
||||
# Callables re-use the connection of the original proxy
|
||||
self.__conn = connection
|
||||
elif self.__url.scheme == 'https':
|
||||
self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=timeout)
|
||||
else:
|
||||
self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=timeout)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name.startswith('__') and name.endswith('__'):
|
||||
# Python internal stuff
|
||||
raise AttributeError
|
||||
if self._service_name is not None:
|
||||
name = "%s.%s" % (self._service_name, name)
|
||||
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
|
||||
|
||||
def _request(self, method, path, postdata):
|
||||
'''
|
||||
Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
|
||||
This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
|
||||
'''
|
||||
headers = {'Host': self.__url.hostname,
|
||||
'User-Agent': USER_AGENT,
|
||||
'Authorization': self.__auth_header,
|
||||
'Content-type': 'application/json'}
|
||||
try:
|
||||
self.__conn.request(method, path, postdata, headers)
|
||||
return self._get_response()
|
||||
except http.client.BadStatusLine as e:
|
||||
if e.line == "''": # if connection was closed, try again
|
||||
self.__conn.close()
|
||||
self.__conn.request(method, path, postdata, headers)
|
||||
return self._get_response()
|
||||
else:
|
||||
raise
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
# Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset
|
||||
# ConnectionResetError happens on FreeBSD with Python 3.4
|
||||
self.__conn.close()
|
||||
self.__conn.request(method, path, postdata, headers)
|
||||
return self._get_response()
|
||||
|
||||
def get_request(self, *args, **argsn):
|
||||
AuthServiceProxy.__id_count += 1
|
||||
|
||||
log.debug("-%s-> %s %s" % (AuthServiceProxy.__id_count, self._service_name,
|
||||
json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||
if args and argsn:
|
||||
raise ValueError('Cannot handle both named and positional arguments')
|
||||
return {'version': '1.1',
|
||||
'method': self._service_name,
|
||||
'params': args or argsn,
|
||||
'id': AuthServiceProxy.__id_count}
|
||||
|
||||
def __call__(self, *args, **argsn):
|
||||
postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||
response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||
if response['error'] is not None:
|
||||
raise JSONRPCException(response['error'])
|
||||
elif 'result' not in response:
|
||||
raise JSONRPCException({
|
||||
'code': -343, 'message': 'missing JSON-RPC result'})
|
||||
else:
|
||||
return response['result']
|
||||
|
||||
def batch(self, rpc_call_list):
|
||||
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||
log.debug("--> " + postdata)
|
||||
return self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||
|
||||
def _get_response(self):
|
||||
req_start_time = time.time()
|
||||
try:
|
||||
http_response = self.__conn.getresponse()
|
||||
except socket.timeout:
|
||||
raise JSONRPCException({
|
||||
'code': -344,
|
||||
'message': '%r RPC took longer than %f seconds. Consider '
|
||||
'using larger timeout for calls that take '
|
||||
'longer to return.' % (self._service_name,
|
||||
self.__conn.timeout)})
|
||||
if http_response is None:
|
||||
raise JSONRPCException({
|
||||
'code': -342, 'message': 'missing HTTP response from server'})
|
||||
|
||||
content_type = http_response.getheader('Content-Type')
|
||||
if content_type != 'application/json':
|
||||
raise JSONRPCException({
|
||||
'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
|
||||
|
||||
responsedata = http_response.read().decode('utf8')
|
||||
response = json.loads(responsedata, parse_float=decimal.Decimal)
|
||||
elapsed = time.time() - req_start_time
|
||||
if "error" in response and response["error"] is None:
|
||||
log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||
else:
|
||||
log.debug("<-- [%.6f] %s" % (elapsed, responsedata))
|
||||
return response
|
||||
|
||||
def __truediv__(self, relative_uri):
|
||||
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
|
109
basicswap/interface/contrib/pivx_test_framework/coverage.py
Normal file
109
basicswap/interface/contrib/pivx_test_framework/coverage.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2015-2017 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Utilities for doing coverage analysis on the RPC interface.
|
||||
|
||||
Provides a way to track which RPC commands are exercised during
|
||||
testing.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
REFERENCE_FILENAME = 'rpc_interface.txt'
|
||||
|
||||
|
||||
class AuthServiceProxyWrapper():
|
||||
"""
|
||||
An object that wraps AuthServiceProxy to record specific RPC calls.
|
||||
|
||||
"""
|
||||
def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
|
||||
"""
|
||||
Kwargs:
|
||||
auth_service_proxy_instance (AuthServiceProxy): the instance
|
||||
being wrapped.
|
||||
coverage_logfile (str): if specified, write each service_name
|
||||
out to a file when called.
|
||||
|
||||
"""
|
||||
self.auth_service_proxy_instance = auth_service_proxy_instance
|
||||
self.coverage_logfile = coverage_logfile
|
||||
|
||||
def __getattr__(self, name):
|
||||
return_val = getattr(self.auth_service_proxy_instance, name)
|
||||
if not isinstance(return_val, type(self.auth_service_proxy_instance)):
|
||||
# If proxy getattr returned an unwrapped value, do the same here.
|
||||
return return_val
|
||||
return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""
|
||||
Delegates to AuthServiceProxy, then writes the particular RPC method
|
||||
called to a file.
|
||||
|
||||
"""
|
||||
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
|
||||
self._log_call()
|
||||
return return_val
|
||||
|
||||
def _log_call(self):
|
||||
rpc_method = self.auth_service_proxy_instance._service_name
|
||||
|
||||
if self.coverage_logfile:
|
||||
with open(self.coverage_logfile, 'a+', encoding='utf8') as f:
|
||||
f.write("%s\n" % rpc_method)
|
||||
|
||||
def __truediv__(self, relative_uri):
|
||||
return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri,
|
||||
self.coverage_logfile)
|
||||
|
||||
def get_request(self, *args, **kwargs):
|
||||
self._log_call()
|
||||
return self.auth_service_proxy_instance.get_request(*args)
|
||||
|
||||
def get_filename(dirname, n_node):
|
||||
"""
|
||||
Get a filename unique to the test process ID and node.
|
||||
|
||||
This file will contain a list of RPC commands covered.
|
||||
"""
|
||||
pid = str(os.getpid())
|
||||
return os.path.join(
|
||||
dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
|
||||
|
||||
|
||||
def write_all_rpc_commands(dirname, node):
|
||||
"""
|
||||
Write out a list of all RPC functions available in `pivx-cli` for
|
||||
coverage comparison. This will only happen once per coverage
|
||||
directory.
|
||||
|
||||
Args:
|
||||
dirname (str): temporary test dir
|
||||
node (AuthServiceProxy): client
|
||||
|
||||
Returns:
|
||||
bool. if the RPC interface file was written.
|
||||
|
||||
"""
|
||||
filename = os.path.join(dirname, REFERENCE_FILENAME)
|
||||
|
||||
if os.path.isfile(filename):
|
||||
return False
|
||||
|
||||
help_output = node.help().split('\n')
|
||||
commands = set()
|
||||
|
||||
for line in help_output:
|
||||
line = line.strip()
|
||||
|
||||
# Ignore blanks and headers
|
||||
if line and not line.startswith('='):
|
||||
commands.add("%s\n" % line.split()[0])
|
||||
|
||||
with open(filename, 'w', encoding='utf8') as f:
|
||||
f.writelines(list(commands))
|
||||
|
||||
return True
|
1505
basicswap/interface/contrib/pivx_test_framework/messages.py
Executable file
1505
basicswap/interface/contrib/pivx_test_framework/messages.py
Executable file
File diff suppressed because it is too large
Load diff
63
basicswap/interface/contrib/pivx_test_framework/siphash.py
Normal file
63
basicswap/interface/contrib/pivx_test_framework/siphash.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2017 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Specialized SipHash-2-4 implementations.
|
||||
|
||||
This implements SipHash-2-4 for 256-bit integers.
|
||||
"""
|
||||
|
||||
def rotl64(n, b):
|
||||
return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b
|
||||
|
||||
def siphash_round(v0, v1, v2, v3):
|
||||
v0 = (v0 + v1) & ((1 << 64) - 1)
|
||||
v1 = rotl64(v1, 13)
|
||||
v1 ^= v0
|
||||
v0 = rotl64(v0, 32)
|
||||
v2 = (v2 + v3) & ((1 << 64) - 1)
|
||||
v3 = rotl64(v3, 16)
|
||||
v3 ^= v2
|
||||
v0 = (v0 + v3) & ((1 << 64) - 1)
|
||||
v3 = rotl64(v3, 21)
|
||||
v3 ^= v0
|
||||
v2 = (v2 + v1) & ((1 << 64) - 1)
|
||||
v1 = rotl64(v1, 17)
|
||||
v1 ^= v2
|
||||
v2 = rotl64(v2, 32)
|
||||
return (v0, v1, v2, v3)
|
||||
|
||||
def siphash256(k0, k1, h):
|
||||
n0 = h & ((1 << 64) - 1)
|
||||
n1 = (h >> 64) & ((1 << 64) - 1)
|
||||
n2 = (h >> 128) & ((1 << 64) - 1)
|
||||
n3 = (h >> 192) & ((1 << 64) - 1)
|
||||
v0 = 0x736f6d6570736575 ^ k0
|
||||
v1 = 0x646f72616e646f6d ^ k1
|
||||
v2 = 0x6c7967656e657261 ^ k0
|
||||
v3 = 0x7465646279746573 ^ k1 ^ n0
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0 ^= n0
|
||||
v3 ^= n1
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0 ^= n1
|
||||
v3 ^= n2
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0 ^= n2
|
||||
v3 ^= n3
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0 ^= n3
|
||||
v3 ^= 0x2000000000000000
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0 ^= 0x2000000000000000
|
||||
v2 ^= 0xFF
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||
return v0 ^ v1 ^ v2 ^ v3
|
625
basicswap/interface/contrib/pivx_test_framework/util.py
Normal file
625
basicswap/interface/contrib/pivx_test_framework/util.py
Normal file
|
@ -0,0 +1,625 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014-2017 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Helpful routines for regression testing."""
|
||||
|
||||
from base64 import b64encode
|
||||
from binascii import hexlify, unhexlify
|
||||
from decimal import Decimal, ROUND_DOWN
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from subprocess import CalledProcessError
|
||||
import time
|
||||
|
||||
from . import coverage, messages
|
||||
from .authproxy import AuthServiceProxy, JSONRPCException
|
||||
|
||||
logger = logging.getLogger("TestFramework.utils")
|
||||
|
||||
# Assert functions
|
||||
##################
|
||||
|
||||
def assert_fee_amount(fee, tx_size, fee_per_kB):
|
||||
"""Assert the fee was in range"""
|
||||
target_fee = round(tx_size * fee_per_kB / 1000, 8)
|
||||
if fee < target_fee:
|
||||
raise AssertionError("Fee of %s PIV too low! (Should be %s PIV)" % (str(fee), str(target_fee)))
|
||||
# allow the wallet's estimation to be at most 2 bytes off
|
||||
if fee > (tx_size + 20) * fee_per_kB / 1000:
|
||||
raise AssertionError("Fee of %s PIV too high! (Should be %s PIV)" % (str(fee), str(target_fee)))
|
||||
|
||||
def assert_equal(thing1, thing2, *args):
|
||||
if thing1 != thing2 or any(thing1 != arg for arg in args):
|
||||
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
|
||||
|
||||
def assert_true(condition, message = ""):
|
||||
if not condition:
|
||||
raise AssertionError(message)
|
||||
|
||||
def assert_false(condition, message = ""):
|
||||
assert_true(not condition, message)
|
||||
|
||||
def assert_greater_than(thing1, thing2):
|
||||
if thing1 <= thing2:
|
||||
raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))
|
||||
|
||||
def assert_greater_than_or_equal(thing1, thing2):
|
||||
if thing1 < thing2:
|
||||
raise AssertionError("%s < %s" % (str(thing1), str(thing2)))
|
||||
|
||||
def assert_raises(exc, fun, *args, **kwds):
|
||||
assert_raises_message(exc, None, fun, *args, **kwds)
|
||||
|
||||
def assert_raises_message(exc, message, fun, *args, **kwds):
|
||||
try:
|
||||
fun(*args, **kwds)
|
||||
except JSONRPCException:
|
||||
raise AssertionError("Use assert_raises_rpc_error() to test RPC failures")
|
||||
except exc as e:
|
||||
if message is not None and message not in e.error['message']:
|
||||
raise AssertionError("Expected substring not found:" + e.error['message'])
|
||||
except Exception as e:
|
||||
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
|
||||
else:
|
||||
raise AssertionError("No exception raised")
|
||||
|
||||
def assert_raises_process_error(returncode, output, fun, *args, **kwds):
|
||||
"""Execute a process and asserts the process return code and output.
|
||||
|
||||
Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError
|
||||
and verifies that the return code and output are as expected. Throws AssertionError if
|
||||
no CalledProcessError was raised or if the return code and output are not as expected.
|
||||
|
||||
Args:
|
||||
returncode (int): the process return code.
|
||||
output (string): [a substring of] the process output.
|
||||
fun (function): the function to call. This should execute a process.
|
||||
args*: positional arguments for the function.
|
||||
kwds**: named arguments for the function.
|
||||
"""
|
||||
try:
|
||||
fun(*args, **kwds)
|
||||
except CalledProcessError as e:
|
||||
if returncode != e.returncode:
|
||||
raise AssertionError("Unexpected returncode %i" % e.returncode)
|
||||
if output not in e.output:
|
||||
raise AssertionError("Expected substring not found:" + e.output)
|
||||
else:
|
||||
raise AssertionError("No exception raised")
|
||||
|
||||
def assert_raises_rpc_error(code, message, fun, *args, **kwds):
|
||||
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
|
||||
|
||||
Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
|
||||
and verifies that the error code and message are as expected. Throws AssertionError if
|
||||
no JSONRPCException was raised or if the error code/message are not as expected.
|
||||
|
||||
Args:
|
||||
code (int), optional: the error code returned by the RPC call (defined
|
||||
in src/rpc/protocol.h). Set to None if checking the error code is not required.
|
||||
message (string), optional: [a substring of] the error string returned by the
|
||||
RPC call. Set to None if checking the error string is not required.
|
||||
fun (function): the function to call. This should be the name of an RPC.
|
||||
args*: positional arguments for the function.
|
||||
kwds**: named arguments for the function.
|
||||
"""
|
||||
assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
|
||||
|
||||
def try_rpc(code, message, fun, *args, **kwds):
|
||||
"""Tries to run an rpc command.
|
||||
|
||||
Test against error code and message if the rpc fails.
|
||||
Returns whether a JSONRPCException was raised."""
|
||||
try:
|
||||
fun(*args, **kwds)
|
||||
except JSONRPCException as e:
|
||||
# JSONRPCException was thrown as expected. Check the code and message values are correct.
|
||||
if (code is not None) and (code != e.error["code"]):
|
||||
raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
|
||||
if (message is not None) and (message not in e.error['message']):
|
||||
raise AssertionError("Expected substring (%s) not found in: %s" % (message, e.error['message']))
|
||||
return True
|
||||
except Exception as e:
|
||||
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
|
||||
else:
|
||||
return False
|
||||
|
||||
def assert_is_hex_string(string):
|
||||
try:
|
||||
int(string, 16)
|
||||
except Exception as e:
|
||||
raise AssertionError(
|
||||
"Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
|
||||
|
||||
def assert_is_hash_string(string, length=64):
|
||||
if not isinstance(string, str):
|
||||
raise AssertionError("Expected a string, got type %r" % type(string))
|
||||
elif length and len(string) != length:
|
||||
raise AssertionError(
|
||||
"String of length %d expected; got %d" % (length, len(string)))
|
||||
elif not re.match('[abcdef0-9]+$', string):
|
||||
raise AssertionError(
|
||||
"String %r contains invalid characters for a hash." % string)
|
||||
|
||||
def assert_array_result(object_array, to_match, expected, should_not_find=False):
|
||||
"""
|
||||
Pass in array of JSON objects, a dictionary with key/value pairs
|
||||
to match against, and another dictionary with expected key/value
|
||||
pairs.
|
||||
If the should_not_find flag is true, to_match should not be found
|
||||
in object_array
|
||||
"""
|
||||
if should_not_find:
|
||||
assert_equal(expected, {})
|
||||
num_matched = 0
|
||||
for item in object_array:
|
||||
all_match = True
|
||||
for key, value in to_match.items():
|
||||
if item[key] != value:
|
||||
all_match = False
|
||||
if not all_match:
|
||||
continue
|
||||
elif should_not_find:
|
||||
num_matched = num_matched + 1
|
||||
for key, value in expected.items():
|
||||
if item[key] != value:
|
||||
raise AssertionError("%s : expected %s=%s" % (str(item), str(key), str(value)))
|
||||
num_matched = num_matched + 1
|
||||
if num_matched == 0 and not should_not_find:
|
||||
raise AssertionError("No objects matched %s" % (str(to_match)))
|
||||
if num_matched > 0 and should_not_find:
|
||||
raise AssertionError("Objects were found %s" % (str(to_match)))
|
||||
|
||||
# Utility functions
|
||||
###################
|
||||
|
||||
def check_json_precision():
|
||||
"""Make sure json library being used does not lose precision converting BTC values"""
|
||||
n = Decimal("20000000.00000003")
|
||||
satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8)
|
||||
if satoshis != 2000000000000003:
|
||||
raise RuntimeError("JSON encode/decode loses precision")
|
||||
|
||||
def count_bytes(hex_string):
|
||||
return len(bytearray.fromhex(hex_string))
|
||||
|
||||
def bytes_to_hex_str(byte_str):
|
||||
return hexlify(byte_str).decode('ascii')
|
||||
|
||||
def hash256(byte_str):
|
||||
sha256 = hashlib.sha256()
|
||||
sha256.update(byte_str)
|
||||
sha256d = hashlib.sha256()
|
||||
sha256d.update(sha256.digest())
|
||||
return sha256d.digest()[::-1]
|
||||
|
||||
def hex_str_to_bytes(hex_str):
|
||||
return unhexlify(hex_str.encode('ascii'))
|
||||
|
||||
def str_to_b64str(string):
|
||||
return b64encode(string.encode('utf-8')).decode('ascii')
|
||||
|
||||
def satoshi_round(amount):
|
||||
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||
|
||||
def wait_until(predicate,
|
||||
*,
|
||||
attempts=float('inf'),
|
||||
timeout=float('inf'),
|
||||
lock=None,
|
||||
sendpings=None,
|
||||
mocktime=None):
|
||||
|
||||
if attempts == float('inf') and timeout == float('inf'):
|
||||
timeout = 60
|
||||
attempt = 0
|
||||
timeout += time.time()
|
||||
|
||||
while attempt < attempts and time.time() < timeout:
|
||||
if lock:
|
||||
with lock:
|
||||
if predicate():
|
||||
return
|
||||
else:
|
||||
if predicate():
|
||||
return
|
||||
attempt += 1
|
||||
time.sleep(0.5)
|
||||
if sendpings is not None:
|
||||
sendpings()
|
||||
if mocktime is not None:
|
||||
mocktime(1)
|
||||
|
||||
# Print the cause of the timeout
|
||||
assert_greater_than(attempts, attempt)
|
||||
assert_greater_than(timeout, time.time())
|
||||
raise RuntimeError('Unreachable')
|
||||
|
||||
# RPC/P2P connection constants and functions
|
||||
############################################
|
||||
|
||||
# The maximum number of nodes a single test can spawn
|
||||
MAX_NODES = 8
|
||||
# Don't assign rpc or p2p ports lower than this
|
||||
PORT_MIN = 11000
|
||||
# The number of ports to "reserve" for p2p and rpc, each
|
||||
PORT_RANGE = 5000
|
||||
|
||||
class PortSeed:
|
||||
# Must be initialized with a unique integer for each process
|
||||
n = None
|
||||
|
||||
def get_rpc_proxy(url, node_number, timeout=None, coveragedir=None):
|
||||
"""
|
||||
Args:
|
||||
url (str): URL of the RPC server to call
|
||||
node_number (int): the node number (or id) that this calls to
|
||||
|
||||
Kwargs:
|
||||
timeout (int): HTTP timeout in seconds
|
||||
|
||||
Returns:
|
||||
AuthServiceProxy. convenience object for making RPC calls.
|
||||
|
||||
"""
|
||||
proxy_kwargs = {}
|
||||
if timeout is not None:
|
||||
proxy_kwargs['timeout'] = timeout
|
||||
|
||||
proxy = AuthServiceProxy(url, **proxy_kwargs)
|
||||
proxy.url = url # store URL on proxy for info
|
||||
|
||||
coverage_logfile = coverage.get_filename(
|
||||
coveragedir, node_number) if coveragedir else None
|
||||
|
||||
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
|
||||
|
||||
def p2p_port(n):
|
||||
assert(n <= MAX_NODES)
|
||||
return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||
|
||||
def rpc_port(n):
|
||||
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||
|
||||
def rpc_url(datadir, i, rpchost=None):
|
||||
rpc_u, rpc_p = get_auth_cookie(datadir)
|
||||
host = '127.0.0.1'
|
||||
port = rpc_port(i)
|
||||
if rpchost:
|
||||
parts = rpchost.split(':')
|
||||
if len(parts) == 2:
|
||||
host, port = parts
|
||||
else:
|
||||
host = rpchost
|
||||
return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port))
|
||||
|
||||
# Node functions
|
||||
################
|
||||
|
||||
def initialize_datadir(dirname, n):
|
||||
datadir = get_datadir_path(dirname, n)
|
||||
if not os.path.isdir(datadir):
|
||||
os.makedirs(datadir)
|
||||
with open(os.path.join(datadir, "pivx.conf"), 'w', encoding='utf8') as f:
|
||||
f.write("regtest=1\n")
|
||||
f.write("[regtest]\n")
|
||||
f.write("port=" + str(p2p_port(n)) + "\n")
|
||||
f.write("rpcport=" + str(rpc_port(n)) + "\n")
|
||||
f.write("server=1\n")
|
||||
f.write("keypool=1\n")
|
||||
f.write("discover=0\n")
|
||||
f.write("listenonion=0\n")
|
||||
f.write("spendzeroconfchange=1\n")
|
||||
f.write("printtoconsole=0\n")
|
||||
f.write("natpmp=0\n")
|
||||
return datadir
|
||||
|
||||
def get_datadir_path(dirname, n):
|
||||
return os.path.join(dirname, "node" + str(n))
|
||||
|
||||
def append_config(dirname, n, options):
|
||||
datadir = get_datadir_path(dirname, n)
|
||||
with open(os.path.join(datadir, "pivx.conf"), 'a', encoding='utf8') as f:
|
||||
for option in options:
|
||||
f.write(option + "\n")
|
||||
|
||||
def get_auth_cookie(datadir):
|
||||
user = None
|
||||
password = None
|
||||
if os.path.isfile(os.path.join(datadir, "pivx.conf")):
|
||||
with open(os.path.join(datadir, "pivx.conf"), 'r', encoding='utf8') as f:
|
||||
for line in f:
|
||||
if line.startswith("rpcuser="):
|
||||
assert user is None # Ensure that there is only one rpcuser line
|
||||
user = line.split("=")[1].strip("\n")
|
||||
if line.startswith("rpcpassword="):
|
||||
assert password is None # Ensure that there is only one rpcpassword line
|
||||
password = line.split("=")[1].strip("\n")
|
||||
if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
|
||||
with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="utf8") as f:
|
||||
userpass = f.read()
|
||||
split_userpass = userpass.split(':')
|
||||
user = split_userpass[0]
|
||||
password = split_userpass[1]
|
||||
if user is None or password is None:
|
||||
raise ValueError("No RPC credentials")
|
||||
return user, password
|
||||
|
||||
# If a cookie file exists in the given datadir, delete it.
|
||||
def delete_cookie_file(datadir):
|
||||
if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
|
||||
logger.debug("Deleting leftover cookie file")
|
||||
os.remove(os.path.join(datadir, "regtest", ".cookie"))
|
||||
|
||||
def get_bip9_status(node, key):
|
||||
info = node.getblockchaininfo()
|
||||
return info['bip9_softforks'][key]
|
||||
|
||||
def set_node_times(nodes, t):
|
||||
for node in nodes:
|
||||
node.setmocktime(t)
|
||||
|
||||
def disconnect_nodes(from_connection, node_num):
|
||||
for addr in [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']]:
|
||||
try:
|
||||
from_connection.disconnectnode(addr)
|
||||
except JSONRPCException as e:
|
||||
# If this node is disconnected between calculating the peer id
|
||||
# and issuing the disconnect, don't worry about it.
|
||||
# This avoids a race condition if we're mass-disconnecting peers.
|
||||
if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
|
||||
raise
|
||||
|
||||
# wait to disconnect
|
||||
wait_until(lambda: [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5)
|
||||
|
||||
def connect_nodes(from_connection, node_num):
|
||||
ip_port = "127.0.0.1:" + str(p2p_port(node_num))
|
||||
from_connection.addnode(ip_port, "onetry")
|
||||
# poll until version handshake complete to avoid race conditions
|
||||
# with transaction relaying
|
||||
wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
|
||||
|
||||
def connect_nodes_clique(nodes):
|
||||
l = len(nodes)
|
||||
for a in range(l):
|
||||
for b in range(a, l):
|
||||
connect_nodes(nodes[a], b)
|
||||
connect_nodes(nodes[b], a)
|
||||
|
||||
# Transaction/Block functions
|
||||
#############################
|
||||
|
||||
def find_output(node, txid, amount):
|
||||
"""
|
||||
Return index to output of txid with value amount
|
||||
Raises exception if there is none.
|
||||
"""
|
||||
txdata = node.getrawtransaction(txid, 1)
|
||||
for i in range(len(txdata["vout"])):
|
||||
if txdata["vout"][i]["value"] == amount:
|
||||
return i
|
||||
raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
|
||||
|
||||
def gather_inputs(from_node, amount_needed, confirmations_required=1):
|
||||
"""
|
||||
Return a random set of unspent txouts that are enough to pay amount_needed
|
||||
"""
|
||||
assert(confirmations_required >= 0)
|
||||
utxo = from_node.listunspent(confirmations_required)
|
||||
random.shuffle(utxo)
|
||||
inputs = []
|
||||
total_in = Decimal("0.00000000")
|
||||
while total_in < amount_needed and len(utxo) > 0:
|
||||
t = utxo.pop()
|
||||
total_in += t["amount"]
|
||||
inputs.append({"txid": t["txid"], "vout": t["vout"], "address": t["address"]})
|
||||
if total_in < amount_needed:
|
||||
raise RuntimeError("Insufficient funds: need %d, have %d" % (amount_needed, total_in))
|
||||
return (total_in, inputs)
|
||||
|
||||
def make_change(from_node, amount_in, amount_out, fee):
|
||||
"""
|
||||
Create change output(s), return them
|
||||
"""
|
||||
outputs = {}
|
||||
amount = amount_out + fee
|
||||
change = amount_in - amount
|
||||
if change > amount * 2:
|
||||
# Create an extra change output to break up big inputs
|
||||
change_address = from_node.getnewaddress()
|
||||
# Split change in two, being careful of rounding:
|
||||
outputs[change_address] = Decimal(change / 2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||
change = amount_in - amount - outputs[change_address]
|
||||
if change > 0:
|
||||
outputs[from_node.getnewaddress()] = change
|
||||
return outputs
|
||||
|
||||
def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||
"""
|
||||
Create a random transaction.
|
||||
Returns (txid, hex-encoded-transaction-data, fee)
|
||||
"""
|
||||
from_node = random.choice(nodes)
|
||||
to_node = random.choice(nodes)
|
||||
fee = min_fee + fee_increment * random.randint(0, fee_variants)
|
||||
|
||||
(total_in, inputs) = gather_inputs(from_node, amount + fee)
|
||||
outputs = make_change(from_node, total_in, amount, fee)
|
||||
outputs[to_node.getnewaddress()] = float(amount)
|
||||
|
||||
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||
signresult = from_node.signrawtransaction(rawtx)
|
||||
txid = from_node.sendrawtransaction(signresult["hex"], True)
|
||||
|
||||
return (txid, signresult["hex"], fee)
|
||||
|
||||
# Helper to create at least "count" utxos
|
||||
# Pass in a fee that is sufficient for relay and mining new transactions.
|
||||
def create_confirmed_utxos(fee, node, count):
|
||||
to_generate = int(0.5 * count) + 101
|
||||
while to_generate > 0:
|
||||
node.generate(min(25, to_generate))
|
||||
to_generate -= 25
|
||||
utxos = node.listunspent()
|
||||
iterations = count - len(utxos)
|
||||
addr1 = node.getnewaddress()
|
||||
addr2 = node.getnewaddress()
|
||||
if iterations <= 0:
|
||||
return utxos
|
||||
for i in range(iterations):
|
||||
t = utxos.pop()
|
||||
inputs = []
|
||||
inputs.append({"txid": t["txid"], "vout": t["vout"]})
|
||||
outputs = {}
|
||||
send_value = t['amount'] - fee
|
||||
outputs[addr1] = float(satoshi_round(send_value / 2))
|
||||
outputs[addr2] = float(satoshi_round(send_value / 2))
|
||||
raw_tx = node.createrawtransaction(inputs, outputs)
|
||||
signed_tx = node.signrawtransaction(raw_tx)["hex"]
|
||||
node.sendrawtransaction(signed_tx)
|
||||
|
||||
while (node.getmempoolinfo()['size'] > 0):
|
||||
node.generate(1)
|
||||
|
||||
utxos = node.listunspent()
|
||||
assert(len(utxos) >= count)
|
||||
return utxos
|
||||
|
||||
# Create large OP_RETURN txouts that can be appended to a transaction
|
||||
# to make it large (helper for constructing large transactions).
|
||||
def gen_return_txouts():
|
||||
# Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
|
||||
# So we have big transactions (and therefore can't fit very many into each block)
|
||||
# create one script_pubkey
|
||||
script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
|
||||
for i in range(512):
|
||||
script_pubkey = script_pubkey + "01"
|
||||
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
|
||||
txouts = "81"
|
||||
for k in range(128):
|
||||
# add txout value
|
||||
txouts = txouts + "0000000000000000"
|
||||
# add length of script_pubkey
|
||||
txouts = txouts + "fd0402"
|
||||
# add script_pubkey
|
||||
txouts = txouts + script_pubkey
|
||||
return txouts
|
||||
|
||||
def create_tx(node, coinbase, to_address, amount):
|
||||
inputs = [{"txid": coinbase, "vout": 0}]
|
||||
outputs = {to_address: amount}
|
||||
rawtx = node.createrawtransaction(inputs, outputs)
|
||||
signresult = node.signrawtransaction(rawtx)
|
||||
assert_equal(signresult["complete"], True)
|
||||
return signresult["hex"]
|
||||
|
||||
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
|
||||
# transaction to make it large. See gen_return_txouts() above.
|
||||
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
|
||||
addr = node.getnewaddress()
|
||||
txids = []
|
||||
for _ in range(num):
|
||||
t = utxos.pop()
|
||||
inputs = [{"txid": t["txid"], "vout": t["vout"]}]
|
||||
outputs = {}
|
||||
change = t['amount'] - fee
|
||||
outputs[addr] = float(satoshi_round(change))
|
||||
rawtx = node.createrawtransaction(inputs, outputs)
|
||||
newtx = rawtx[0:92]
|
||||
newtx = newtx + txouts
|
||||
newtx = newtx + rawtx[94:]
|
||||
signresult = node.signrawtransaction(newtx, None, None, "NONE")
|
||||
txid = node.sendrawtransaction(signresult["hex"], True)
|
||||
txids.append(txid)
|
||||
return txids
|
||||
|
||||
def mine_large_block(node, utxos=None):
|
||||
# generate a 66k transaction,
|
||||
# and 14 of them is close to the 1MB block limit
|
||||
num = 14
|
||||
txouts = gen_return_txouts()
|
||||
utxos = utxos if utxos is not None else []
|
||||
if len(utxos) < num:
|
||||
utxos.clear()
|
||||
utxos.extend(node.listunspent())
|
||||
fee = 100 * node.getnetworkinfo()["relayfee"]
|
||||
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
|
||||
node.generate(1)
|
||||
|
||||
def find_vout_for_address(node, txid, addr):
|
||||
"""
|
||||
Locate the vout index of the given transaction sending to the
|
||||
given address. Raises runtime error exception if not found.
|
||||
"""
|
||||
tx = node.getrawtransaction(txid, True)
|
||||
for i in range(len(tx["vout"])):
|
||||
if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]):
|
||||
return i
|
||||
raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
|
||||
|
||||
# PIVX specific utils
|
||||
DEFAULT_FEE = 0.01
|
||||
SPORK_ACTIVATION_TIME = 1563253447
|
||||
SPORK_DEACTIVATION_TIME = 4070908800
|
||||
|
||||
def DecimalAmt(x):
|
||||
"""Return Decimal from float for equality checks against rpc outputs"""
|
||||
return Decimal("{:0.8f}".format(x))
|
||||
|
||||
# Find a coinstake/coinbase address on the node, filtering by the number of UTXOs it has.
|
||||
# If no filter is provided, returns the coinstake/coinbase address on the node containing
|
||||
# the greatest number of spendable UTXOs.
|
||||
# The default cached chain has one address per coinbase output.
|
||||
def get_coinstake_address(node, expected_utxos=None):
|
||||
addrs = [utxo['address'] for utxo in node.listunspent() if utxo['generated']]
|
||||
assert(len(set(addrs)) > 0)
|
||||
|
||||
if expected_utxos is None:
|
||||
addrs = [(addrs.count(a), a) for a in set(addrs)]
|
||||
return sorted(addrs, reverse=True)[0][1]
|
||||
|
||||
addrs = [a for a in set(addrs) if addrs.count(a) == expected_utxos]
|
||||
assert(len(addrs) > 0)
|
||||
return addrs[0]
|
||||
|
||||
# Deterministic masternodes
|
||||
def is_coin_locked_by(node, outpoint):
|
||||
return outpoint.to_json() in node.listlockunspent()
|
||||
|
||||
def get_collateral_vout(json_tx):
|
||||
funding_txidn = -1
|
||||
for o in json_tx["vout"]:
|
||||
if o["value"] == Decimal('100'):
|
||||
funding_txidn = o["n"]
|
||||
break
|
||||
assert_greater_than(funding_txidn, -1)
|
||||
return funding_txidn
|
||||
|
||||
# owner and voting keys are created from controller node.
|
||||
# operator keys are created, if operator_keys is None.
|
||||
def create_new_dmn(idx, controller, payout_addr, operator_keys):
|
||||
port = p2p_port(idx) if idx <= MAX_NODES else p2p_port(MAX_NODES) + (idx - MAX_NODES)
|
||||
ipport = "127.0.0.1:" + str(port)
|
||||
owner_addr = controller.getnewaddress("mnowner-%d" % idx)
|
||||
voting_addr = controller.getnewaddress("mnvoting-%d" % idx)
|
||||
if operator_keys is None:
|
||||
bls_keypair = controller.generateblskeypair()
|
||||
operator_pk = bls_keypair["public"]
|
||||
operator_sk = bls_keypair["secret"]
|
||||
else:
|
||||
operator_pk = operator_keys[0]
|
||||
operator_sk = operator_keys[1]
|
||||
return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk)
|
||||
|
||||
def spend_mn_collateral(spender, dmn):
|
||||
inputs = [dmn.collateral.to_json()]
|
||||
outputs = {spender.getnewaddress(): Decimal('99.99')}
|
||||
sig_res = spender.signrawtransaction(spender.createrawtransaction(inputs, outputs))
|
||||
assert_equal(sig_res['complete'], True)
|
||||
return spender.sendrawtransaction(sig_res['hex'])
|
56
basicswap/interface/pivx.py
Normal file
56
basicswap/interface/pivx.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from .btc import BTCInterface
|
||||
from basicswap.chainparams import Coins
|
||||
from .contrib.pivx_test_framework.messages import (
|
||||
CBlock,
|
||||
ToHex,
|
||||
FromHex)
|
||||
|
||||
|
||||
class PIVXInterface(BTCInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.PIVX
|
||||
|
||||
def createRawSignedTransaction(self, addr_to, amount):
|
||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
|
||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||
|
||||
options = {
|
||||
'lockUnspents': True,
|
||||
'feeRate': fee_rate,
|
||||
}
|
||||
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
||||
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
||||
return txn_signed
|
||||
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
# TODO: Bypass decoderawtransaction and getblockheader
|
||||
block = self.rpc_callback('getblock', [block_hash, False])
|
||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
||||
decoded_block = CBlock()
|
||||
decoded_block = FromHex(decoded_block, block)
|
||||
|
||||
tx_rv = []
|
||||
for tx in decoded_block.vtx:
|
||||
tx_dec = self.rpc_callback('decoderawtransaction', [ToHex(tx)])
|
||||
tx_rv.append(tx_dec)
|
||||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
|
||||
return block_rv
|
|
@ -46,6 +46,9 @@ MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.0.0')
|
|||
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
||||
XMR_SITE_COMMIT = 'f093c0da2219d94e6bef5f3948ac61b4ecdcb95b' # Lock hashes.txt to monero version
|
||||
|
||||
PIVX_VERSION = os.getenv('PIVX_VERSION', '5.4.0')
|
||||
PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '')
|
||||
|
||||
# version, version tag eg. "rc1", signers
|
||||
known_coins = {
|
||||
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
||||
|
@ -53,6 +56,7 @@ known_coins = {
|
|||
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
|
||||
'namecoin': ('0.18.0', '', ('JeremyRand',)),
|
||||
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
|
||||
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
|
||||
}
|
||||
|
||||
expected_key_ids = {
|
||||
|
@ -62,6 +66,7 @@ expected_key_ids = {
|
|||
'JeremyRand': ('2DBE339E29F6294C',),
|
||||
'binaryfate': ('F0AF4D462A0BDF92',),
|
||||
'davidburkett38': ('3620E9D387E55666',),
|
||||
'fuzzbawls': ('3BDCDA2D87A881D9',),
|
||||
}
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
|
@ -114,6 +119,12 @@ BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
|
|||
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
|
||||
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
|
||||
|
||||
PIVX_RPC_HOST = os.getenv('PIVX_RPC_HOST', '127.0.0.1')
|
||||
PIVX_RPC_PORT = int(os.getenv('PIVX_RPC_PORT', 51473))
|
||||
PIVX_ONION_PORT = int(os.getenv('PIVX_ONION_PORT', 51472)) # nDefaultPort
|
||||
PIVX_RPC_USER = os.getenv('PIVX_RPC_USER', '')
|
||||
PIVX_RPC_PWD = os.getenv('PIVX_RPC_PWD', '')
|
||||
|
||||
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
|
||||
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
|
||||
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
|
||||
|
@ -205,6 +216,36 @@ def testOnionLink():
|
|||
logger.info('Onion links work.')
|
||||
|
||||
|
||||
def downloadPIVXParams(output_dir):
|
||||
# util/fetch-params.sh
|
||||
|
||||
if os.path.exists(output_dir):
|
||||
logger.info(f'Skipping PIVX params download, path exists: {output_dir}')
|
||||
return
|
||||
os.makedirs(output_dir)
|
||||
|
||||
source_url = 'https://download.z.cash/downloads/'
|
||||
files = {
|
||||
'sapling-spend.params': '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13',
|
||||
'sapling-output.params': '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4',
|
||||
}
|
||||
|
||||
try:
|
||||
setConnectionParameters()
|
||||
for k, v in files.items():
|
||||
url = urllib.parse.urljoin(source_url, k)
|
||||
path = os.path.join(output_dir, k)
|
||||
downloadFile(url, path)
|
||||
hasher = hashlib.sha256()
|
||||
with open(path, 'rb') as fp:
|
||||
hasher.update(fp.read())
|
||||
file_hash = hasher.hexdigest()
|
||||
logger.info('%s hash: %s', k, file_hash)
|
||||
assert file_hash == v
|
||||
finally:
|
||||
popConnectionParameters()
|
||||
|
||||
|
||||
def isValidSignature(result):
|
||||
if result.valid is False \
|
||||
and (result.status == 'signature valid' and result.key_status == 'signing key has expired'):
|
||||
|
@ -342,6 +383,10 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||
release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
|
||||
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
||||
assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
||||
elif coin == 'pivx':
|
||||
release_url = 'https://github.com/PIVX-Project/PIVX/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
||||
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
|
||||
assert_url = 'https://raw.githubusercontent.com/PIVX-Project/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name.capitalize(), assert_filename)
|
||||
else:
|
||||
raise ValueError('Unknown coin')
|
||||
|
||||
|
@ -568,6 +613,14 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||
fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
|
||||
elif coin == 'namecoin':
|
||||
fp.write('prune=2000\n')
|
||||
elif coin == 'pivx':
|
||||
base_dir = extra_opts.get('data_dir', data_dir)
|
||||
params_dir = os.path.join(base_dir, 'pivx-params')
|
||||
downloadPIVXParams(params_dir)
|
||||
fp.write('prune=4000\n')
|
||||
fp.write(f'paramsdir={params_dir}\n')
|
||||
if PIVX_RPC_USER != '':
|
||||
fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD)))
|
||||
else:
|
||||
logger.warning('Unknown coin %s', coin)
|
||||
|
||||
|
@ -804,7 +857,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
|||
swap_client.setCoinRunParams(c)
|
||||
swap_client.createCoinInterface(c)
|
||||
|
||||
if c in (Coins.PART, Coins.BTC, Coins.LTC):
|
||||
if c in (Coins.PART, Coins.BTC, Coins.LTC, Coins.PIVX):
|
||||
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
||||
# Create wallet if it doesn't exist yet
|
||||
wallets = swap_client.callcoinrpc(c, 'listwallets')
|
||||
|
@ -1068,6 +1121,21 @@ def main():
|
|||
'bindir': os.path.join(bin_dir, 'monero'),
|
||||
'restore_height': xmr_restore_height,
|
||||
'blocks_confirmed': 7, # TODO: 10?
|
||||
},
|
||||
'pivx': {
|
||||
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
|
||||
'manage_daemon': True if ('pivx' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False,
|
||||
'rpchost': PIVX_RPC_HOST,
|
||||
'rpcport': PIVX_RPC_PORT + port_offset,
|
||||
'onionport': PIVX_ONION_PORT + port_offset,
|
||||
'datadir': os.getenv('PIVX_DATA_DIR', os.path.join(data_dir, 'pivx')),
|
||||
'bindir': os.path.join(bin_dir, 'pivx'),
|
||||
'use_segwit': False,
|
||||
'use_csv': False,
|
||||
'blocks_confirmed': 1,
|
||||
'conf_target': 2,
|
||||
'core_version_group': 20,
|
||||
'chain_lookups': 'local',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1080,6 +1148,9 @@ def main():
|
|||
if BTC_RPC_USER != '':
|
||||
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
|
||||
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
|
||||
if PIVX_RPC_USER != '':
|
||||
chainclients['pivx']['rpcuser'] = PIVX_RPC_USER
|
||||
chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD
|
||||
|
||||
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
|
||||
|
||||
|
|
589
pgp/keys/pivx_fuzzbawls.pgp
Normal file
589
pgp/keys/pivx_fuzzbawls.pgp
Normal file
|
@ -0,0 +1,589 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: Hostname:
|
||||
Version: Hockeypuck 2.1.0-166-geb2a11b
|
||||
|
||||
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
|
||||
tXo=
|
||||
=lheU
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -14,6 +14,7 @@ from urllib.request import urlopen
|
|||
|
||||
from basicswap.rpc import callrpc
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
from bin.basicswap_prepare import downloadPIVXParams
|
||||
|
||||
|
||||
TEST_HTTP_HOST = os.getenv('TEST_HTTP_HOST', '127.0.0.1') # Set to 0.0.0.0 when used in docker
|
||||
|
@ -33,6 +34,10 @@ LTC_BASE_PORT = 34792
|
|||
LTC_BASE_RPC_PORT = 35792
|
||||
LTC_BASE_ZMQ_PORT = 36792
|
||||
|
||||
PIVX_BASE_PORT = 34892
|
||||
PIVX_BASE_RPC_PORT = 35892
|
||||
PIVX_BASE_ZMQ_PORT = 36892
|
||||
|
||||
PREFIX_SECRET_KEY_REGTEST = 0x2e
|
||||
|
||||
|
||||
|
@ -73,6 +78,11 @@ def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_P
|
|||
fp.write('stakethreadconddelayms=1000\n')
|
||||
fp.write('smsgsregtestadjust=0\n')
|
||||
|
||||
if conf_file == 'pivx.conf':
|
||||
params_dir = os.path.join(datadir, 'pivx-params')
|
||||
downloadPIVXParams(params_dir)
|
||||
fp.write(f'paramsdir={params_dir}\n')
|
||||
|
||||
for i in range(0, num_nodes):
|
||||
if node_id == i:
|
||||
continue
|
||||
|
|
|
@ -27,6 +27,7 @@ from tests.basicswap.common import (
|
|||
BASE_PORT, BASE_RPC_PORT,
|
||||
BTC_BASE_PORT, BTC_BASE_RPC_PORT,
|
||||
LTC_BASE_PORT,
|
||||
PIVX_BASE_PORT,
|
||||
)
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
|
||||
|
@ -45,9 +46,6 @@ XMR_BASE_P2P_PORT = 17792
|
|||
XMR_BASE_RPC_PORT = 29798
|
||||
XMR_BASE_WALLET_RPC_PORT = 29998
|
||||
|
||||
LTC_BASE_RPC_PORT = 35792
|
||||
LTC_BASE_ZMQ_PORT = 36792
|
||||
|
||||
EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
|
||||
|
||||
|
||||
|
@ -192,6 +190,33 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
|
|||
for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if 'pivx' in coins_array:
|
||||
# Pruned nodes don't provide blocks
|
||||
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('prune'):
|
||||
fp.write(line)
|
||||
fp.write('port={}\n'.format(PIVX_BASE_PORT + node_id + port_ofs))
|
||||
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_pivx_' + str(node_id)
|
||||
rpc_pass = 'test_pivx_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['pivx']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['pivx']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(PIVX_BASE_PORT + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('pivx{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if 'monero' in coins_array:
|
||||
with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
|
||||
fp.write('p2p-bind-ip=127.0.0.1\n')
|
||||
|
|
552
tests/basicswap/extended/test_pivx.py
Normal file
552
tests/basicswap/extended/test_pivx.py
Normal file
|
@ -0,0 +1,552 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
"""
|
||||
basicswap]$ python tests/basicswap/extended/test_pivx.py
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import shutil
|
||||
import signal
|
||||
import logging
|
||||
import unittest
|
||||
import threading
|
||||
|
||||
import basicswap.config as cfg
|
||||
from basicswap.basicswap import (
|
||||
BasicSwap,
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
TxStates,
|
||||
)
|
||||
from basicswap.util import (
|
||||
COIN,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
TxLockTypes,
|
||||
)
|
||||
from basicswap.util.address import (
|
||||
toWIF,
|
||||
)
|
||||
from basicswap.rpc import (
|
||||
callrpc_cli,
|
||||
waitForRPC,
|
||||
)
|
||||
from basicswap.contrib.key import (
|
||||
ECKey,
|
||||
)
|
||||
from basicswap.http_server import (
|
||||
HttpThread,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
checkForks,
|
||||
stopDaemons,
|
||||
wait_for_offer,
|
||||
wait_for_bid,
|
||||
wait_for_bid_tx_state,
|
||||
wait_for_in_progress,
|
||||
read_json_api,
|
||||
TEST_HTTP_HOST,
|
||||
TEST_HTTP_PORT,
|
||||
BASE_PORT,
|
||||
BASE_RPC_PORT,
|
||||
BASE_ZMQ_PORT,
|
||||
PREFIX_SECRET_KEY_REGTEST,
|
||||
)
|
||||
from bin.basicswap_run import startDaemon
|
||||
from bin.basicswap_prepare import downloadPIVXParams
|
||||
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
NUM_NODES = 3
|
||||
PIVX_NODE = 3
|
||||
BTC_NODE = 4
|
||||
|
||||
delay_event = threading.Event()
|
||||
stop_test = False
|
||||
|
||||
|
||||
def prepareOtherDir(datadir, nodeId, conf_file='pivx.conf'):
|
||||
node_dir = os.path.join(datadir, str(nodeId))
|
||||
if not os.path.exists(node_dir):
|
||||
os.makedirs(node_dir)
|
||||
filePath = os.path.join(node_dir, conf_file)
|
||||
|
||||
with open(filePath, 'w+') as fp:
|
||||
fp.write('regtest=1\n')
|
||||
fp.write('[regtest]\n')
|
||||
fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
|
||||
fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
|
||||
|
||||
fp.write('daemon=0\n')
|
||||
fp.write('printtoconsole=0\n')
|
||||
fp.write('server=1\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('findpeers=0\n')
|
||||
fp.write('debug=1\n')
|
||||
fp.write('debugexclude=libevent\n')
|
||||
|
||||
fp.write('fallbackfee=0.01\n')
|
||||
fp.write('acceptnonstdtxn=0\n')
|
||||
|
||||
if conf_file == 'pivx.conf':
|
||||
params_dir = os.path.join(datadir, 'pivx-params')
|
||||
downloadPIVXParams(params_dir)
|
||||
fp.write(f'paramsdir={params_dir}\n')
|
||||
|
||||
if conf_file == 'bitcoin.conf':
|
||||
fp.write('wallet=wallet.dat\n')
|
||||
|
||||
|
||||
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
||||
node_dir = os.path.join(datadir, str(nodeId))
|
||||
if not os.path.exists(node_dir):
|
||||
os.makedirs(node_dir)
|
||||
filePath = os.path.join(node_dir, 'particl.conf')
|
||||
|
||||
with open(filePath, 'w+') as fp:
|
||||
fp.write('regtest=1\n')
|
||||
fp.write('[regtest]\n')
|
||||
fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
|
||||
fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
|
||||
|
||||
fp.write('daemon=0\n')
|
||||
fp.write('printtoconsole=0\n')
|
||||
fp.write('server=1\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('findpeers=0\n')
|
||||
fp.write('debug=1\n')
|
||||
fp.write('debugexclude=libevent\n')
|
||||
fp.write('zmqpubsmsg=tcp://127.0.0.1:' + str(BASE_ZMQ_PORT + nodeId) + '\n')
|
||||
fp.write('wallet=wallet.dat\n')
|
||||
fp.write('fallbackfee=0.01\n')
|
||||
|
||||
fp.write('acceptnonstdtxn=0\n')
|
||||
fp.write('minstakeinterval=5\n')
|
||||
fp.write('smsgsregtestadjust=0\n')
|
||||
|
||||
for i in range(0, NUM_NODES):
|
||||
if nodeId == i:
|
||||
continue
|
||||
fp.write('addnode=127.0.0.1:%d\n' % (BASE_PORT + i))
|
||||
|
||||
if nodeId < 2:
|
||||
fp.write('spentindex=1\n')
|
||||
fp.write('txindex=1\n')
|
||||
|
||||
basicswap_dir = os.path.join(datadir, str(nodeId), 'basicswap')
|
||||
if not os.path.exists(basicswap_dir):
|
||||
os.makedirs(basicswap_dir)
|
||||
|
||||
pivxdatadir = os.path.join(datadir, str(PIVX_NODE))
|
||||
btcdatadir = os.path.join(datadir, str(BTC_NODE))
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
settings = {
|
||||
'debug': True,
|
||||
'zmqhost': 'tcp://127.0.0.1',
|
||||
'zmqport': BASE_ZMQ_PORT + nodeId,
|
||||
'htmlhost': '127.0.0.1',
|
||||
'htmlport': 12700 + nodeId,
|
||||
'network_key': network_key,
|
||||
'network_pubkey': network_pubkey,
|
||||
'chainclients': {
|
||||
'particl': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': BASE_RPC_PORT + nodeId,
|
||||
'datadir': node_dir,
|
||||
'bindir': cfg.PARTICL_BINDIR,
|
||||
'blocks_confirmed': 2, # Faster testing
|
||||
},
|
||||
'pivx': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': BASE_RPC_PORT + PIVX_NODE,
|
||||
'datadir': pivxdatadir,
|
||||
'bindir': cfg.PIVX_BINDIR,
|
||||
'use_csv': False,
|
||||
'use_segwit': False,
|
||||
},
|
||||
'bitcoin': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': BASE_RPC_PORT + BTC_NODE,
|
||||
'datadir': btcdatadir,
|
||||
'bindir': cfg.BITCOIN_BINDIR,
|
||||
'use_segwit': True,
|
||||
}
|
||||
},
|
||||
'check_progress_seconds': 2,
|
||||
'check_watched_seconds': 4,
|
||||
'check_expired_seconds': 60
|
||||
}
|
||||
with open(settings_path, 'w') as fp:
|
||||
json.dump(settings, fp, indent=4)
|
||||
|
||||
|
||||
def partRpc(cmd, node_id=0):
|
||||
return callrpc_cli(cfg.PARTICL_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(node_id)), 'regtest', cmd, cfg.PARTICL_CLI)
|
||||
|
||||
|
||||
def btcRpc(cmd):
|
||||
return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)), 'regtest', cmd, cfg.BITCOIN_CLI)
|
||||
|
||||
|
||||
def pivxRpc(cmd):
|
||||
return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), 'regtest', cmd, cfg.PIVX_CLI)
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
global stop_test
|
||||
print('signal {} detected.'.format(sig))
|
||||
stop_test = True
|
||||
delay_event.set()
|
||||
|
||||
|
||||
def run_coins_loop(cls):
|
||||
while not stop_test:
|
||||
try:
|
||||
pivxRpc('generatetoaddress 1 {}'.format(cls.pivx_addr))
|
||||
btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr))
|
||||
except Exception as e:
|
||||
logging.warning('run_coins_loop ' + str(e))
|
||||
time.sleep(1.0)
|
||||
|
||||
|
||||
def run_loop(self):
|
||||
while not stop_test:
|
||||
for c in self.swap_clients:
|
||||
c.update()
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def make_part_cli_rpc_func(node_id):
|
||||
node_id = node_id
|
||||
|
||||
def rpc_func(method, params=None, wallet=None):
|
||||
nonlocal node_id
|
||||
cmd = method
|
||||
if params:
|
||||
for p in params:
|
||||
cmd += ' "' + p + '"'
|
||||
return partRpc(cmd, node_id)
|
||||
return rpc_func
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
eckey = ECKey()
|
||||
eckey.generate()
|
||||
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
|
||||
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||
|
||||
if os.path.isdir(cfg.TEST_DATADIRS):
|
||||
logging.info('Removing ' + cfg.TEST_DATADIRS)
|
||||
for name in os.listdir(cfg.TEST_DATADIRS):
|
||||
if name == 'pivx-params':
|
||||
continue
|
||||
fullpath = os.path.join(cfg.TEST_DATADIRS, name)
|
||||
if os.path.isdir(fullpath):
|
||||
shutil.rmtree(fullpath)
|
||||
else:
|
||||
os.remove(fullpath)
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
prepareDir(cfg.TEST_DATADIRS, i, cls.network_key, cls.network_pubkey)
|
||||
|
||||
prepareOtherDir(cfg.TEST_DATADIRS, PIVX_NODE)
|
||||
prepareOtherDir(cfg.TEST_DATADIRS, BTC_NODE, 'bitcoin.conf')
|
||||
|
||||
cls.daemons = []
|
||||
cls.swap_clients = []
|
||||
cls.http_threads = []
|
||||
|
||||
btc_data_dir = os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE))
|
||||
if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')):
|
||||
callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
|
||||
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
||||
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
|
||||
cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), cfg.PIVX_BINDIR, cfg.PIVXD))
|
||||
logging.info('Started %s %d', cfg.PIVXD, cls.daemons[-1].pid)
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
||||
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')):
|
||||
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
|
||||
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
|
||||
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid)
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
rpc = make_part_cli_rpc_func(i)
|
||||
waitForRPC(rpc)
|
||||
if i == 0:
|
||||
rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
|
||||
elif i == 1:
|
||||
rpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true'])
|
||||
rpc('getnewextaddress', ['lblExtTest'])
|
||||
rpc('rescanblockchain')
|
||||
else:
|
||||
rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']])
|
||||
|
||||
basicswap_dir = os.path.join(os.path.join(cfg.TEST_DATADIRS, str(i)), 'basicswap')
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
with open(settings_path) as fs:
|
||||
settings = json.load(fs)
|
||||
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
|
||||
cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)))
|
||||
cls.swap_clients[-1].setDaemonPID(Coins.BTC, cls.daemons[0].pid)
|
||||
cls.swap_clients[-1].setDaemonPID(Coins.PIVX, cls.daemons[1].pid)
|
||||
cls.swap_clients[-1].setDaemonPID(Coins.PART, cls.daemons[2 + i].pid)
|
||||
cls.swap_clients[-1].start()
|
||||
|
||||
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
|
||||
cls.http_threads.append(t)
|
||||
t.start()
|
||||
|
||||
waitForRPC(pivxRpc)
|
||||
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
|
||||
logging.info('Mining %d pivx blocks', num_blocks)
|
||||
cls.pivx_addr = pivxRpc('getnewaddress mining_addr')
|
||||
pivxRpc('generatetoaddress {} {}'.format(num_blocks, cls.pivx_addr))
|
||||
|
||||
ro = pivxRpc('getblockchaininfo')
|
||||
try:
|
||||
assert (ro['bip9_softforks']['csv']['status'] == 'active')
|
||||
except Exception:
|
||||
logging.info('pivx: csv is not active')
|
||||
try:
|
||||
assert (ro['bip9_softforks']['segwit']['status'] == 'active')
|
||||
except Exception:
|
||||
logging.info('pivx: segwit is not active')
|
||||
|
||||
waitForRPC(btcRpc)
|
||||
cls.btc_addr = btcRpc('getnewaddress mining_addr bech32')
|
||||
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
|
||||
btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr))
|
||||
|
||||
ro = btcRpc('getblockchaininfo')
|
||||
checkForks(ro)
|
||||
|
||||
ro = pivxRpc('getwalletinfo')
|
||||
print('pivxRpc', ro)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
|
||||
cls.update_thread.start()
|
||||
|
||||
cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,))
|
||||
cls.coins_update_thread.start()
|
||||
|
||||
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||
num_blocks = 3
|
||||
logging.info('Waiting for Particl chain height %d', num_blocks)
|
||||
for i in range(60):
|
||||
particl_blocks = cls.swap_clients[0].callrpc('getblockchaininfo')['blocks']
|
||||
print('particl_blocks', particl_blocks)
|
||||
if particl_blocks >= num_blocks:
|
||||
break
|
||||
delay_event.wait(1)
|
||||
assert (particl_blocks >= num_blocks)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
global stop_test
|
||||
logging.info('Finalising')
|
||||
stop_test = True
|
||||
cls.update_thread.join()
|
||||
cls.coins_update_thread.join()
|
||||
for t in cls.http_threads:
|
||||
t.stop()
|
||||
t.join()
|
||||
for c in cls.swap_clients:
|
||||
c.finalise()
|
||||
c.fp.close()
|
||||
|
||||
stopDaemons(cls.daemons)
|
||||
|
||||
super(Test, cls).tearDownClass()
|
||||
|
||||
def test_02_part_pivx(self):
|
||||
logging.info('---------- Test PART to PIVX')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.PIVX, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
|
||||
|
||||
wait_for_offer(delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[1].listOffers()
|
||||
assert (len(offers) == 1)
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id)
|
||||
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
def test_03_pivx_part(self):
|
||||
logging.info('---------- Test PIVX to PART')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
offer_id = swap_clients[1].postOffer(Coins.PIVX, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
|
||||
|
||||
wait_for_offer(delay_event, swap_clients[0], offer_id)
|
||||
offers = swap_clients[0].listOffers()
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[1], bid_id)
|
||||
swap_clients[1].acceptBid(bid_id)
|
||||
|
||||
wait_for_in_progress(delay_event, swap_clients[0], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
def test_04_pivx_btc(self):
|
||||
logging.info('---------- Test PIVX to BTC')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
|
||||
|
||||
wait_for_offer(delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[1].listOffers()
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
|
||||
js_0bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
def test_05_refund(self):
|
||||
# Seller submits initiate txn, buyer doesn't respond
|
||||
logging.info('---------- Test refund, PIVX to BTC')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST,
|
||||
TxLockTypes.ABS_LOCK_BLOCKS, 10)
|
||||
|
||||
wait_for_offer(delay_event, swap_clients[1], offer_id)
|
||||
offers = swap_clients[1].listOffers()
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id)
|
||||
swap_clients[1].abandonBid(bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=60)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
def test_06_self_bid(self):
|
||||
logging.info('---------- Test same client, BTC to PIVX')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_0_before = read_json_api(1800)
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
|
||||
|
||||
wait_for_offer(delay_event, swap_clients[0], offer_id)
|
||||
offers = swap_clients[0].listOffers()
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid_tx_state(delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60)
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_0['num_recv_bids'] == js_0_before['num_recv_bids'] + 1 and js_0['num_sent_bids'] == js_0_before['num_sent_bids'] + 1)
|
||||
|
||||
def test_07_error(self):
|
||||
logging.info('---------- Test error, BTC to PIVX, set fee above bid value')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
js_0_before = read_json_api(1800)
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
|
||||
|
||||
wait_for_offer(delay_event, swap_clients[0], offer_id)
|
||||
offers = swap_clients[0].listOffers()
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate'] = 10.0
|
||||
swap_clients[0].getChainClientSettings(Coins.PIVX)['override_feerate'] = 10.0
|
||||
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60)
|
||||
|
||||
def pass_99_delay(self):
|
||||
global stop_test
|
||||
logging.info('Delay')
|
||||
for i in range(60 * 5):
|
||||
if stop_test:
|
||||
break
|
||||
time.sleep(1)
|
||||
print('delay', i)
|
||||
stop_test = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -43,6 +43,8 @@ class Test(BaseTest):
|
|||
cls.test_coin_from = Coins.BTC
|
||||
if not hasattr(cls, 'start_ltc_nodes'):
|
||||
cls.start_ltc_nodes = False
|
||||
if not hasattr(cls, 'start_pivx_nodes'):
|
||||
cls.start_pivx_nodes = False
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -38,7 +38,7 @@ from basicswap.util import (
|
|||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
REQUIRED_SETTINGS = {'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': True}
|
||||
REQUIRED_SETTINGS = {'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': True, 'connection_type': 'rpc'}
|
||||
|
||||
def test_serialise_num(self):
|
||||
def test_case(v, nb=None):
|
||||
|
|
|
@ -64,6 +64,7 @@ class Test(BaseTest):
|
|||
def setUpClass(cls):
|
||||
cls.start_ltc_nodes = True
|
||||
cls.start_xmr_nodes = False
|
||||
cls.start_pivx_nodes = False
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
|
||||
|
|
|
@ -80,6 +80,8 @@ from tests.basicswap.common import (
|
|||
BTC_BASE_RPC_PORT,
|
||||
LTC_BASE_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
PIVX_BASE_PORT,
|
||||
PIVX_BASE_RPC_PORT,
|
||||
PREFIX_SECRET_KEY_REGTEST,
|
||||
)
|
||||
from bin.basicswap_run import startDaemon, startXmrDaemon
|
||||
|
@ -91,6 +93,7 @@ NUM_NODES = 3
|
|||
NUM_XMR_NODES = 3
|
||||
NUM_BTC_NODES = 3
|
||||
NUM_LTC_NODES = 3
|
||||
NUM_PIVX_NODES = 3
|
||||
TEST_DIR = cfg.TEST_DATADIRS
|
||||
|
||||
XMR_BASE_P2P_PORT = 17792
|
||||
|
@ -150,7 +153,7 @@ def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]):
|
|||
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
|
||||
|
||||
|
||||
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_ltc=False, with_xmr=False):
|
||||
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set()):
|
||||
basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
|
||||
if not os.path.exists(basicswap_dir):
|
||||
os.makedirs(basicswap_dir)
|
||||
|
@ -201,7 +204,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
|
|||
'debug_ui': True,
|
||||
}
|
||||
|
||||
if with_xmr:
|
||||
if Coins.XMR in with_coins:
|
||||
settings['chainclients']['monero'] = {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
|
@ -214,7 +217,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
|
|||
'bindir': cfg.XMR_BINDIR,
|
||||
}
|
||||
|
||||
if with_ltc:
|
||||
if Coins.LTC in with_coins:
|
||||
settings['chainclients']['litecoin'] = {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
|
@ -226,6 +229,18 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
|
|||
'use_segwit': True,
|
||||
}
|
||||
|
||||
if Coins.PIVX in with_coins:
|
||||
settings['chainclients']['pivx'] = {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': PIVX_BASE_RPC_PORT + node_id,
|
||||
'rpcuser': 'test' + str(node_id),
|
||||
'rpcpassword': 'test_pass' + str(node_id),
|
||||
'datadir': os.path.join(datadir, 'pivx_' + str(node_id)),
|
||||
'bindir': cfg.PIVX_BINDIR,
|
||||
'use_segwit': False,
|
||||
}
|
||||
|
||||
with open(settings_path, 'w') as fp:
|
||||
json.dump(settings, fp, indent=4)
|
||||
|
||||
|
@ -238,6 +253,10 @@ def ltcCli(cmd, node_id=0):
|
|||
return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(TEST_DIR, 'ltc_' + str(node_id)), 'regtest', cmd, cfg.LITECOIN_CLI)
|
||||
|
||||
|
||||
def pivxCli(cmd, node_id=0):
|
||||
return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(TEST_DIR, 'pivx_' + str(node_id)), 'regtest', cmd, cfg.PIVX_CLI)
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
logging.info('signal {} detected.'.format(sig))
|
||||
test_delay_event.set()
|
||||
|
@ -283,6 +302,8 @@ def run_coins_loop(cls):
|
|||
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
|
||||
if cls.ltc_addr is not None:
|
||||
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
|
||||
if cls.pivx_addr is not None:
|
||||
pivxCli('generatetoaddress 1 {}'.format(cls.pivx_addr))
|
||||
if cls.xmr_addr is not None:
|
||||
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
|
||||
except Exception as e:
|
||||
|
@ -304,6 +325,8 @@ class BaseTest(unittest.TestCase):
|
|||
def setUpClass(cls):
|
||||
if not hasattr(cls, 'start_ltc_nodes'):
|
||||
cls.start_ltc_nodes = False
|
||||
if not hasattr(cls, 'start_pivx_nodes'):
|
||||
cls.start_pivx_nodes = False
|
||||
if not hasattr(cls, 'start_xmr_nodes'):
|
||||
cls.start_xmr_nodes = True
|
||||
|
||||
|
@ -316,12 +339,14 @@ class BaseTest(unittest.TestCase):
|
|||
cls.part_daemons = []
|
||||
cls.btc_daemons = []
|
||||
cls.ltc_daemons = []
|
||||
cls.pivx_daemons = []
|
||||
cls.xmr_daemons = []
|
||||
cls.xmr_wallet_auth = []
|
||||
|
||||
cls.xmr_addr = None
|
||||
cls.btc_addr = None
|
||||
cls.ltc_addr = None
|
||||
cls.pivx_addr = None
|
||||
|
||||
logger.propagate = False
|
||||
logger.handlers = []
|
||||
|
@ -333,7 +358,14 @@ class BaseTest(unittest.TestCase):
|
|||
|
||||
if os.path.isdir(TEST_DIR):
|
||||
logging.info('Removing ' + TEST_DIR)
|
||||
shutil.rmtree(TEST_DIR)
|
||||
for name in os.listdir(TEST_DIR):
|
||||
if name == 'pivx-params':
|
||||
continue
|
||||
fullpath = os.path.join(TEST_DIR, name)
|
||||
if os.path.isdir(fullpath):
|
||||
shutil.rmtree(fullpath)
|
||||
else:
|
||||
os.remove(fullpath)
|
||||
if not os.path.exists(TEST_DIR):
|
||||
os.makedirs(TEST_DIR)
|
||||
|
||||
|
@ -391,6 +423,17 @@ class BaseTest(unittest.TestCase):
|
|||
|
||||
waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT))
|
||||
|
||||
if cls.start_pivx_nodes:
|
||||
for i in range(NUM_PIVX_NODES):
|
||||
data_dir = prepareDataDir(TEST_DIR, i, 'pivx.conf', 'pivx_', base_p2p_port=PIVX_BASE_PORT, base_rpc_port=PIVX_BASE_RPC_PORT)
|
||||
if os.path.exists(os.path.join(cfg.PIVX_BINDIR, 'pivx-wallet')):
|
||||
callrpc_cli(cfg.PIVX_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'pivx-wallet')
|
||||
|
||||
cls.pivx_daemons.append(startDaemon(os.path.join(TEST_DIR, 'pivx_' + str(i)), cfg.PIVX_BINDIR, cfg.PIVXD))
|
||||
logging.info('Started %s %d', cfg.PIVXD, cls.part_daemons[-1].pid)
|
||||
|
||||
waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT))
|
||||
|
||||
if cls.start_xmr_nodes:
|
||||
for i in range(NUM_XMR_NODES):
|
||||
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
|
||||
|
@ -417,7 +460,14 @@ class BaseTest(unittest.TestCase):
|
|||
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, cls.start_ltc_nodes, cls.start_xmr_nodes)
|
||||
start_nodes = set()
|
||||
if cls.start_ltc_nodes:
|
||||
start_nodes.add(Coins.LTC)
|
||||
if cls.start_xmr_nodes:
|
||||
start_nodes.add(Coins.XMR)
|
||||
if cls.start_pivx_nodes:
|
||||
start_nodes.add(Coins.PIVX)
|
||||
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes)
|
||||
basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
with open(settings_path) as fs:
|
||||
|
@ -481,6 +531,18 @@ class BaseTest(unittest.TestCase):
|
|||
|
||||
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
|
||||
|
||||
if cls.start_pivx_nodes:
|
||||
num_blocks = 400
|
||||
cls.pivx_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=PIVX_BASE_RPC_PORT)
|
||||
logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr)
|
||||
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT)
|
||||
|
||||
# Switch addresses so wallet amounts stay constant
|
||||
num_blocks = 100
|
||||
cls.pivx_addr = cls.swap_clients[0].ci(Coins.PIVX).pubkey_to_address(void_block_rewards_pubkey)
|
||||
logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr)
|
||||
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT)
|
||||
|
||||
num_blocks = 100
|
||||
if cls.start_xmr_nodes:
|
||||
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
|
||||
|
@ -537,6 +599,7 @@ class BaseTest(unittest.TestCase):
|
|||
stopDaemons(cls.part_daemons)
|
||||
stopDaemons(cls.btc_daemons)
|
||||
stopDaemons(cls.ltc_daemons)
|
||||
stopDaemons(cls.pivx_daemons)
|
||||
|
||||
super(BaseTest, cls).tearDownClass()
|
||||
|
||||
|
|
Loading…
Reference in a new issue