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