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
cacd29130e
commit
d74699992b
23 changed files with 3969 additions and 38 deletions
|
@ -7,8 +7,8 @@ lint_task:
|
||||||
- pip install codespell
|
- pip install codespell
|
||||||
script:
|
script:
|
||||||
- flake8 --version
|
- flake8 --version
|
||||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs,.tox,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,*mnemonics.py,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:
|
test_task:
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -52,8 +52,8 @@ jobs:
|
||||||
- travis_retry pip install codespell==1.15.0
|
- travis_retry pip install codespell==1.15.0
|
||||||
before_script:
|
before_script:
|
||||||
script:
|
script:
|
||||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs,.tox,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,*mnemonics.py,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:
|
after_success:
|
||||||
- echo "End lint"
|
- echo "End lint"
|
||||||
- stage: test
|
- stage: test
|
||||||
|
|
|
@ -32,6 +32,7 @@ from .interface.btc import BTCInterface
|
||||||
from .interface.ltc import LTCInterface
|
from .interface.ltc import LTCInterface
|
||||||
from .interface.nmc import NMCInterface
|
from .interface.nmc import NMCInterface
|
||||||
from .interface.xmr import XMRInterface
|
from .interface.xmr import XMRInterface
|
||||||
|
from .interface.pivx import PIVXInterface
|
||||||
from .interface.passthrough_btc import PassthroughBTCInterface
|
from .interface.passthrough_btc import PassthroughBTCInterface
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
@ -174,7 +175,8 @@ def threadPollChainState(swap_client, coin_type):
|
||||||
with swap_client.mxDB:
|
with swap_client.mxDB:
|
||||||
cc['chain_height'] = chain_state['blocks']
|
cc['chain_height'] = chain_state['blocks']
|
||||||
cc['chain_best_block'] = chain_state['bestblockhash']
|
cc['chain_best_block'] = chain_state['bestblockhash']
|
||||||
cc['chain_median_time'] = chain_state['mediantime']
|
if 'mediantime' in chain_state:
|
||||||
|
cc['chain_median_time'] = chain_state['mediantime']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.warning('threadPollChainState {}, error: {}'.format(str(coin_type), str(e)))
|
swap_client.log.warning('threadPollChainState {}, error: {}'.format(str(coin_type), str(e)))
|
||||||
swap_client.delay_event.wait(random.randrange(20, 30)) # random to stagger updates
|
swap_client.delay_event.wait(random.randrange(20, 30)) # random to stagger updates
|
||||||
|
@ -380,21 +382,24 @@ class BasicSwap(BaseApp):
|
||||||
session.close()
|
session.close()
|
||||||
session.remove()
|
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] = {
|
self.coin_clients[coin] = {
|
||||||
'coin': coin,
|
'coin': coin,
|
||||||
'name': chainparams[coin]['name'],
|
'name': coin_chainparams['name'],
|
||||||
'connection_type': connection_type,
|
'connection_type': connection_type,
|
||||||
'bindir': bindir,
|
'bindir': bindir,
|
||||||
'datadir': datadir,
|
'datadir': datadir,
|
||||||
'rpchost': chain_client_settings.get('rpchost', '127.0.0.1'),
|
'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,
|
'rpcauth': rpcauth,
|
||||||
'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6),
|
'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6),
|
||||||
'conf_target': chain_client_settings.get('conf_target', 2),
|
'conf_target': chain_client_settings.get('conf_target', 2),
|
||||||
'watched_outputs': [],
|
'watched_outputs': [],
|
||||||
'last_height_checked': last_height_checked,
|
'last_height_checked': last_height_checked,
|
||||||
'use_segwit': chain_client_settings.get('use_segwit', False),
|
'use_segwit': chain_client_settings.get('use_segwit', default_segwit),
|
||||||
'use_csv': chain_client_settings.get('use_csv', True),
|
'use_csv': chain_client_settings.get('use_csv', default_csv),
|
||||||
'core_version_group': chain_client_settings.get('core_version_group', 0),
|
'core_version_group': chain_client_settings.get('core_version_group', 0),
|
||||||
'pid': None,
|
'pid': None,
|
||||||
'core_version': None,
|
'core_version': None,
|
||||||
|
@ -482,6 +487,8 @@ class BasicSwap(BaseApp):
|
||||||
chain_client_settings = self.getChainClientSettings(coin)
|
chain_client_settings = self.getChainClientSettings(coin)
|
||||||
xmr_i.setWalletFilename(chain_client_settings['walletfile'])
|
xmr_i.setWalletFilename(chain_client_settings['walletfile'])
|
||||||
return xmr_i
|
return xmr_i
|
||||||
|
elif coin == Coins.PIVX:
|
||||||
|
return PIVXInterface(self.coin_clients[coin], self.chain, self)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown coin type')
|
raise ValueError('Unknown coin type')
|
||||||
|
|
||||||
|
@ -927,6 +934,8 @@ class BasicSwap(BaseApp):
|
||||||
raise ValueError('Invalid swap type for PART_ANON')
|
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:
|
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')
|
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):
|
def notify(self, event_type, event_data):
|
||||||
if event_type == NT.OFFER_RECEIVED:
|
if event_type == NT.OFFER_RECEIVED:
|
||||||
|
@ -959,19 +968,23 @@ class BasicSwap(BaseApp):
|
||||||
ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain')
|
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):
|
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:
|
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(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:
|
elif lock_type == OfferMessage.SEQUENCE_LOCK_BLOCKS:
|
||||||
ensure(lock_value >= 5 and lock_value <= 1000, 'Invalid lock_value 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:
|
elif lock_type == TxLockTypes.ABS_LOCK_TIME:
|
||||||
# TODO: range?
|
# 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')
|
ensure(lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60, 'Invalid lock_value time')
|
||||||
elif lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
|
elif lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
|
||||||
# TODO: range?
|
# 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')
|
ensure(lock_value >= 10 and lock_value <= 1000, 'Invalid lock_value blocks')
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown locktype')
|
raise ValueError('Unknown locktype')
|
||||||
|
@ -2570,10 +2583,14 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
# Check fee
|
# 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])
|
redeem_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [redeem_txn])
|
||||||
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize'])
|
if ci.using_segwit():
|
||||||
ensure(tx_vsize >= redeem_txjs['vsize'], 'Underpaid fee')
|
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize'])
|
||||||
|
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])
|
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)
|
self.log.debug('Have valid redeem txn %s for contract %s tx %s', redeem_txjs['txid'], for_txn_type, prev_txnid)
|
||||||
|
@ -2670,10 +2687,14 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
# Check fee
|
# 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])
|
refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn])
|
||||||
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, refund_txjs['vsize'])
|
if ci.using_segwit():
|
||||||
ensure(tx_vsize >= refund_txjs['vsize'], 'underpaid fee')
|
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])
|
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'])
|
self.log.debug('Have valid refund txn %s for contract tx %s', refund_txjs['txid'], txjs['txid'])
|
||||||
|
@ -3497,13 +3518,14 @@ class BasicSwap(BaseApp):
|
||||||
spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
|
spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
|
||||||
self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
|
self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
|
||||||
else:
|
else:
|
||||||
chain_blocks = self.callcoinrpc(coin_type, 'getblockcount')
|
ci = self.ci(coin_type)
|
||||||
|
chain_blocks = ci.getChainHeight()
|
||||||
last_height_checked = c['last_height_checked']
|
last_height_checked = c['last_height_checked']
|
||||||
self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
|
self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
|
||||||
while last_height_checked < chain_blocks:
|
while last_height_checked < chain_blocks:
|
||||||
block_hash = self.callcoinrpc(coin_type, 'getblockhash', [last_height_checked + 1])
|
block_hash = self.callcoinrpc(coin_type, 'getblockhash', [last_height_checked + 1])
|
||||||
try:
|
try:
|
||||||
block = self.callcoinrpc(coin_type, 'getblock', [block_hash, 2])
|
block = ci.getBlockWithTxns(block_hash)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'Block not available (pruned data)' in str(e):
|
if 'Block not available (pruned data)' in str(e):
|
||||||
# TODO: Better solution?
|
# TODO: Better solution?
|
||||||
|
@ -3511,6 +3533,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'])
|
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']
|
last_height_checked = bci['pruneheight']
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
self.log.error(f'getblock error {e}')
|
||||||
|
break
|
||||||
|
|
||||||
for tx in block['tx']:
|
for tx in block['tx']:
|
||||||
for i, inp in enumerate(tx['vin']):
|
for i, inp in enumerate(tx['vin']):
|
||||||
|
|
|
@ -26,6 +26,9 @@ class Coins(IntEnum):
|
||||||
XMR = 6
|
XMR = 6
|
||||||
PART_BLIND = 7
|
PART_BLIND = 7
|
||||||
PART_ANON = 8
|
PART_ANON = 8
|
||||||
|
# ZANO = 9
|
||||||
|
# NDAU = 10
|
||||||
|
PIVX = 11
|
||||||
|
|
||||||
|
|
||||||
chainparams = {
|
chainparams = {
|
||||||
|
@ -206,10 +209,45 @@ chainparams = {
|
||||||
'min_amount': 100000,
|
'min_amount': 100000,
|
||||||
'max_amount': 10000 * XMR_COIN,
|
'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 = {}
|
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')))
|
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
|
||||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
||||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
||||||
|
|
||||||
|
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.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||||
self.setConfTarget(coin_settings['conf_target'])
|
self.setConfTarget(coin_settings['conf_target'])
|
||||||
self._use_segwit = coin_settings['use_segwit']
|
self._use_segwit = coin_settings['use_segwit']
|
||||||
|
self._connection_type = coin_settings['connection_type']
|
||||||
self._sc = swap_client
|
self._sc = swap_client
|
||||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
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):
|
def open_rpc(self, wallet=None):
|
||||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
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):
|
def get_fee_rate(self, conf_target=2):
|
||||||
try:
|
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:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'], 'paytxfee'
|
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
|
||||||
assert (fee_rate > 0.0), '0 feerate'
|
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||||
return fee_rate
|
return fee_rate, 'paytxfee'
|
||||||
except Exception:
|
except Exception:
|
||||||
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
||||||
|
|
||||||
|
@ -1161,6 +1170,9 @@ class BTCInterface(CoinInterface):
|
||||||
txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
|
txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
|
||||||
return txn_signed
|
return txn_signed
|
||||||
|
|
||||||
|
def getBlockWithTxns(self, block_hash):
|
||||||
|
return self.rpc_callback('getblock', [block_hash, 2])
|
||||||
|
|
||||||
|
|
||||||
def testBTCInterface():
|
def testBTCInterface():
|
||||||
print('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', '')
|
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
||||||
XMR_SITE_COMMIT = 'f093c0da2219d94e6bef5f3948ac61b4ecdcb95b' # Lock hashes.txt to monero version
|
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
|
# version, version tag eg. "rc1", signers
|
||||||
known_coins = {
|
known_coins = {
|
||||||
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
||||||
|
@ -53,6 +56,7 @@ known_coins = {
|
||||||
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
|
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
|
||||||
'namecoin': ('0.18.0', '', ('JeremyRand',)),
|
'namecoin': ('0.18.0', '', ('JeremyRand',)),
|
||||||
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
|
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
|
||||||
|
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_key_ids = {
|
expected_key_ids = {
|
||||||
|
@ -62,6 +66,7 @@ expected_key_ids = {
|
||||||
'JeremyRand': ('2DBE339E29F6294C',),
|
'JeremyRand': ('2DBE339E29F6294C',),
|
||||||
'binaryfate': ('F0AF4D462A0BDF92',),
|
'binaryfate': ('F0AF4D462A0BDF92',),
|
||||||
'davidburkett38': ('3620E9D387E55666',),
|
'davidburkett38': ('3620E9D387E55666',),
|
||||||
|
'fuzzbawls': ('3BDCDA2D87A881D9',),
|
||||||
}
|
}
|
||||||
|
|
||||||
if platform.system() == 'Darwin':
|
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_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
|
||||||
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
|
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_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
|
||||||
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
|
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
|
||||||
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
|
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
|
||||||
|
@ -205,6 +216,36 @@ def testOnionLink():
|
||||||
logger.info('Onion links work.')
|
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):
|
def isValidSignature(result):
|
||||||
if result.valid is False \
|
if result.valid is False \
|
||||||
and (result.status == 'signature valid' and result.key_status == 'signing key has expired'):
|
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)
|
release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
|
||||||
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
||||||
assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
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:
|
else:
|
||||||
raise ValueError('Unknown coin')
|
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)))
|
fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
|
||||||
elif coin == 'namecoin':
|
elif coin == 'namecoin':
|
||||||
fp.write('prune=2000\n')
|
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:
|
else:
|
||||||
logger.warning('Unknown coin %s', coin)
|
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.setCoinRunParams(c)
|
||||||
swap_client.createCoinInterface(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)
|
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
||||||
# Create wallet if it doesn't exist yet
|
# Create wallet if it doesn't exist yet
|
||||||
wallets = swap_client.callcoinrpc(c, 'listwallets')
|
wallets = swap_client.callcoinrpc(c, 'listwallets')
|
||||||
|
@ -1068,6 +1121,21 @@ def main():
|
||||||
'bindir': os.path.join(bin_dir, 'monero'),
|
'bindir': os.path.join(bin_dir, 'monero'),
|
||||||
'restore_height': xmr_restore_height,
|
'restore_height': xmr_restore_height,
|
||||||
'blocks_confirmed': 7, # TODO: 10?
|
'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 != '':
|
if BTC_RPC_USER != '':
|
||||||
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
|
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
|
||||||
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
|
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
|
||||||
|
if 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'])
|
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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
|
||||||
|
2H/kSTLCXWNkm0CMycRHZ17YnBufl6+9zSsP5P1OAR28/FGblHADq1AxzHm+eDhB
|
||||||
|
LKiMUage5SFJ37FtUGb+8GM8p8TGARPzZ4OMxkagcWtvNJ0OXFUXiN68LqH4Dwwa
|
||||||
|
6djo5gka0MFo4WdYX/blw2R0xM7to22OsOP2NcSPKrm92SYyotRjEmjQZr4Swy4Z
|
||||||
|
wSKyouIc0f8AADg5/wAAODQBEAABAQAAAAAAAAAAAAAAAP/Y/9sAhAAIBgYHBgUI
|
||||||
|
BwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8n
|
||||||
|
OT04MjwuMzQyAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy
|
||||||
|
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAEoASgDASIAAhEBAxEB/8QB
|
||||||
|
ogAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLEAACAQMDAgQDBQUEBAAAAX0B
|
||||||
|
AgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygp
|
||||||
|
KjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImK
|
||||||
|
kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj
|
||||||
|
5OXm5+jp6vHy8/T19vf4+foBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxEA
|
||||||
|
AgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
|
||||||
|
YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
|
||||||
|
anN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPE
|
||||||
|
xcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDy
|
||||||
|
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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.rpc import callrpc
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
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
|
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_RPC_PORT = 35792
|
||||||
LTC_BASE_ZMQ_PORT = 36792
|
LTC_BASE_ZMQ_PORT = 36792
|
||||||
|
|
||||||
|
PIVX_BASE_PORT = 34892
|
||||||
|
PIVX_BASE_RPC_PORT = 35892
|
||||||
|
PIVX_BASE_ZMQ_PORT = 36892
|
||||||
|
|
||||||
PREFIX_SECRET_KEY_REGTEST = 0x2e
|
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('stakethreadconddelayms=1000\n')
|
||||||
fp.write('smsgsregtestadjust=0\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):
|
for i in range(0, num_nodes):
|
||||||
if node_id == i:
|
if node_id == i:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -27,6 +27,7 @@ from tests.basicswap.common import (
|
||||||
BASE_PORT, BASE_RPC_PORT,
|
BASE_PORT, BASE_RPC_PORT,
|
||||||
BTC_BASE_PORT, BTC_BASE_RPC_PORT,
|
BTC_BASE_PORT, BTC_BASE_RPC_PORT,
|
||||||
LTC_BASE_PORT,
|
LTC_BASE_PORT,
|
||||||
|
PIVX_BASE_PORT,
|
||||||
)
|
)
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
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_RPC_PORT = 29798
|
||||||
XMR_BASE_WALLET_RPC_PORT = 29998
|
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', '{}'))
|
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), []):
|
for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
|
||||||
fp.write(opt + '\n')
|
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:
|
if 'monero' in coins_array:
|
||||||
with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
|
with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
|
||||||
fp.write('p2p-bind-ip=127.0.0.1\n')
|
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
|
cls.test_coin_from = Coins.BTC
|
||||||
if not hasattr(cls, 'start_ltc_nodes'):
|
if not hasattr(cls, 'start_ltc_nodes'):
|
||||||
cls.start_ltc_nodes = False
|
cls.start_ltc_nodes = False
|
||||||
|
if not hasattr(cls, 'start_pivx_nodes'):
|
||||||
|
cls.start_pivx_nodes = False
|
||||||
super(Test, cls).setUpClass()
|
super(Test, cls).setUpClass()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -38,7 +38,7 @@ from basicswap.util import (
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
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_serialise_num(self):
|
||||||
def test_case(v, nb=None):
|
def test_case(v, nb=None):
|
||||||
|
|
|
@ -64,6 +64,7 @@ class Test(BaseTest):
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.start_ltc_nodes = True
|
cls.start_ltc_nodes = True
|
||||||
cls.start_xmr_nodes = False
|
cls.start_xmr_nodes = False
|
||||||
|
cls.start_pivx_nodes = False
|
||||||
super(Test, cls).setUpClass()
|
super(Test, cls).setUpClass()
|
||||||
|
|
||||||
btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
|
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,
|
BTC_BASE_RPC_PORT,
|
||||||
LTC_BASE_PORT,
|
LTC_BASE_PORT,
|
||||||
LTC_BASE_RPC_PORT,
|
LTC_BASE_RPC_PORT,
|
||||||
|
PIVX_BASE_PORT,
|
||||||
|
PIVX_BASE_RPC_PORT,
|
||||||
PREFIX_SECRET_KEY_REGTEST,
|
PREFIX_SECRET_KEY_REGTEST,
|
||||||
)
|
)
|
||||||
from bin.basicswap_run import startDaemon, startXmrDaemon
|
from bin.basicswap_run import startDaemon, startXmrDaemon
|
||||||
|
@ -91,6 +93,7 @@ NUM_NODES = 3
|
||||||
NUM_XMR_NODES = 3
|
NUM_XMR_NODES = 3
|
||||||
NUM_BTC_NODES = 3
|
NUM_BTC_NODES = 3
|
||||||
NUM_LTC_NODES = 3
|
NUM_LTC_NODES = 3
|
||||||
|
NUM_PIVX_NODES = 3
|
||||||
TEST_DIR = cfg.TEST_DATADIRS
|
TEST_DIR = cfg.TEST_DATADIRS
|
||||||
|
|
||||||
XMR_BASE_P2P_PORT = 17792
|
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)
|
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))
|
basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
|
||||||
if not os.path.exists(basicswap_dir):
|
if not os.path.exists(basicswap_dir):
|
||||||
os.makedirs(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,
|
'debug_ui': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
if with_xmr:
|
if Coins.XMR in with_coins:
|
||||||
settings['chainclients']['monero'] = {
|
settings['chainclients']['monero'] = {
|
||||||
'connection_type': 'rpc',
|
'connection_type': 'rpc',
|
||||||
'manage_daemon': False,
|
'manage_daemon': False,
|
||||||
|
@ -214,7 +217,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
|
||||||
'bindir': cfg.XMR_BINDIR,
|
'bindir': cfg.XMR_BINDIR,
|
||||||
}
|
}
|
||||||
|
|
||||||
if with_ltc:
|
if Coins.LTC in with_coins:
|
||||||
settings['chainclients']['litecoin'] = {
|
settings['chainclients']['litecoin'] = {
|
||||||
'connection_type': 'rpc',
|
'connection_type': 'rpc',
|
||||||
'manage_daemon': False,
|
'manage_daemon': False,
|
||||||
|
@ -226,6 +229,18 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
|
||||||
'use_segwit': True,
|
'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:
|
with open(settings_path, 'w') as fp:
|
||||||
json.dump(settings, fp, indent=4)
|
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)
|
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):
|
def signal_handler(sig, frame):
|
||||||
logging.info('signal {} detected.'.format(sig))
|
logging.info('signal {} detected.'.format(sig))
|
||||||
test_delay_event.set()
|
test_delay_event.set()
|
||||||
|
@ -283,6 +302,8 @@ def run_coins_loop(cls):
|
||||||
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
|
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
|
||||||
if cls.ltc_addr is not None:
|
if cls.ltc_addr is not None:
|
||||||
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
|
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
|
||||||
|
if cls.pivx_addr is not None:
|
||||||
|
pivxCli('generatetoaddress 1 {}'.format(cls.pivx_addr))
|
||||||
if cls.xmr_addr is not None:
|
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})
|
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -304,6 +325,8 @@ class BaseTest(unittest.TestCase):
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
if not hasattr(cls, 'start_ltc_nodes'):
|
if not hasattr(cls, 'start_ltc_nodes'):
|
||||||
cls.start_ltc_nodes = False
|
cls.start_ltc_nodes = False
|
||||||
|
if not hasattr(cls, 'start_pivx_nodes'):
|
||||||
|
cls.start_pivx_nodes = False
|
||||||
if not hasattr(cls, 'start_xmr_nodes'):
|
if not hasattr(cls, 'start_xmr_nodes'):
|
||||||
cls.start_xmr_nodes = True
|
cls.start_xmr_nodes = True
|
||||||
|
|
||||||
|
@ -316,12 +339,14 @@ class BaseTest(unittest.TestCase):
|
||||||
cls.part_daemons = []
|
cls.part_daemons = []
|
||||||
cls.btc_daemons = []
|
cls.btc_daemons = []
|
||||||
cls.ltc_daemons = []
|
cls.ltc_daemons = []
|
||||||
|
cls.pivx_daemons = []
|
||||||
cls.xmr_daemons = []
|
cls.xmr_daemons = []
|
||||||
cls.xmr_wallet_auth = []
|
cls.xmr_wallet_auth = []
|
||||||
|
|
||||||
cls.xmr_addr = None
|
cls.xmr_addr = None
|
||||||
cls.btc_addr = None
|
cls.btc_addr = None
|
||||||
cls.ltc_addr = None
|
cls.ltc_addr = None
|
||||||
|
cls.pivx_addr = None
|
||||||
|
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
logger.handlers = []
|
logger.handlers = []
|
||||||
|
@ -333,7 +358,14 @@ class BaseTest(unittest.TestCase):
|
||||||
|
|
||||||
if os.path.isdir(TEST_DIR):
|
if os.path.isdir(TEST_DIR):
|
||||||
logging.info('Removing ' + 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):
|
if not os.path.exists(TEST_DIR):
|
||||||
os.makedirs(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))
|
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:
|
if cls.start_xmr_nodes:
|
||||||
for i in range(NUM_XMR_NODES):
|
for i in range(NUM_XMR_NODES):
|
||||||
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
|
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
|
||||||
|
@ -417,7 +460,14 @@ class BaseTest(unittest.TestCase):
|
||||||
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
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)))
|
basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
|
||||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||||
with open(settings_path) as fs:
|
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))
|
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
|
num_blocks = 100
|
||||||
if cls.start_xmr_nodes:
|
if cls.start_xmr_nodes:
|
||||||
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
|
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
|
||||||
|
@ -537,6 +599,7 @@ class BaseTest(unittest.TestCase):
|
||||||
stopDaemons(cls.part_daemons)
|
stopDaemons(cls.part_daemons)
|
||||||
stopDaemons(cls.btc_daemons)
|
stopDaemons(cls.btc_daemons)
|
||||||
stopDaemons(cls.ltc_daemons)
|
stopDaemons(cls.ltc_daemons)
|
||||||
|
stopDaemons(cls.pivx_daemons)
|
||||||
|
|
||||||
super(BaseTest, cls).tearDownClass()
|
super(BaseTest, cls).tearDownClass()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue