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
 | 
					    - pip install codespell
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
    - flake8 --version
 | 
					    - flake8 --version
 | 
				
			||||||
    - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
 | 
					    - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
 | 
				
			||||||
    - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*mnemonics.py,bin/install_certifi.py
 | 
					    - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_task:
 | 
					test_task:
 | 
				
			||||||
  environment:
 | 
					  environment:
 | 
				
			||||||
 | 
				
			|||||||
@ -52,8 +52,8 @@ jobs:
 | 
				
			|||||||
        - travis_retry pip install codespell==1.15.0
 | 
					        - travis_retry pip install codespell==1.15.0
 | 
				
			||||||
      before_script:
 | 
					      before_script:
 | 
				
			||||||
      script:
 | 
					      script:
 | 
				
			||||||
        - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
 | 
					        - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
 | 
				
			||||||
        - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*mnemonics.py,bin/install_certifi.py
 | 
					        - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py
 | 
				
			||||||
      after_success:
 | 
					      after_success:
 | 
				
			||||||
        - echo "End lint"
 | 
					        - echo "End lint"
 | 
				
			||||||
    - stage: test
 | 
					    - stage: test
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,7 @@ from .interface.btc import BTCInterface
 | 
				
			|||||||
from .interface.ltc import LTCInterface
 | 
					from .interface.ltc import LTCInterface
 | 
				
			||||||
from .interface.nmc import NMCInterface
 | 
					from .interface.nmc import NMCInterface
 | 
				
			||||||
from .interface.xmr import XMRInterface
 | 
					from .interface.xmr import XMRInterface
 | 
				
			||||||
 | 
					from .interface.pivx import PIVXInterface
 | 
				
			||||||
from .interface.passthrough_btc import PassthroughBTCInterface
 | 
					from .interface.passthrough_btc import PassthroughBTCInterface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import __version__
 | 
					from . import __version__
 | 
				
			||||||
@ -174,6 +175,7 @@ def threadPollChainState(swap_client, coin_type):
 | 
				
			|||||||
                with swap_client.mxDB:
 | 
					                with swap_client.mxDB:
 | 
				
			||||||
                    cc['chain_height'] = chain_state['blocks']
 | 
					                    cc['chain_height'] = chain_state['blocks']
 | 
				
			||||||
                    cc['chain_best_block'] = chain_state['bestblockhash']
 | 
					                    cc['chain_best_block'] = chain_state['bestblockhash']
 | 
				
			||||||
 | 
					                    if 'mediantime' in chain_state:
 | 
				
			||||||
                        cc['chain_median_time'] = chain_state['mediantime']
 | 
					                        cc['chain_median_time'] = chain_state['mediantime']
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            swap_client.log.warning('threadPollChainState {}, error: {}'.format(str(coin_type), str(e)))
 | 
					            swap_client.log.warning('threadPollChainState {}, error: {}'.format(str(coin_type), str(e)))
 | 
				
			||||||
@ -380,21 +382,24 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
        session.close()
 | 
					        session.close()
 | 
				
			||||||
        session.remove()
 | 
					        session.remove()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        coin_chainparams = chainparams[coin]
 | 
				
			||||||
 | 
					        default_segwit = coin_chainparams.get('has_segwit', False)
 | 
				
			||||||
 | 
					        default_csv = coin_chainparams.get('has_csv', True)
 | 
				
			||||||
        self.coin_clients[coin] = {
 | 
					        self.coin_clients[coin] = {
 | 
				
			||||||
            'coin': coin,
 | 
					            'coin': coin,
 | 
				
			||||||
            'name': chainparams[coin]['name'],
 | 
					            'name': coin_chainparams['name'],
 | 
				
			||||||
            'connection_type': connection_type,
 | 
					            'connection_type': connection_type,
 | 
				
			||||||
            'bindir': bindir,
 | 
					            'bindir': bindir,
 | 
				
			||||||
            'datadir': datadir,
 | 
					            'datadir': datadir,
 | 
				
			||||||
            'rpchost': chain_client_settings.get('rpchost', '127.0.0.1'),
 | 
					            'rpchost': chain_client_settings.get('rpchost', '127.0.0.1'),
 | 
				
			||||||
            'rpcport': chain_client_settings.get('rpcport', chainparams[coin][self.chain]['rpcport']),
 | 
					            'rpcport': chain_client_settings.get('rpcport', coin_chainparams[self.chain]['rpcport']),
 | 
				
			||||||
            'rpcauth': rpcauth,
 | 
					            'rpcauth': rpcauth,
 | 
				
			||||||
            'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6),
 | 
					            'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6),
 | 
				
			||||||
            'conf_target': chain_client_settings.get('conf_target', 2),
 | 
					            'conf_target': chain_client_settings.get('conf_target', 2),
 | 
				
			||||||
            'watched_outputs': [],
 | 
					            'watched_outputs': [],
 | 
				
			||||||
            'last_height_checked': last_height_checked,
 | 
					            'last_height_checked': last_height_checked,
 | 
				
			||||||
            'use_segwit': chain_client_settings.get('use_segwit', False),
 | 
					            'use_segwit': chain_client_settings.get('use_segwit', default_segwit),
 | 
				
			||||||
            'use_csv': chain_client_settings.get('use_csv', True),
 | 
					            'use_csv': chain_client_settings.get('use_csv', default_csv),
 | 
				
			||||||
            'core_version_group': chain_client_settings.get('core_version_group', 0),
 | 
					            'core_version_group': chain_client_settings.get('core_version_group', 0),
 | 
				
			||||||
            'pid': None,
 | 
					            'pid': None,
 | 
				
			||||||
            'core_version': None,
 | 
					            'core_version': None,
 | 
				
			||||||
@ -482,6 +487,8 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
            chain_client_settings = self.getChainClientSettings(coin)
 | 
					            chain_client_settings = self.getChainClientSettings(coin)
 | 
				
			||||||
            xmr_i.setWalletFilename(chain_client_settings['walletfile'])
 | 
					            xmr_i.setWalletFilename(chain_client_settings['walletfile'])
 | 
				
			||||||
            return xmr_i
 | 
					            return xmr_i
 | 
				
			||||||
 | 
					        elif coin == Coins.PIVX:
 | 
				
			||||||
 | 
					            return PIVXInterface(self.coin_clients[coin], self.chain, self)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise ValueError('Unknown coin type')
 | 
					            raise ValueError('Unknown coin type')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -927,6 +934,8 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
            raise ValueError('Invalid swap type for PART_ANON')
 | 
					            raise ValueError('Invalid swap type for PART_ANON')
 | 
				
			||||||
        if (coin_from == Coins.PART_BLIND or coin_to == Coins.PART_BLIND) and swap_type != SwapTypes.XMR_SWAP:
 | 
					        if (coin_from == Coins.PART_BLIND or coin_to == Coins.PART_BLIND) and swap_type != SwapTypes.XMR_SWAP:
 | 
				
			||||||
            raise ValueError('Invalid swap type for PART_BLIND')
 | 
					            raise ValueError('Invalid swap type for PART_BLIND')
 | 
				
			||||||
 | 
					        if coin_from == Coins.PIVX and swap_type == SwapTypes.XMR_SWAP:
 | 
				
			||||||
 | 
					            raise ValueError('TODO: PIVX -> XMR')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def notify(self, event_type, event_data):
 | 
					    def notify(self, event_type, event_data):
 | 
				
			||||||
        if event_type == NT.OFFER_RECEIVED:
 | 
					        if event_type == NT.OFFER_RECEIVED:
 | 
				
			||||||
@ -959,19 +968,23 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
        ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain')
 | 
					        ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
 | 
					    def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        coin_from_has_csv = self.coin_clients[coin_from]['use_csv']
 | 
				
			||||||
 | 
					        coin_to_has_csv = self.coin_clients[coin_to]['use_csv']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if lock_type == OfferMessage.SEQUENCE_LOCK_TIME:
 | 
					        if lock_type == OfferMessage.SEQUENCE_LOCK_TIME:
 | 
				
			||||||
            ensure(lock_value >= self.min_sequence_lock_seconds and lock_value <= self.max_sequence_lock_seconds, 'Invalid lock_value time')
 | 
					            ensure(lock_value >= self.min_sequence_lock_seconds and lock_value <= self.max_sequence_lock_seconds, 'Invalid lock_value time')
 | 
				
			||||||
            ensure(self.coin_clients[coin_from]['use_csv'] and self.coin_clients[coin_to]['use_csv'], 'Both coins need CSV activated.')
 | 
					            ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.')
 | 
				
			||||||
        elif lock_type == OfferMessage.SEQUENCE_LOCK_BLOCKS:
 | 
					        elif lock_type == OfferMessage.SEQUENCE_LOCK_BLOCKS:
 | 
				
			||||||
            ensure(lock_value >= 5 and lock_value <= 1000, 'Invalid lock_value blocks')
 | 
					            ensure(lock_value >= 5 and lock_value <= 1000, 'Invalid lock_value blocks')
 | 
				
			||||||
            ensure(self.coin_clients[coin_from]['use_csv'] and self.coin_clients[coin_to]['use_csv'], 'Both coins need CSV activated.')
 | 
					            ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.')
 | 
				
			||||||
        elif lock_type == TxLockTypes.ABS_LOCK_TIME:
 | 
					        elif lock_type == TxLockTypes.ABS_LOCK_TIME:
 | 
				
			||||||
            # TODO: range?
 | 
					            # TODO: range?
 | 
				
			||||||
            ensure(not self.coin_clients[coin_from]['use_csv'] or not self.coin_clients[coin_to]['use_csv'], 'Should use CSV.')
 | 
					            ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.')
 | 
				
			||||||
            ensure(lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60, 'Invalid lock_value time')
 | 
					            ensure(lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60, 'Invalid lock_value time')
 | 
				
			||||||
        elif lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
 | 
					        elif lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
 | 
				
			||||||
            # TODO: range?
 | 
					            # TODO: range?
 | 
				
			||||||
            ensure(not self.coin_clients[coin_from]['use_csv'] or not self.coin_clients[coin_to]['use_csv'], 'Should use CSV.')
 | 
					            ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.')
 | 
				
			||||||
            ensure(lock_value >= 10 and lock_value <= 1000, 'Invalid lock_value blocks')
 | 
					            ensure(lock_value >= 10 and lock_value <= 1000, 'Invalid lock_value blocks')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise ValueError('Unknown locktype')
 | 
					            raise ValueError('Unknown locktype')
 | 
				
			||||||
@ -2570,10 +2583,14 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if self.debug:
 | 
					        if self.debug:
 | 
				
			||||||
            # Check fee
 | 
					            # Check fee
 | 
				
			||||||
            if self.coin_clients[coin_type]['connection_type'] == 'rpc':
 | 
					            if ci.get_connection_type() == 'rpc':
 | 
				
			||||||
                redeem_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [redeem_txn])
 | 
					                redeem_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [redeem_txn])
 | 
				
			||||||
 | 
					                if ci.using_segwit():
 | 
				
			||||||
                    self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize'])
 | 
					                    self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize'])
 | 
				
			||||||
                ensure(tx_vsize >= redeem_txjs['vsize'], 'Underpaid fee')
 | 
					                    ensure(tx_vsize >= redeem_txjs['vsize'], 'underpaid fee')
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.log.debug('size paid, actual size %d %d', tx_vsize, redeem_txjs['size'])
 | 
				
			||||||
 | 
					                    ensure(tx_vsize >= redeem_txjs['size'], 'underpaid fee')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            redeem_txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [redeem_txn])
 | 
					            redeem_txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [redeem_txn])
 | 
				
			||||||
            self.log.debug('Have valid redeem txn %s for contract %s tx %s', redeem_txjs['txid'], for_txn_type, prev_txnid)
 | 
					            self.log.debug('Have valid redeem txn %s for contract %s tx %s', redeem_txjs['txid'], for_txn_type, prev_txnid)
 | 
				
			||||||
@ -2670,10 +2687,14 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if self.debug:
 | 
					        if self.debug:
 | 
				
			||||||
            # Check fee
 | 
					            # Check fee
 | 
				
			||||||
            if self.coin_clients[coin_type]['connection_type'] == 'rpc':
 | 
					            if ci.get_connection_type() == 'rpc':
 | 
				
			||||||
                refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn])
 | 
					                refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn])
 | 
				
			||||||
 | 
					                if ci.using_segwit():
 | 
				
			||||||
                    self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, refund_txjs['vsize'])
 | 
					                    self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, refund_txjs['vsize'])
 | 
				
			||||||
                    ensure(tx_vsize >= refund_txjs['vsize'], 'underpaid fee')
 | 
					                    ensure(tx_vsize >= refund_txjs['vsize'], 'underpaid fee')
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.log.debug('size paid, actual size %d %d', tx_vsize, refund_txjs['size'])
 | 
				
			||||||
 | 
					                    ensure(tx_vsize >= refund_txjs['size'], 'underpaid fee')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            refund_txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [refund_txn])
 | 
					            refund_txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [refund_txn])
 | 
				
			||||||
            self.log.debug('Have valid refund txn %s for contract tx %s', refund_txjs['txid'], txjs['txid'])
 | 
					            self.log.debug('Have valid refund txn %s for contract tx %s', refund_txjs['txid'], txjs['txid'])
 | 
				
			||||||
@ -3497,13 +3518,14 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
                    spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
 | 
					                    spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
 | 
				
			||||||
                    self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
 | 
					                    self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            chain_blocks = self.callcoinrpc(coin_type, 'getblockcount')
 | 
					            ci = self.ci(coin_type)
 | 
				
			||||||
 | 
					            chain_blocks = ci.getChainHeight()
 | 
				
			||||||
            last_height_checked = c['last_height_checked']
 | 
					            last_height_checked = c['last_height_checked']
 | 
				
			||||||
            self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
 | 
					            self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
 | 
				
			||||||
            while last_height_checked < chain_blocks:
 | 
					            while last_height_checked < chain_blocks:
 | 
				
			||||||
                block_hash = self.callcoinrpc(coin_type, 'getblockhash', [last_height_checked + 1])
 | 
					                block_hash = self.callcoinrpc(coin_type, 'getblockhash', [last_height_checked + 1])
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    block = self.callcoinrpc(coin_type, 'getblock', [block_hash, 2])
 | 
					                    block = ci.getBlockWithTxns(block_hash)
 | 
				
			||||||
                except Exception as e:
 | 
					                except Exception as e:
 | 
				
			||||||
                    if 'Block not available (pruned data)' in str(e):
 | 
					                    if 'Block not available (pruned data)' in str(e):
 | 
				
			||||||
                        # TODO: Better solution?
 | 
					                        # TODO: Better solution?
 | 
				
			||||||
@ -3511,6 +3533,9 @@ class BasicSwap(BaseApp):
 | 
				
			|||||||
                        self.log.error('Coin %s last_height_checked %d set to pruneheight %d', self.ci(coin_type).coin_name(), last_height_checked, bci['pruneheight'])
 | 
					                        self.log.error('Coin %s last_height_checked %d set to pruneheight %d', self.ci(coin_type).coin_name(), last_height_checked, bci['pruneheight'])
 | 
				
			||||||
                        last_height_checked = bci['pruneheight']
 | 
					                        last_height_checked = bci['pruneheight']
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        self.log.error(f'getblock error {e}')
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for tx in block['tx']:
 | 
					                for tx in block['tx']:
 | 
				
			||||||
                    for i, inp in enumerate(tx['vin']):
 | 
					                    for i, inp in enumerate(tx['vin']):
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,9 @@ class Coins(IntEnum):
 | 
				
			|||||||
    XMR = 6
 | 
					    XMR = 6
 | 
				
			||||||
    PART_BLIND = 7
 | 
					    PART_BLIND = 7
 | 
				
			||||||
    PART_ANON = 8
 | 
					    PART_ANON = 8
 | 
				
			||||||
 | 
					    # ZANO = 9
 | 
				
			||||||
 | 
					    # NDAU = 10
 | 
				
			||||||
 | 
					    PIVX = 11
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
chainparams = {
 | 
					chainparams = {
 | 
				
			||||||
@ -206,10 +209,45 @@ chainparams = {
 | 
				
			|||||||
            'min_amount': 100000,
 | 
					            'min_amount': 100000,
 | 
				
			||||||
            'max_amount': 10000 * XMR_COIN,
 | 
					            'max_amount': 10000 * XMR_COIN,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Coins.PIVX: {
 | 
				
			||||||
 | 
					        'name': 'pivx',
 | 
				
			||||||
 | 
					        'ticker': 'PIVX',
 | 
				
			||||||
 | 
					        'message_magic': 'DarkNet Signed Message:\n',
 | 
				
			||||||
 | 
					        'blocks_target': 60 * 1,
 | 
				
			||||||
 | 
					        'decimal_places': 8,
 | 
				
			||||||
 | 
					        'has_csv': False,
 | 
				
			||||||
 | 
					        'has_segwit': False,
 | 
				
			||||||
 | 
					        'mainnet': {
 | 
				
			||||||
 | 
					            'rpcport': 51473,
 | 
				
			||||||
 | 
					            'pubkey_address': 30,
 | 
				
			||||||
 | 
					            'script_address': 13,
 | 
				
			||||||
 | 
					            'key_prefix': 212,
 | 
				
			||||||
 | 
					            'bip44': 119,
 | 
				
			||||||
 | 
					            'min_amount': 1000,
 | 
				
			||||||
 | 
					            'max_amount': 100000 * COIN,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'testnet': {
 | 
				
			||||||
 | 
					            'rpcport': 51475,
 | 
				
			||||||
 | 
					            'pubkey_address': 139,
 | 
				
			||||||
 | 
					            'script_address': 19,
 | 
				
			||||||
 | 
					            'key_prefix': 239,
 | 
				
			||||||
 | 
					            'bip44': 1,
 | 
				
			||||||
 | 
					            'min_amount': 1000,
 | 
				
			||||||
 | 
					            'max_amount': 100000 * COIN,
 | 
				
			||||||
 | 
					            'name': 'testnet4',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'regtest': {
 | 
				
			||||||
 | 
					            'rpcport': 51477,
 | 
				
			||||||
 | 
					            'pubkey_address': 139,
 | 
				
			||||||
 | 
					            'script_address': 19,
 | 
				
			||||||
 | 
					            'key_prefix': 239,
 | 
				
			||||||
 | 
					            'bip44': 1,
 | 
				
			||||||
 | 
					            'min_amount': 1000,
 | 
				
			||||||
 | 
					            'max_amount': 100000 * COIN,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ticker_map = {}
 | 
					ticker_map = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -36,3 +36,8 @@ NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
 | 
				
			|||||||
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
 | 
					XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
 | 
				
			||||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
 | 
					XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
 | 
				
			||||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
 | 
					XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PIVX_BINDIR = os.path.expanduser(os.getenv('PIVX_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'namecoin')))
 | 
				
			||||||
 | 
					PIVXD = os.getenv('PIVXD', 'pivxd' + bin_suffix)
 | 
				
			||||||
 | 
					PIVX_CLI = os.getenv('PIVX_CLI', 'pivx-cli' + bin_suffix)
 | 
				
			||||||
 | 
					PIVX_TX = os.getenv('PIVX_TX', 'pivx-tx' + bin_suffix)
 | 
				
			||||||
 | 
				
			|||||||
@ -185,9 +185,16 @@ class BTCInterface(CoinInterface):
 | 
				
			|||||||
        self.blocks_confirmed = coin_settings['blocks_confirmed']
 | 
					        self.blocks_confirmed = coin_settings['blocks_confirmed']
 | 
				
			||||||
        self.setConfTarget(coin_settings['conf_target'])
 | 
					        self.setConfTarget(coin_settings['conf_target'])
 | 
				
			||||||
        self._use_segwit = coin_settings['use_segwit']
 | 
					        self._use_segwit = coin_settings['use_segwit']
 | 
				
			||||||
 | 
					        self._connection_type = coin_settings['connection_type']
 | 
				
			||||||
        self._sc = swap_client
 | 
					        self._sc = swap_client
 | 
				
			||||||
        self._log = self._sc.log if self._sc and self._sc.log else logging
 | 
					        self._log = self._sc.log if self._sc and self._sc.log else logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def using_segwit(self):
 | 
				
			||||||
 | 
					        return self._use_segwit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_connection_type(self):
 | 
				
			||||||
 | 
					        return self._connection_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def open_rpc(self, wallet=None):
 | 
					    def open_rpc(self, wallet=None):
 | 
				
			||||||
        return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
 | 
					        return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -285,12 +292,14 @@ class BTCInterface(CoinInterface):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def get_fee_rate(self, conf_target=2):
 | 
					    def get_fee_rate(self, conf_target=2):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return self.rpc_callback('estimatesmartfee', [conf_target])['feerate'], 'estimatesmartfee'
 | 
					            fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate']
 | 
				
			||||||
 | 
					            assert (fee_rate > 0.0), 'Non positive feerate'
 | 
				
			||||||
 | 
					            return fee_rate, 'estimatesmartfee'
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'], 'paytxfee'
 | 
					                fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
 | 
				
			||||||
                assert (fee_rate > 0.0), '0 feerate'
 | 
					                assert (fee_rate > 0.0), 'Non positive feerate'
 | 
				
			||||||
                return fee_rate
 | 
					                return fee_rate, 'paytxfee'
 | 
				
			||||||
            except Exception:
 | 
					            except Exception:
 | 
				
			||||||
                return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
 | 
					                return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1161,6 +1170,9 @@ class BTCInterface(CoinInterface):
 | 
				
			|||||||
        txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
 | 
					        txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
 | 
				
			||||||
        return txn_signed
 | 
					        return txn_signed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getBlockWithTxns(self, block_hash):
 | 
				
			||||||
 | 
					        return self.rpc_callback('getblock', [block_hash, 2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def testBTCInterface():
 | 
					def testBTCInterface():
 | 
				
			||||||
    print('testBTCInterface')
 | 
					    print('testBTCInterface')
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										0
									
								
								basicswap/interface/contrib/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								basicswap/interface/contrib/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										180
									
								
								basicswap/interface/contrib/pivx_test_framework/authproxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								basicswap/interface/contrib/pivx_test_framework/authproxy.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					# Copyright (c) 2011 Jeff Garzik
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2007 Jan-Klaas Kollhof
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This file is part of jsonrpc.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# jsonrpc is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					# it under the terms of the GNU Lesser General Public License as published by
 | 
				
			||||||
 | 
					# the Free Software Foundation; either version 2.1 of the License, or
 | 
				
			||||||
 | 
					# (at your option) any later version.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This software is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					# GNU Lesser General Public License for more details.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You should have received a copy of the GNU Lesser General Public License
 | 
				
			||||||
 | 
					# along with this software; if not, write to the Free Software
 | 
				
			||||||
 | 
					# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
				
			||||||
 | 
					"""HTTP proxy for opening RPC connection to pivxd.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AuthServiceProxy has the following improvements over python-jsonrpc's
 | 
				
			||||||
 | 
					ServiceProxy class:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- HTTP connections persist for the life of the AuthServiceProxy object
 | 
				
			||||||
 | 
					  (if server supports HTTP/1.1)
 | 
				
			||||||
 | 
					- sends protocol 'version', per JSON-RPC 1.1
 | 
				
			||||||
 | 
					- sends proper, incrementing 'id'
 | 
				
			||||||
 | 
					- sends Basic HTTP authentication headers
 | 
				
			||||||
 | 
					- parses all JSON numbers that look like floats as Decimal
 | 
				
			||||||
 | 
					- uses standard Python json lib
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import decimal
 | 
				
			||||||
 | 
					import http.client
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import urllib.parse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HTTP_TIMEOUT = 300
 | 
				
			||||||
 | 
					USER_AGENT = "AuthServiceProxy/0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log = logging.getLogger("BitcoinRPC")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class JSONRPCException(Exception):
 | 
				
			||||||
 | 
					    def __init__(self, rpc_error):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            errmsg = '%(message)s (%(code)i)' % rpc_error
 | 
				
			||||||
 | 
					        except (KeyError, TypeError):
 | 
				
			||||||
 | 
					            errmsg = ''
 | 
				
			||||||
 | 
					        super().__init__(errmsg)
 | 
				
			||||||
 | 
					        self.error = rpc_error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def EncodeDecimal(o):
 | 
				
			||||||
 | 
					    if isinstance(o, decimal.Decimal):
 | 
				
			||||||
 | 
					        return str(o)
 | 
				
			||||||
 | 
					    raise TypeError(repr(o) + " is not JSON serializable")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthServiceProxy():
 | 
				
			||||||
 | 
					    __id_count = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
 | 
				
			||||||
 | 
					    def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
 | 
				
			||||||
 | 
					        self.__service_url = service_url
 | 
				
			||||||
 | 
					        self._service_name = service_name
 | 
				
			||||||
 | 
					        self.ensure_ascii = ensure_ascii  # can be toggled on the fly by tests
 | 
				
			||||||
 | 
					        self.__url = urllib.parse.urlparse(service_url)
 | 
				
			||||||
 | 
					        port = 80 if self.__url.port is None else self.__url.port
 | 
				
			||||||
 | 
					        user = None if self.__url.username is None else self.__url.username.encode('utf8')
 | 
				
			||||||
 | 
					        passwd = None if self.__url.password is None else self.__url.password.encode('utf8')
 | 
				
			||||||
 | 
					        authpair = user + b':' + passwd
 | 
				
			||||||
 | 
					        self.__auth_header = b'Basic ' + base64.b64encode(authpair)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if connection:
 | 
				
			||||||
 | 
					            # Callables re-use the connection of the original proxy
 | 
				
			||||||
 | 
					            self.__conn = connection
 | 
				
			||||||
 | 
					        elif self.__url.scheme == 'https':
 | 
				
			||||||
 | 
					            self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=timeout)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, name):
 | 
				
			||||||
 | 
					        if name.startswith('__') and name.endswith('__'):
 | 
				
			||||||
 | 
					            # Python internal stuff
 | 
				
			||||||
 | 
					            raise AttributeError
 | 
				
			||||||
 | 
					        if self._service_name is not None:
 | 
				
			||||||
 | 
					            name = "%s.%s" % (self._service_name, name)
 | 
				
			||||||
 | 
					        return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _request(self, method, path, postdata):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
 | 
				
			||||||
 | 
					        This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        headers = {'Host': self.__url.hostname,
 | 
				
			||||||
 | 
					                   'User-Agent': USER_AGENT,
 | 
				
			||||||
 | 
					                   'Authorization': self.__auth_header,
 | 
				
			||||||
 | 
					                   'Content-type': 'application/json'}
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.__conn.request(method, path, postdata, headers)
 | 
				
			||||||
 | 
					            return self._get_response()
 | 
				
			||||||
 | 
					        except http.client.BadStatusLine as e:
 | 
				
			||||||
 | 
					            if e.line == "''":  # if connection was closed, try again
 | 
				
			||||||
 | 
					                self.__conn.close()
 | 
				
			||||||
 | 
					                self.__conn.request(method, path, postdata, headers)
 | 
				
			||||||
 | 
					                return self._get_response()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					        except (BrokenPipeError, ConnectionResetError):
 | 
				
			||||||
 | 
					            # Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset
 | 
				
			||||||
 | 
					            # ConnectionResetError happens on FreeBSD with Python 3.4
 | 
				
			||||||
 | 
					            self.__conn.close()
 | 
				
			||||||
 | 
					            self.__conn.request(method, path, postdata, headers)
 | 
				
			||||||
 | 
					            return self._get_response()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_request(self, *args, **argsn):
 | 
				
			||||||
 | 
					        AuthServiceProxy.__id_count += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log.debug("-%s-> %s %s" % (AuthServiceProxy.__id_count, self._service_name,
 | 
				
			||||||
 | 
					                                   json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
 | 
				
			||||||
 | 
					        if args and argsn:
 | 
				
			||||||
 | 
					            raise ValueError('Cannot handle both named and positional arguments')
 | 
				
			||||||
 | 
					        return {'version': '1.1',
 | 
				
			||||||
 | 
					                'method': self._service_name,
 | 
				
			||||||
 | 
					                'params': args or argsn,
 | 
				
			||||||
 | 
					                'id': AuthServiceProxy.__id_count}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __call__(self, *args, **argsn):
 | 
				
			||||||
 | 
					        postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
 | 
				
			||||||
 | 
					        response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
 | 
				
			||||||
 | 
					        if response['error'] is not None:
 | 
				
			||||||
 | 
					            raise JSONRPCException(response['error'])
 | 
				
			||||||
 | 
					        elif 'result' not in response:
 | 
				
			||||||
 | 
					            raise JSONRPCException({
 | 
				
			||||||
 | 
					                'code': -343, 'message': 'missing JSON-RPC result'})
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return response['result']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def batch(self, rpc_call_list):
 | 
				
			||||||
 | 
					        postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
 | 
				
			||||||
 | 
					        log.debug("--> " + postdata)
 | 
				
			||||||
 | 
					        return self._request('POST', self.__url.path, postdata.encode('utf-8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_response(self):
 | 
				
			||||||
 | 
					        req_start_time = time.time()
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            http_response = self.__conn.getresponse()
 | 
				
			||||||
 | 
					        except socket.timeout:
 | 
				
			||||||
 | 
					            raise JSONRPCException({
 | 
				
			||||||
 | 
					                'code': -344,
 | 
				
			||||||
 | 
					                'message': '%r RPC took longer than %f seconds. Consider '
 | 
				
			||||||
 | 
					                           'using larger timeout for calls that take '
 | 
				
			||||||
 | 
					                           'longer to return.' % (self._service_name,
 | 
				
			||||||
 | 
					                                                  self.__conn.timeout)})
 | 
				
			||||||
 | 
					        if http_response is None:
 | 
				
			||||||
 | 
					            raise JSONRPCException({
 | 
				
			||||||
 | 
					                'code': -342, 'message': 'missing HTTP response from server'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        content_type = http_response.getheader('Content-Type')
 | 
				
			||||||
 | 
					        if content_type != 'application/json':
 | 
				
			||||||
 | 
					            raise JSONRPCException({
 | 
				
			||||||
 | 
					                'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        responsedata = http_response.read().decode('utf8')
 | 
				
			||||||
 | 
					        response = json.loads(responsedata, parse_float=decimal.Decimal)
 | 
				
			||||||
 | 
					        elapsed = time.time() - req_start_time
 | 
				
			||||||
 | 
					        if "error" in response and response["error"] is None:
 | 
				
			||||||
 | 
					            log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            log.debug("<-- [%.6f] %s" % (elapsed, responsedata))
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __truediv__(self, relative_uri):
 | 
				
			||||||
 | 
					        return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
 | 
				
			||||||
							
								
								
									
										109
									
								
								basicswap/interface/contrib/pivx_test_framework/coverage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								basicswap/interface/contrib/pivx_test_framework/coverage.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					# Copyright (c) 2015-2017 The Bitcoin Core developers
 | 
				
			||||||
 | 
					# Distributed under the MIT software license, see the accompanying
 | 
				
			||||||
 | 
					# file COPYING or http://www.opensource.org/licenses/mit-license.php.
 | 
				
			||||||
 | 
					"""Utilities for doing coverage analysis on the RPC interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Provides a way to track which RPC commands are exercised during
 | 
				
			||||||
 | 
					testing.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					REFERENCE_FILENAME = 'rpc_interface.txt'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthServiceProxyWrapper():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    An object that wraps AuthServiceProxy to record specific RPC calls.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Kwargs:
 | 
				
			||||||
 | 
					            auth_service_proxy_instance (AuthServiceProxy): the instance
 | 
				
			||||||
 | 
					                being wrapped.
 | 
				
			||||||
 | 
					            coverage_logfile (str): if specified, write each service_name
 | 
				
			||||||
 | 
					                out to a file when called.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.auth_service_proxy_instance = auth_service_proxy_instance
 | 
				
			||||||
 | 
					        self.coverage_logfile = coverage_logfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, name):
 | 
				
			||||||
 | 
					        return_val = getattr(self.auth_service_proxy_instance, name)
 | 
				
			||||||
 | 
					        if not isinstance(return_val, type(self.auth_service_proxy_instance)):
 | 
				
			||||||
 | 
					            # If proxy getattr returned an unwrapped value, do the same here.
 | 
				
			||||||
 | 
					            return return_val
 | 
				
			||||||
 | 
					        return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __call__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Delegates to AuthServiceProxy, then writes the particular RPC method
 | 
				
			||||||
 | 
					        called to a file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self._log_call()
 | 
				
			||||||
 | 
					        return return_val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _log_call(self):
 | 
				
			||||||
 | 
					        rpc_method = self.auth_service_proxy_instance._service_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.coverage_logfile:
 | 
				
			||||||
 | 
					            with open(self.coverage_logfile, 'a+', encoding='utf8') as f:
 | 
				
			||||||
 | 
					                f.write("%s\n" % rpc_method)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __truediv__(self, relative_uri):
 | 
				
			||||||
 | 
					        return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri,
 | 
				
			||||||
 | 
					                                       self.coverage_logfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_request(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self._log_call()
 | 
				
			||||||
 | 
					        return self.auth_service_proxy_instance.get_request(*args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_filename(dirname, n_node):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get a filename unique to the test process ID and node.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This file will contain a list of RPC commands covered.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    pid = str(os.getpid())
 | 
				
			||||||
 | 
					    return os.path.join(
 | 
				
			||||||
 | 
					        dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def write_all_rpc_commands(dirname, node):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Write out a list of all RPC functions available in `pivx-cli` for
 | 
				
			||||||
 | 
					    coverage comparison. This will only happen once per coverage
 | 
				
			||||||
 | 
					    directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        dirname (str): temporary test dir
 | 
				
			||||||
 | 
					        node (AuthServiceProxy): client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        bool. if the RPC interface file was written.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    filename = os.path.join(dirname, REFERENCE_FILENAME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if os.path.isfile(filename):
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    help_output = node.help().split('\n')
 | 
				
			||||||
 | 
					    commands = set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for line in help_output:
 | 
				
			||||||
 | 
					        line = line.strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ignore blanks and headers
 | 
				
			||||||
 | 
					        if line and not line.startswith('='):
 | 
				
			||||||
 | 
					            commands.add("%s\n" % line.split()[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(filename, 'w', encoding='utf8') as f:
 | 
				
			||||||
 | 
					        f.writelines(list(commands))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return True
 | 
				
			||||||
							
								
								
									
										1505
									
								
								basicswap/interface/contrib/pivx_test_framework/messages.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1505
									
								
								basicswap/interface/contrib/pivx_test_framework/messages.py
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										63
									
								
								basicswap/interface/contrib/pivx_test_framework/siphash.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								basicswap/interface/contrib/pivx_test_framework/siphash.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					# Copyright (c) 2016-2017 The Bitcoin Core developers
 | 
				
			||||||
 | 
					# Distributed under the MIT software license, see the accompanying
 | 
				
			||||||
 | 
					# file COPYING or http://www.opensource.org/licenses/mit-license.php.
 | 
				
			||||||
 | 
					"""Specialized SipHash-2-4 implementations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This implements SipHash-2-4 for 256-bit integers.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def rotl64(n, b):
 | 
				
			||||||
 | 
					    return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def siphash_round(v0, v1, v2, v3):
 | 
				
			||||||
 | 
					    v0 = (v0 + v1) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    v1 = rotl64(v1, 13)
 | 
				
			||||||
 | 
					    v1 ^= v0
 | 
				
			||||||
 | 
					    v0 = rotl64(v0, 32)
 | 
				
			||||||
 | 
					    v2 = (v2 + v3) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    v3 = rotl64(v3, 16)
 | 
				
			||||||
 | 
					    v3 ^= v2
 | 
				
			||||||
 | 
					    v0 = (v0 + v3) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    v3 = rotl64(v3, 21)
 | 
				
			||||||
 | 
					    v3 ^= v0
 | 
				
			||||||
 | 
					    v2 = (v2 + v1) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    v1 = rotl64(v1, 17)
 | 
				
			||||||
 | 
					    v1 ^= v2
 | 
				
			||||||
 | 
					    v2 = rotl64(v2, 32)
 | 
				
			||||||
 | 
					    return (v0, v1, v2, v3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def siphash256(k0, k1, h):
 | 
				
			||||||
 | 
					    n0 = h & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    n1 = (h >> 64) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    n2 = (h >> 128) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    n3 = (h >> 192) & ((1 << 64) - 1)
 | 
				
			||||||
 | 
					    v0 = 0x736f6d6570736575 ^ k0
 | 
				
			||||||
 | 
					    v1 = 0x646f72616e646f6d ^ k1
 | 
				
			||||||
 | 
					    v2 = 0x6c7967656e657261 ^ k0
 | 
				
			||||||
 | 
					    v3 = 0x7465646279746573 ^ k1 ^ n0
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0 ^= n0
 | 
				
			||||||
 | 
					    v3 ^= n1
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0 ^= n1
 | 
				
			||||||
 | 
					    v3 ^= n2
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0 ^= n2
 | 
				
			||||||
 | 
					    v3 ^= n3
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0 ^= n3
 | 
				
			||||||
 | 
					    v3 ^= 0x2000000000000000
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0 ^= 0x2000000000000000
 | 
				
			||||||
 | 
					    v2 ^= 0xFF
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
 | 
				
			||||||
 | 
					    return v0 ^ v1 ^ v2 ^ v3
 | 
				
			||||||
							
								
								
									
										625
									
								
								basicswap/interface/contrib/pivx_test_framework/util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										625
									
								
								basicswap/interface/contrib/pivx_test_framework/util.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,625 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					# Copyright (c) 2014-2017 The Bitcoin Core developers
 | 
				
			||||||
 | 
					# Distributed under the MIT software license, see the accompanying
 | 
				
			||||||
 | 
					# file COPYING or http://www.opensource.org/licenses/mit-license.php.
 | 
				
			||||||
 | 
					"""Helpful routines for regression testing."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from base64 import b64encode
 | 
				
			||||||
 | 
					from binascii import hexlify, unhexlify
 | 
				
			||||||
 | 
					from decimal import Decimal, ROUND_DOWN
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					from subprocess import CalledProcessError
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import coverage, messages
 | 
				
			||||||
 | 
					from .authproxy import AuthServiceProxy, JSONRPCException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger("TestFramework.utils")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Assert functions
 | 
				
			||||||
 | 
					##################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_fee_amount(fee, tx_size, fee_per_kB):
 | 
				
			||||||
 | 
					    """Assert the fee was in range"""
 | 
				
			||||||
 | 
					    target_fee = round(tx_size * fee_per_kB / 1000, 8)
 | 
				
			||||||
 | 
					    if fee < target_fee:
 | 
				
			||||||
 | 
					        raise AssertionError("Fee of %s PIV too low! (Should be %s PIV)" % (str(fee), str(target_fee)))
 | 
				
			||||||
 | 
					    # allow the wallet's estimation to be at most 2 bytes off
 | 
				
			||||||
 | 
					    if fee > (tx_size + 20) * fee_per_kB / 1000:
 | 
				
			||||||
 | 
					        raise AssertionError("Fee of %s PIV too high! (Should be %s PIV)" % (str(fee), str(target_fee)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_equal(thing1, thing2, *args):
 | 
				
			||||||
 | 
					    if thing1 != thing2 or any(thing1 != arg for arg in args):
 | 
				
			||||||
 | 
					        raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_true(condition, message = ""):
 | 
				
			||||||
 | 
					    if not condition:
 | 
				
			||||||
 | 
					        raise AssertionError(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_false(condition, message = ""):
 | 
				
			||||||
 | 
					    assert_true(not condition, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_greater_than(thing1, thing2):
 | 
				
			||||||
 | 
					    if thing1 <= thing2:
 | 
				
			||||||
 | 
					        raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_greater_than_or_equal(thing1, thing2):
 | 
				
			||||||
 | 
					    if thing1 < thing2:
 | 
				
			||||||
 | 
					        raise AssertionError("%s < %s" % (str(thing1), str(thing2)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_raises(exc, fun, *args, **kwds):
 | 
				
			||||||
 | 
					    assert_raises_message(exc, None, fun, *args, **kwds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_raises_message(exc, message, fun, *args, **kwds):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        fun(*args, **kwds)
 | 
				
			||||||
 | 
					    except JSONRPCException:
 | 
				
			||||||
 | 
					        raise AssertionError("Use assert_raises_rpc_error() to test RPC failures")
 | 
				
			||||||
 | 
					    except exc as e:
 | 
				
			||||||
 | 
					        if message is not None and message not in e.error['message']:
 | 
				
			||||||
 | 
					            raise AssertionError("Expected substring not found:" + e.error['message'])
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        raise AssertionError("Unexpected exception raised: " + type(e).__name__)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise AssertionError("No exception raised")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_raises_process_error(returncode, output, fun, *args, **kwds):
 | 
				
			||||||
 | 
					    """Execute a process and asserts the process return code and output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError
 | 
				
			||||||
 | 
					    and verifies that the return code and output are as expected. Throws AssertionError if
 | 
				
			||||||
 | 
					    no CalledProcessError was raised or if the return code and output are not as expected.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        returncode (int): the process return code.
 | 
				
			||||||
 | 
					        output (string): [a substring of] the process output.
 | 
				
			||||||
 | 
					        fun (function): the function to call. This should execute a process.
 | 
				
			||||||
 | 
					        args*: positional arguments for the function.
 | 
				
			||||||
 | 
					        kwds**: named arguments for the function.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        fun(*args, **kwds)
 | 
				
			||||||
 | 
					    except CalledProcessError as e:
 | 
				
			||||||
 | 
					        if returncode != e.returncode:
 | 
				
			||||||
 | 
					            raise AssertionError("Unexpected returncode %i" % e.returncode)
 | 
				
			||||||
 | 
					        if output not in e.output:
 | 
				
			||||||
 | 
					            raise AssertionError("Expected substring not found:" + e.output)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise AssertionError("No exception raised")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_raises_rpc_error(code, message, fun, *args, **kwds):
 | 
				
			||||||
 | 
					    """Run an RPC and verify that a specific JSONRPC exception code and message is raised.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
 | 
				
			||||||
 | 
					    and verifies that the error code and message are as expected. Throws AssertionError if
 | 
				
			||||||
 | 
					    no JSONRPCException was raised or if the error code/message are not as expected.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        code (int), optional: the error code returned by the RPC call (defined
 | 
				
			||||||
 | 
					            in src/rpc/protocol.h). Set to None if checking the error code is not required.
 | 
				
			||||||
 | 
					        message (string), optional: [a substring of] the error string returned by the
 | 
				
			||||||
 | 
					            RPC call. Set to None if checking the error string is not required.
 | 
				
			||||||
 | 
					        fun (function): the function to call. This should be the name of an RPC.
 | 
				
			||||||
 | 
					        args*: positional arguments for the function.
 | 
				
			||||||
 | 
					        kwds**: named arguments for the function.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def try_rpc(code, message, fun, *args, **kwds):
 | 
				
			||||||
 | 
					    """Tries to run an rpc command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Test against error code and message if the rpc fails.
 | 
				
			||||||
 | 
					    Returns whether a JSONRPCException was raised."""
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        fun(*args, **kwds)
 | 
				
			||||||
 | 
					    except JSONRPCException as e:
 | 
				
			||||||
 | 
					        # JSONRPCException was thrown as expected. Check the code and message values are correct.
 | 
				
			||||||
 | 
					        if (code is not None) and (code != e.error["code"]):
 | 
				
			||||||
 | 
					            raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
 | 
				
			||||||
 | 
					        if (message is not None) and (message not in e.error['message']):
 | 
				
			||||||
 | 
					            raise AssertionError("Expected substring (%s) not found in: %s" % (message, e.error['message']))
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        raise AssertionError("Unexpected exception raised: " + type(e).__name__)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_is_hex_string(string):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        int(string, 16)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        raise AssertionError(
 | 
				
			||||||
 | 
					            "Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_is_hash_string(string, length=64):
 | 
				
			||||||
 | 
					    if not isinstance(string, str):
 | 
				
			||||||
 | 
					        raise AssertionError("Expected a string, got type %r" % type(string))
 | 
				
			||||||
 | 
					    elif length and len(string) != length:
 | 
				
			||||||
 | 
					        raise AssertionError(
 | 
				
			||||||
 | 
					            "String of length %d expected; got %d" % (length, len(string)))
 | 
				
			||||||
 | 
					    elif not re.match('[abcdef0-9]+$', string):
 | 
				
			||||||
 | 
					        raise AssertionError(
 | 
				
			||||||
 | 
					            "String %r contains invalid characters for a hash." % string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_array_result(object_array, to_match, expected, should_not_find=False):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					        Pass in array of JSON objects, a dictionary with key/value pairs
 | 
				
			||||||
 | 
					        to match against, and another dictionary with expected key/value
 | 
				
			||||||
 | 
					        pairs.
 | 
				
			||||||
 | 
					        If the should_not_find flag is true, to_match should not be found
 | 
				
			||||||
 | 
					        in object_array
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    if should_not_find:
 | 
				
			||||||
 | 
					        assert_equal(expected, {})
 | 
				
			||||||
 | 
					    num_matched = 0
 | 
				
			||||||
 | 
					    for item in object_array:
 | 
				
			||||||
 | 
					        all_match = True
 | 
				
			||||||
 | 
					        for key, value in to_match.items():
 | 
				
			||||||
 | 
					            if item[key] != value:
 | 
				
			||||||
 | 
					                all_match = False
 | 
				
			||||||
 | 
					        if not all_match:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        elif should_not_find:
 | 
				
			||||||
 | 
					            num_matched = num_matched + 1
 | 
				
			||||||
 | 
					        for key, value in expected.items():
 | 
				
			||||||
 | 
					            if item[key] != value:
 | 
				
			||||||
 | 
					                raise AssertionError("%s : expected %s=%s" % (str(item), str(key), str(value)))
 | 
				
			||||||
 | 
					            num_matched = num_matched + 1
 | 
				
			||||||
 | 
					    if num_matched == 0 and not should_not_find:
 | 
				
			||||||
 | 
					        raise AssertionError("No objects matched %s" % (str(to_match)))
 | 
				
			||||||
 | 
					    if num_matched > 0 and should_not_find:
 | 
				
			||||||
 | 
					        raise AssertionError("Objects were found %s" % (str(to_match)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Utility functions
 | 
				
			||||||
 | 
					###################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_json_precision():
 | 
				
			||||||
 | 
					    """Make sure json library being used does not lose precision converting BTC values"""
 | 
				
			||||||
 | 
					    n = Decimal("20000000.00000003")
 | 
				
			||||||
 | 
					    satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8)
 | 
				
			||||||
 | 
					    if satoshis != 2000000000000003:
 | 
				
			||||||
 | 
					        raise RuntimeError("JSON encode/decode loses precision")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def count_bytes(hex_string):
 | 
				
			||||||
 | 
					    return len(bytearray.fromhex(hex_string))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def bytes_to_hex_str(byte_str):
 | 
				
			||||||
 | 
					    return hexlify(byte_str).decode('ascii')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def hash256(byte_str):
 | 
				
			||||||
 | 
					    sha256 = hashlib.sha256()
 | 
				
			||||||
 | 
					    sha256.update(byte_str)
 | 
				
			||||||
 | 
					    sha256d = hashlib.sha256()
 | 
				
			||||||
 | 
					    sha256d.update(sha256.digest())
 | 
				
			||||||
 | 
					    return sha256d.digest()[::-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def hex_str_to_bytes(hex_str):
 | 
				
			||||||
 | 
					    return unhexlify(hex_str.encode('ascii'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def str_to_b64str(string):
 | 
				
			||||||
 | 
					    return b64encode(string.encode('utf-8')).decode('ascii')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def satoshi_round(amount):
 | 
				
			||||||
 | 
					    return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def wait_until(predicate,
 | 
				
			||||||
 | 
					               *,
 | 
				
			||||||
 | 
					               attempts=float('inf'),
 | 
				
			||||||
 | 
					               timeout=float('inf'),
 | 
				
			||||||
 | 
					               lock=None,
 | 
				
			||||||
 | 
					               sendpings=None,
 | 
				
			||||||
 | 
					               mocktime=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if attempts == float('inf') and timeout == float('inf'):
 | 
				
			||||||
 | 
					        timeout = 60
 | 
				
			||||||
 | 
					    attempt = 0
 | 
				
			||||||
 | 
					    timeout += time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while attempt < attempts and time.time() < timeout:
 | 
				
			||||||
 | 
					        if lock:
 | 
				
			||||||
 | 
					            with lock:
 | 
				
			||||||
 | 
					                if predicate():
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if predicate():
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					        attempt += 1
 | 
				
			||||||
 | 
					        time.sleep(0.5)
 | 
				
			||||||
 | 
					        if sendpings is not None:
 | 
				
			||||||
 | 
					            sendpings()
 | 
				
			||||||
 | 
					        if mocktime is not None:
 | 
				
			||||||
 | 
					            mocktime(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Print the cause of the timeout
 | 
				
			||||||
 | 
					    assert_greater_than(attempts, attempt)
 | 
				
			||||||
 | 
					    assert_greater_than(timeout, time.time())
 | 
				
			||||||
 | 
					    raise RuntimeError('Unreachable')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# RPC/P2P connection constants and functions
 | 
				
			||||||
 | 
					############################################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The maximum number of nodes a single test can spawn
 | 
				
			||||||
 | 
					MAX_NODES = 8
 | 
				
			||||||
 | 
					# Don't assign rpc or p2p ports lower than this
 | 
				
			||||||
 | 
					PORT_MIN = 11000
 | 
				
			||||||
 | 
					# The number of ports to "reserve" for p2p and rpc, each
 | 
				
			||||||
 | 
					PORT_RANGE = 5000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PortSeed:
 | 
				
			||||||
 | 
					    # Must be initialized with a unique integer for each process
 | 
				
			||||||
 | 
					    n = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_rpc_proxy(url, node_number, timeout=None, coveragedir=None):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        url (str): URL of the RPC server to call
 | 
				
			||||||
 | 
					        node_number (int): the node number (or id) that this calls to
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Kwargs:
 | 
				
			||||||
 | 
					        timeout (int): HTTP timeout in seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        AuthServiceProxy. convenience object for making RPC calls.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    proxy_kwargs = {}
 | 
				
			||||||
 | 
					    if timeout is not None:
 | 
				
			||||||
 | 
					        proxy_kwargs['timeout'] = timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    proxy = AuthServiceProxy(url, **proxy_kwargs)
 | 
				
			||||||
 | 
					    proxy.url = url  # store URL on proxy for info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    coverage_logfile = coverage.get_filename(
 | 
				
			||||||
 | 
					        coveragedir, node_number) if coveragedir else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def p2p_port(n):
 | 
				
			||||||
 | 
					    assert(n <= MAX_NODES)
 | 
				
			||||||
 | 
					    return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def rpc_port(n):
 | 
				
			||||||
 | 
					    return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def rpc_url(datadir, i, rpchost=None):
 | 
				
			||||||
 | 
					    rpc_u, rpc_p = get_auth_cookie(datadir)
 | 
				
			||||||
 | 
					    host = '127.0.0.1'
 | 
				
			||||||
 | 
					    port = rpc_port(i)
 | 
				
			||||||
 | 
					    if rpchost:
 | 
				
			||||||
 | 
					        parts = rpchost.split(':')
 | 
				
			||||||
 | 
					        if len(parts) == 2:
 | 
				
			||||||
 | 
					            host, port = parts
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            host = rpchost
 | 
				
			||||||
 | 
					    return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Node functions
 | 
				
			||||||
 | 
					################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def initialize_datadir(dirname, n):
 | 
				
			||||||
 | 
					    datadir = get_datadir_path(dirname, n)
 | 
				
			||||||
 | 
					    if not os.path.isdir(datadir):
 | 
				
			||||||
 | 
					        os.makedirs(datadir)
 | 
				
			||||||
 | 
					    with open(os.path.join(datadir, "pivx.conf"), 'w', encoding='utf8') as f:
 | 
				
			||||||
 | 
					        f.write("regtest=1\n")
 | 
				
			||||||
 | 
					        f.write("[regtest]\n")
 | 
				
			||||||
 | 
					        f.write("port=" + str(p2p_port(n)) + "\n")
 | 
				
			||||||
 | 
					        f.write("rpcport=" + str(rpc_port(n)) + "\n")
 | 
				
			||||||
 | 
					        f.write("server=1\n")
 | 
				
			||||||
 | 
					        f.write("keypool=1\n")
 | 
				
			||||||
 | 
					        f.write("discover=0\n")
 | 
				
			||||||
 | 
					        f.write("listenonion=0\n")
 | 
				
			||||||
 | 
					        f.write("spendzeroconfchange=1\n")
 | 
				
			||||||
 | 
					        f.write("printtoconsole=0\n")
 | 
				
			||||||
 | 
					        f.write("natpmp=0\n")
 | 
				
			||||||
 | 
					    return datadir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_datadir_path(dirname, n):
 | 
				
			||||||
 | 
					    return os.path.join(dirname, "node" + str(n))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def append_config(dirname, n, options):
 | 
				
			||||||
 | 
					    datadir = get_datadir_path(dirname, n)
 | 
				
			||||||
 | 
					    with open(os.path.join(datadir, "pivx.conf"), 'a', encoding='utf8') as f:
 | 
				
			||||||
 | 
					        for option in options:
 | 
				
			||||||
 | 
					            f.write(option + "\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_auth_cookie(datadir):
 | 
				
			||||||
 | 
					    user = None
 | 
				
			||||||
 | 
					    password = None
 | 
				
			||||||
 | 
					    if os.path.isfile(os.path.join(datadir, "pivx.conf")):
 | 
				
			||||||
 | 
					        with open(os.path.join(datadir, "pivx.conf"), 'r', encoding='utf8') as f:
 | 
				
			||||||
 | 
					            for line in f:
 | 
				
			||||||
 | 
					                if line.startswith("rpcuser="):
 | 
				
			||||||
 | 
					                    assert user is None  # Ensure that there is only one rpcuser line
 | 
				
			||||||
 | 
					                    user = line.split("=")[1].strip("\n")
 | 
				
			||||||
 | 
					                if line.startswith("rpcpassword="):
 | 
				
			||||||
 | 
					                    assert password is None  # Ensure that there is only one rpcpassword line
 | 
				
			||||||
 | 
					                    password = line.split("=")[1].strip("\n")
 | 
				
			||||||
 | 
					    if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
 | 
				
			||||||
 | 
					        with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="utf8") as f:
 | 
				
			||||||
 | 
					            userpass = f.read()
 | 
				
			||||||
 | 
					            split_userpass = userpass.split(':')
 | 
				
			||||||
 | 
					            user = split_userpass[0]
 | 
				
			||||||
 | 
					            password = split_userpass[1]
 | 
				
			||||||
 | 
					    if user is None or password is None:
 | 
				
			||||||
 | 
					        raise ValueError("No RPC credentials")
 | 
				
			||||||
 | 
					    return user, password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If a cookie file exists in the given datadir, delete it.
 | 
				
			||||||
 | 
					def delete_cookie_file(datadir):
 | 
				
			||||||
 | 
					    if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
 | 
				
			||||||
 | 
					        logger.debug("Deleting leftover cookie file")
 | 
				
			||||||
 | 
					        os.remove(os.path.join(datadir, "regtest", ".cookie"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_bip9_status(node, key):
 | 
				
			||||||
 | 
					    info = node.getblockchaininfo()
 | 
				
			||||||
 | 
					    return info['bip9_softforks'][key]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_node_times(nodes, t):
 | 
				
			||||||
 | 
					    for node in nodes:
 | 
				
			||||||
 | 
					        node.setmocktime(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def disconnect_nodes(from_connection, node_num):
 | 
				
			||||||
 | 
					    for addr in [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']]:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            from_connection.disconnectnode(addr)
 | 
				
			||||||
 | 
					        except JSONRPCException as e:
 | 
				
			||||||
 | 
					            # If this node is disconnected between calculating the peer id
 | 
				
			||||||
 | 
					            # and issuing the disconnect, don't worry about it.
 | 
				
			||||||
 | 
					            # This avoids a race condition if we're mass-disconnecting peers.
 | 
				
			||||||
 | 
					            if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wait to disconnect
 | 
				
			||||||
 | 
					    wait_until(lambda: [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def connect_nodes(from_connection, node_num):
 | 
				
			||||||
 | 
					    ip_port = "127.0.0.1:" + str(p2p_port(node_num))
 | 
				
			||||||
 | 
					    from_connection.addnode(ip_port, "onetry")
 | 
				
			||||||
 | 
					    # poll until version handshake complete to avoid race conditions
 | 
				
			||||||
 | 
					    # with transaction relaying
 | 
				
			||||||
 | 
					    wait_until(lambda:  all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def connect_nodes_clique(nodes):
 | 
				
			||||||
 | 
					    l = len(nodes)
 | 
				
			||||||
 | 
					    for a in range(l):
 | 
				
			||||||
 | 
					        for b in range(a, l):
 | 
				
			||||||
 | 
					            connect_nodes(nodes[a], b)
 | 
				
			||||||
 | 
					            connect_nodes(nodes[b], a)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Transaction/Block functions
 | 
				
			||||||
 | 
					#############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def find_output(node, txid, amount):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return index to output of txid with value amount
 | 
				
			||||||
 | 
					    Raises exception if there is none.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    txdata = node.getrawtransaction(txid, 1)
 | 
				
			||||||
 | 
					    for i in range(len(txdata["vout"])):
 | 
				
			||||||
 | 
					        if txdata["vout"][i]["value"] == amount:
 | 
				
			||||||
 | 
					            return i
 | 
				
			||||||
 | 
					    raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gather_inputs(from_node, amount_needed, confirmations_required=1):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return a random set of unspent txouts that are enough to pay amount_needed
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert(confirmations_required >= 0)
 | 
				
			||||||
 | 
					    utxo = from_node.listunspent(confirmations_required)
 | 
				
			||||||
 | 
					    random.shuffle(utxo)
 | 
				
			||||||
 | 
					    inputs = []
 | 
				
			||||||
 | 
					    total_in = Decimal("0.00000000")
 | 
				
			||||||
 | 
					    while total_in < amount_needed and len(utxo) > 0:
 | 
				
			||||||
 | 
					        t = utxo.pop()
 | 
				
			||||||
 | 
					        total_in += t["amount"]
 | 
				
			||||||
 | 
					        inputs.append({"txid": t["txid"], "vout": t["vout"], "address": t["address"]})
 | 
				
			||||||
 | 
					    if total_in < amount_needed:
 | 
				
			||||||
 | 
					        raise RuntimeError("Insufficient funds: need %d, have %d" % (amount_needed, total_in))
 | 
				
			||||||
 | 
					    return (total_in, inputs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def make_change(from_node, amount_in, amount_out, fee):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Create change output(s), return them
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    outputs = {}
 | 
				
			||||||
 | 
					    amount = amount_out + fee
 | 
				
			||||||
 | 
					    change = amount_in - amount
 | 
				
			||||||
 | 
					    if change > amount * 2:
 | 
				
			||||||
 | 
					        # Create an extra change output to break up big inputs
 | 
				
			||||||
 | 
					        change_address = from_node.getnewaddress()
 | 
				
			||||||
 | 
					        # Split change in two, being careful of rounding:
 | 
				
			||||||
 | 
					        outputs[change_address] = Decimal(change / 2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
 | 
				
			||||||
 | 
					        change = amount_in - amount - outputs[change_address]
 | 
				
			||||||
 | 
					    if change > 0:
 | 
				
			||||||
 | 
					        outputs[from_node.getnewaddress()] = change
 | 
				
			||||||
 | 
					    return outputs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Create a random transaction.
 | 
				
			||||||
 | 
					    Returns (txid, hex-encoded-transaction-data, fee)
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    from_node = random.choice(nodes)
 | 
				
			||||||
 | 
					    to_node = random.choice(nodes)
 | 
				
			||||||
 | 
					    fee = min_fee + fee_increment * random.randint(0, fee_variants)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (total_in, inputs) = gather_inputs(from_node, amount + fee)
 | 
				
			||||||
 | 
					    outputs = make_change(from_node, total_in, amount, fee)
 | 
				
			||||||
 | 
					    outputs[to_node.getnewaddress()] = float(amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rawtx = from_node.createrawtransaction(inputs, outputs)
 | 
				
			||||||
 | 
					    signresult = from_node.signrawtransaction(rawtx)
 | 
				
			||||||
 | 
					    txid = from_node.sendrawtransaction(signresult["hex"], True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (txid, signresult["hex"], fee)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Helper to create at least "count" utxos
 | 
				
			||||||
 | 
					# Pass in a fee that is sufficient for relay and mining new transactions.
 | 
				
			||||||
 | 
					def create_confirmed_utxos(fee, node, count):
 | 
				
			||||||
 | 
					    to_generate = int(0.5 * count) + 101
 | 
				
			||||||
 | 
					    while to_generate > 0:
 | 
				
			||||||
 | 
					        node.generate(min(25, to_generate))
 | 
				
			||||||
 | 
					        to_generate -= 25
 | 
				
			||||||
 | 
					    utxos = node.listunspent()
 | 
				
			||||||
 | 
					    iterations = count - len(utxos)
 | 
				
			||||||
 | 
					    addr1 = node.getnewaddress()
 | 
				
			||||||
 | 
					    addr2 = node.getnewaddress()
 | 
				
			||||||
 | 
					    if iterations <= 0:
 | 
				
			||||||
 | 
					        return utxos
 | 
				
			||||||
 | 
					    for i in range(iterations):
 | 
				
			||||||
 | 
					        t = utxos.pop()
 | 
				
			||||||
 | 
					        inputs = []
 | 
				
			||||||
 | 
					        inputs.append({"txid": t["txid"], "vout": t["vout"]})
 | 
				
			||||||
 | 
					        outputs = {}
 | 
				
			||||||
 | 
					        send_value = t['amount'] - fee
 | 
				
			||||||
 | 
					        outputs[addr1] = float(satoshi_round(send_value / 2))
 | 
				
			||||||
 | 
					        outputs[addr2] = float(satoshi_round(send_value / 2))
 | 
				
			||||||
 | 
					        raw_tx = node.createrawtransaction(inputs, outputs)
 | 
				
			||||||
 | 
					        signed_tx = node.signrawtransaction(raw_tx)["hex"]
 | 
				
			||||||
 | 
					        node.sendrawtransaction(signed_tx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (node.getmempoolinfo()['size'] > 0):
 | 
				
			||||||
 | 
					        node.generate(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    utxos = node.listunspent()
 | 
				
			||||||
 | 
					    assert(len(utxos) >= count)
 | 
				
			||||||
 | 
					    return utxos
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create large OP_RETURN txouts that can be appended to a transaction
 | 
				
			||||||
 | 
					# to make it large (helper for constructing large transactions).
 | 
				
			||||||
 | 
					def gen_return_txouts():
 | 
				
			||||||
 | 
					    # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
 | 
				
			||||||
 | 
					    # So we have big transactions (and therefore can't fit very many into each block)
 | 
				
			||||||
 | 
					    # create one script_pubkey
 | 
				
			||||||
 | 
					    script_pubkey = "6a4d0200"  # OP_RETURN OP_PUSH2 512 bytes
 | 
				
			||||||
 | 
					    for i in range(512):
 | 
				
			||||||
 | 
					        script_pubkey = script_pubkey + "01"
 | 
				
			||||||
 | 
					    # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
 | 
				
			||||||
 | 
					    txouts = "81"
 | 
				
			||||||
 | 
					    for k in range(128):
 | 
				
			||||||
 | 
					        # add txout value
 | 
				
			||||||
 | 
					        txouts = txouts + "0000000000000000"
 | 
				
			||||||
 | 
					        # add length of script_pubkey
 | 
				
			||||||
 | 
					        txouts = txouts + "fd0402"
 | 
				
			||||||
 | 
					        # add script_pubkey
 | 
				
			||||||
 | 
					        txouts = txouts + script_pubkey
 | 
				
			||||||
 | 
					    return txouts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_tx(node, coinbase, to_address, amount):
 | 
				
			||||||
 | 
					    inputs = [{"txid": coinbase, "vout": 0}]
 | 
				
			||||||
 | 
					    outputs = {to_address: amount}
 | 
				
			||||||
 | 
					    rawtx = node.createrawtransaction(inputs, outputs)
 | 
				
			||||||
 | 
					    signresult = node.signrawtransaction(rawtx)
 | 
				
			||||||
 | 
					    assert_equal(signresult["complete"], True)
 | 
				
			||||||
 | 
					    return signresult["hex"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
 | 
				
			||||||
 | 
					# transaction to make it large.  See gen_return_txouts() above.
 | 
				
			||||||
 | 
					def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
 | 
				
			||||||
 | 
					    addr = node.getnewaddress()
 | 
				
			||||||
 | 
					    txids = []
 | 
				
			||||||
 | 
					    for _ in range(num):
 | 
				
			||||||
 | 
					        t = utxos.pop()
 | 
				
			||||||
 | 
					        inputs = [{"txid": t["txid"], "vout": t["vout"]}]
 | 
				
			||||||
 | 
					        outputs = {}
 | 
				
			||||||
 | 
					        change = t['amount'] - fee
 | 
				
			||||||
 | 
					        outputs[addr] = float(satoshi_round(change))
 | 
				
			||||||
 | 
					        rawtx = node.createrawtransaction(inputs, outputs)
 | 
				
			||||||
 | 
					        newtx = rawtx[0:92]
 | 
				
			||||||
 | 
					        newtx = newtx + txouts
 | 
				
			||||||
 | 
					        newtx = newtx + rawtx[94:]
 | 
				
			||||||
 | 
					        signresult = node.signrawtransaction(newtx, None, None, "NONE")
 | 
				
			||||||
 | 
					        txid = node.sendrawtransaction(signresult["hex"], True)
 | 
				
			||||||
 | 
					        txids.append(txid)
 | 
				
			||||||
 | 
					    return txids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mine_large_block(node, utxos=None):
 | 
				
			||||||
 | 
					    # generate a 66k transaction,
 | 
				
			||||||
 | 
					    # and 14 of them is close to the 1MB block limit
 | 
				
			||||||
 | 
					    num = 14
 | 
				
			||||||
 | 
					    txouts = gen_return_txouts()
 | 
				
			||||||
 | 
					    utxos = utxos if utxos is not None else []
 | 
				
			||||||
 | 
					    if len(utxos) < num:
 | 
				
			||||||
 | 
					        utxos.clear()
 | 
				
			||||||
 | 
					        utxos.extend(node.listunspent())
 | 
				
			||||||
 | 
					    fee = 100 * node.getnetworkinfo()["relayfee"]
 | 
				
			||||||
 | 
					    create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
 | 
				
			||||||
 | 
					    node.generate(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def find_vout_for_address(node, txid, addr):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Locate the vout index of the given transaction sending to the
 | 
				
			||||||
 | 
					    given address. Raises runtime error exception if not found.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    tx = node.getrawtransaction(txid, True)
 | 
				
			||||||
 | 
					    for i in range(len(tx["vout"])):
 | 
				
			||||||
 | 
					        if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]):
 | 
				
			||||||
 | 
					            return i
 | 
				
			||||||
 | 
					    raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PIVX specific utils
 | 
				
			||||||
 | 
					DEFAULT_FEE = 0.01
 | 
				
			||||||
 | 
					SPORK_ACTIVATION_TIME = 1563253447
 | 
				
			||||||
 | 
					SPORK_DEACTIVATION_TIME = 4070908800
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def DecimalAmt(x):
 | 
				
			||||||
 | 
					    """Return Decimal from float for equality checks against rpc outputs"""
 | 
				
			||||||
 | 
					    return Decimal("{:0.8f}".format(x))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Find a coinstake/coinbase address on the node, filtering by the number of UTXOs it has.
 | 
				
			||||||
 | 
					# If no filter is provided, returns the coinstake/coinbase address on the node containing
 | 
				
			||||||
 | 
					# the greatest number of spendable UTXOs.
 | 
				
			||||||
 | 
					# The default cached chain has one address per coinbase output.
 | 
				
			||||||
 | 
					def get_coinstake_address(node, expected_utxos=None):
 | 
				
			||||||
 | 
					    addrs = [utxo['address'] for utxo in node.listunspent() if utxo['generated']]
 | 
				
			||||||
 | 
					    assert(len(set(addrs)) > 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if expected_utxos is None:
 | 
				
			||||||
 | 
					        addrs = [(addrs.count(a), a) for a in set(addrs)]
 | 
				
			||||||
 | 
					        return sorted(addrs, reverse=True)[0][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addrs = [a for a in set(addrs) if addrs.count(a) == expected_utxos]
 | 
				
			||||||
 | 
					    assert(len(addrs) > 0)
 | 
				
			||||||
 | 
					    return addrs[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Deterministic masternodes
 | 
				
			||||||
 | 
					def is_coin_locked_by(node, outpoint):
 | 
				
			||||||
 | 
					    return outpoint.to_json() in node.listlockunspent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_collateral_vout(json_tx):
 | 
				
			||||||
 | 
					    funding_txidn = -1
 | 
				
			||||||
 | 
					    for o in json_tx["vout"]:
 | 
				
			||||||
 | 
					        if o["value"] == Decimal('100'):
 | 
				
			||||||
 | 
					            funding_txidn = o["n"]
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					    assert_greater_than(funding_txidn, -1)
 | 
				
			||||||
 | 
					    return funding_txidn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# owner and voting keys are created from controller node.
 | 
				
			||||||
 | 
					# operator keys are created, if operator_keys is None.
 | 
				
			||||||
 | 
					def create_new_dmn(idx, controller, payout_addr, operator_keys):
 | 
				
			||||||
 | 
					    port = p2p_port(idx) if idx <= MAX_NODES else p2p_port(MAX_NODES) + (idx - MAX_NODES)
 | 
				
			||||||
 | 
					    ipport = "127.0.0.1:" + str(port)
 | 
				
			||||||
 | 
					    owner_addr = controller.getnewaddress("mnowner-%d" % idx)
 | 
				
			||||||
 | 
					    voting_addr = controller.getnewaddress("mnvoting-%d" % idx)
 | 
				
			||||||
 | 
					    if operator_keys is None:
 | 
				
			||||||
 | 
					        bls_keypair = controller.generateblskeypair()
 | 
				
			||||||
 | 
					        operator_pk = bls_keypair["public"]
 | 
				
			||||||
 | 
					        operator_sk = bls_keypair["secret"]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        operator_pk = operator_keys[0]
 | 
				
			||||||
 | 
					        operator_sk = operator_keys[1]
 | 
				
			||||||
 | 
					    return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def spend_mn_collateral(spender, dmn):
 | 
				
			||||||
 | 
					    inputs = [dmn.collateral.to_json()]
 | 
				
			||||||
 | 
					    outputs = {spender.getnewaddress(): Decimal('99.99')}
 | 
				
			||||||
 | 
					    sig_res = spender.signrawtransaction(spender.createrawtransaction(inputs, outputs))
 | 
				
			||||||
 | 
					    assert_equal(sig_res['complete'], True)
 | 
				
			||||||
 | 
					    return spender.sendrawtransaction(sig_res['hex'])
 | 
				
			||||||
							
								
								
									
										56
									
								
								basicswap/interface/pivx.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								basicswap/interface/pivx.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copyright (c) 2020 tecnovert
 | 
				
			||||||
 | 
					# Distributed under the MIT software license, see the accompanying
 | 
				
			||||||
 | 
					# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .btc import BTCInterface
 | 
				
			||||||
 | 
					from basicswap.chainparams import Coins
 | 
				
			||||||
 | 
					from .contrib.pivx_test_framework.messages import (
 | 
				
			||||||
 | 
					    CBlock,
 | 
				
			||||||
 | 
					    ToHex,
 | 
				
			||||||
 | 
					    FromHex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PIVXInterface(BTCInterface):
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def coin_type():
 | 
				
			||||||
 | 
					        return Coins.PIVX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def createRawSignedTransaction(self, addr_to, amount):
 | 
				
			||||||
 | 
					        txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fee_rate, fee_src = self.get_fee_rate(self._conf_target)
 | 
				
			||||||
 | 
					        self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        options = {
 | 
				
			||||||
 | 
					            'lockUnspents': True,
 | 
				
			||||||
 | 
					            'feeRate': fee_rate,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
 | 
				
			||||||
 | 
					        txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
 | 
				
			||||||
 | 
					        return txn_signed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getBlockWithTxns(self, block_hash):
 | 
				
			||||||
 | 
					        # TODO: Bypass decoderawtransaction and getblockheader
 | 
				
			||||||
 | 
					        block = self.rpc_callback('getblock', [block_hash, False])
 | 
				
			||||||
 | 
					        block_header = self.rpc_callback('getblockheader', [block_hash])
 | 
				
			||||||
 | 
					        decoded_block = CBlock()
 | 
				
			||||||
 | 
					        decoded_block = FromHex(decoded_block, block)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tx_rv = []
 | 
				
			||||||
 | 
					        for tx in decoded_block.vtx:
 | 
				
			||||||
 | 
					            tx_dec = self.rpc_callback('decoderawtransaction', [ToHex(tx)])
 | 
				
			||||||
 | 
					            tx_rv.append(tx_dec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        block_rv = {
 | 
				
			||||||
 | 
					            'hash': block_hash,
 | 
				
			||||||
 | 
					            'tx': tx_rv,
 | 
				
			||||||
 | 
					            'confirmations': block_header['confirmations'],
 | 
				
			||||||
 | 
					            'height': block_header['height'],
 | 
				
			||||||
 | 
					            'version': block_header['version'],
 | 
				
			||||||
 | 
					            'merkleroot': block_header['merkleroot'],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return block_rv
 | 
				
			||||||
@ -46,6 +46,9 @@ MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.0.0')
 | 
				
			|||||||
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
 | 
					MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
 | 
				
			||||||
XMR_SITE_COMMIT = 'f093c0da2219d94e6bef5f3948ac61b4ecdcb95b'  # Lock hashes.txt to monero version
 | 
					XMR_SITE_COMMIT = 'f093c0da2219d94e6bef5f3948ac61b4ecdcb95b'  # Lock hashes.txt to monero version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PIVX_VERSION = os.getenv('PIVX_VERSION', '5.4.0')
 | 
				
			||||||
 | 
					PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# version, version tag eg. "rc1", signers
 | 
					# version, version tag eg. "rc1", signers
 | 
				
			||||||
known_coins = {
 | 
					known_coins = {
 | 
				
			||||||
    'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
 | 
					    'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
 | 
				
			||||||
@ -53,6 +56,7 @@ known_coins = {
 | 
				
			|||||||
    'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
 | 
					    'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
 | 
				
			||||||
    'namecoin': ('0.18.0', '', ('JeremyRand',)),
 | 
					    'namecoin': ('0.18.0', '', ('JeremyRand',)),
 | 
				
			||||||
    'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
 | 
					    'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
 | 
				
			||||||
 | 
					    'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
expected_key_ids = {
 | 
					expected_key_ids = {
 | 
				
			||||||
@ -62,6 +66,7 @@ expected_key_ids = {
 | 
				
			|||||||
    'JeremyRand': ('2DBE339E29F6294C',),
 | 
					    'JeremyRand': ('2DBE339E29F6294C',),
 | 
				
			||||||
    'binaryfate': ('F0AF4D462A0BDF92',),
 | 
					    'binaryfate': ('F0AF4D462A0BDF92',),
 | 
				
			||||||
    'davidburkett38': ('3620E9D387E55666',),
 | 
					    'davidburkett38': ('3620E9D387E55666',),
 | 
				
			||||||
 | 
					    'fuzzbawls': ('3BDCDA2D87A881D9',),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if platform.system() == 'Darwin':
 | 
					if platform.system() == 'Darwin':
 | 
				
			||||||
@ -114,6 +119,12 @@ BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
 | 
				
			|||||||
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
 | 
					NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
 | 
				
			||||||
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
 | 
					NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PIVX_RPC_HOST = os.getenv('PIVX_RPC_HOST', '127.0.0.1')
 | 
				
			||||||
 | 
					PIVX_RPC_PORT = int(os.getenv('PIVX_RPC_PORT', 51473))
 | 
				
			||||||
 | 
					PIVX_ONION_PORT = int(os.getenv('PIVX_ONION_PORT', 51472))  # nDefaultPort
 | 
				
			||||||
 | 
					PIVX_RPC_USER = os.getenv('PIVX_RPC_USER', '')
 | 
				
			||||||
 | 
					PIVX_RPC_PWD = os.getenv('PIVX_RPC_PWD', '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
 | 
					TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
 | 
				
			||||||
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
 | 
					TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
 | 
				
			||||||
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
 | 
					TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
 | 
				
			||||||
@ -205,6 +216,36 @@ def testOnionLink():
 | 
				
			|||||||
    logger.info('Onion links work.')
 | 
					    logger.info('Onion links work.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downloadPIVXParams(output_dir):
 | 
				
			||||||
 | 
					    # util/fetch-params.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if os.path.exists(output_dir):
 | 
				
			||||||
 | 
					        logger.info(f'Skipping PIVX params download, path exists: {output_dir}')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    os.makedirs(output_dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    source_url = 'https://download.z.cash/downloads/'
 | 
				
			||||||
 | 
					    files = {
 | 
				
			||||||
 | 
					        'sapling-spend.params': '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13',
 | 
				
			||||||
 | 
					        'sapling-output.params': '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4',
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        setConnectionParameters()
 | 
				
			||||||
 | 
					        for k, v in files.items():
 | 
				
			||||||
 | 
					            url = urllib.parse.urljoin(source_url, k)
 | 
				
			||||||
 | 
					            path = os.path.join(output_dir, k)
 | 
				
			||||||
 | 
					            downloadFile(url, path)
 | 
				
			||||||
 | 
					        hasher = hashlib.sha256()
 | 
				
			||||||
 | 
					        with open(path, 'rb') as fp:
 | 
				
			||||||
 | 
					            hasher.update(fp.read())
 | 
				
			||||||
 | 
					        file_hash = hasher.hexdigest()
 | 
				
			||||||
 | 
					        logger.info('%s hash: %s', k, file_hash)
 | 
				
			||||||
 | 
					        assert file_hash == v
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        popConnectionParameters()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def isValidSignature(result):
 | 
					def isValidSignature(result):
 | 
				
			||||||
    if result.valid is False \
 | 
					    if result.valid is False \
 | 
				
			||||||
       and (result.status == 'signature valid' and result.key_status == 'signing key has expired'):
 | 
					       and (result.status == 'signature valid' and result.key_status == 'signing key has expired'):
 | 
				
			||||||
@ -342,6 +383,10 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
 | 
				
			|||||||
            release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
 | 
					            release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
 | 
				
			||||||
            assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
 | 
					            assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
 | 
				
			||||||
            assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
 | 
					            assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
 | 
				
			||||||
 | 
					        elif coin == 'pivx':
 | 
				
			||||||
 | 
					            release_url = 'https://github.com/PIVX-Project/PIVX/releases/download/v{}/{}'.format(version + version_tag, release_filename)
 | 
				
			||||||
 | 
					            assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
 | 
				
			||||||
 | 
					            assert_url = 'https://raw.githubusercontent.com/PIVX-Project/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name.capitalize(), assert_filename)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise ValueError('Unknown coin')
 | 
					            raise ValueError('Unknown coin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -568,6 +613,14 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
 | 
				
			|||||||
                fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
 | 
					                fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
 | 
				
			||||||
        elif coin == 'namecoin':
 | 
					        elif coin == 'namecoin':
 | 
				
			||||||
            fp.write('prune=2000\n')
 | 
					            fp.write('prune=2000\n')
 | 
				
			||||||
 | 
					        elif coin == 'pivx':
 | 
				
			||||||
 | 
					            base_dir = extra_opts.get('data_dir', data_dir)
 | 
				
			||||||
 | 
					            params_dir = os.path.join(base_dir, 'pivx-params')
 | 
				
			||||||
 | 
					            downloadPIVXParams(params_dir)
 | 
				
			||||||
 | 
					            fp.write('prune=4000\n')
 | 
				
			||||||
 | 
					            fp.write(f'paramsdir={params_dir}\n')
 | 
				
			||||||
 | 
					            if PIVX_RPC_USER != '':
 | 
				
			||||||
 | 
					                fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD)))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            logger.warning('Unknown coin %s', coin)
 | 
					            logger.warning('Unknown coin %s', coin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -804,7 +857,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
 | 
				
			|||||||
                swap_client.setCoinRunParams(c)
 | 
					                swap_client.setCoinRunParams(c)
 | 
				
			||||||
                swap_client.createCoinInterface(c)
 | 
					                swap_client.createCoinInterface(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if c in (Coins.PART, Coins.BTC, Coins.LTC):
 | 
					                if c in (Coins.PART, Coins.BTC, Coins.LTC, Coins.PIVX):
 | 
				
			||||||
                    swap_client.waitForDaemonRPC(c, with_wallet=False)
 | 
					                    swap_client.waitForDaemonRPC(c, with_wallet=False)
 | 
				
			||||||
                    # Create wallet if it doesn't exist yet
 | 
					                    # Create wallet if it doesn't exist yet
 | 
				
			||||||
                    wallets = swap_client.callcoinrpc(c, 'listwallets')
 | 
					                    wallets = swap_client.callcoinrpc(c, 'listwallets')
 | 
				
			||||||
@ -1068,6 +1121,21 @@ def main():
 | 
				
			|||||||
            'bindir': os.path.join(bin_dir, 'monero'),
 | 
					            'bindir': os.path.join(bin_dir, 'monero'),
 | 
				
			||||||
            'restore_height': xmr_restore_height,
 | 
					            'restore_height': xmr_restore_height,
 | 
				
			||||||
            'blocks_confirmed': 7,  # TODO: 10?
 | 
					            'blocks_confirmed': 7,  # TODO: 10?
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'pivx': {
 | 
				
			||||||
 | 
					            'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
 | 
				
			||||||
 | 
					            'manage_daemon': True if ('pivx' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False,
 | 
				
			||||||
 | 
					            'rpchost': PIVX_RPC_HOST,
 | 
				
			||||||
 | 
					            'rpcport': PIVX_RPC_PORT + port_offset,
 | 
				
			||||||
 | 
					            'onionport': PIVX_ONION_PORT + port_offset,
 | 
				
			||||||
 | 
					            'datadir': os.getenv('PIVX_DATA_DIR', os.path.join(data_dir, 'pivx')),
 | 
				
			||||||
 | 
					            'bindir': os.path.join(bin_dir, 'pivx'),
 | 
				
			||||||
 | 
					            'use_segwit': False,
 | 
				
			||||||
 | 
					            'use_csv': False,
 | 
				
			||||||
 | 
					            'blocks_confirmed': 1,
 | 
				
			||||||
 | 
					            'conf_target': 2,
 | 
				
			||||||
 | 
					            'core_version_group': 20,
 | 
				
			||||||
 | 
					            'chain_lookups': 'local',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1080,6 +1148,9 @@ def main():
 | 
				
			|||||||
    if BTC_RPC_USER != '':
 | 
					    if BTC_RPC_USER != '':
 | 
				
			||||||
        chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
 | 
					        chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
 | 
				
			||||||
        chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
 | 
					        chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
 | 
				
			||||||
 | 
					    if PIVX_RPC_USER != '':
 | 
				
			||||||
 | 
					        chainclients['pivx']['rpcuser'] = PIVX_RPC_USER
 | 
				
			||||||
 | 
					        chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
 | 
					    chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										589
									
								
								pgp/keys/pivx_fuzzbawls.pgp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										589
									
								
								pgp/keys/pivx_fuzzbawls.pgp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,589 @@
 | 
				
			|||||||
 | 
					-----BEGIN PGP PUBLIC KEY BLOCK-----
 | 
				
			||||||
 | 
					Comment: Hostname:
 | 
				
			||||||
 | 
					Version: Hockeypuck 2.1.0-166-geb2a11b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xsFNBFZSUAgBEADUwy/lGEZozqiX04ny7Ysa5vBvHUpFp41qUwgl5tTyTxTP8BW7
 | 
				
			||||||
 | 
					xxcKLuLJsPp0QlLiBK2KqOKvzkkESw0iu5Ucj+Yk1Lf3fkctqgO7gh5Ma3J4vcky
 | 
				
			||||||
 | 
					07VhzsTdnETt4I8GRPf4iJotRNQjwJ1V0Jes794Zb1Co3COTXFkE8LaqK9eemv7u
 | 
				
			||||||
 | 
					z1oRDyn6QY+PIsg0EE3eQ9DQFuPYK5AiCm/b+jwX6H+GMENokMzImiqyUvpbccI3
 | 
				
			||||||
 | 
					p0hBx9m8cDmNxVmpXxegC0GCRktC+8T69qyx821aSWjNom5XRg8QncOlswjOEB57
 | 
				
			||||||
 | 
					RP3b25y38Nb+ejHbMtyaICyOyCzVKDsAUO1SvMnaB2QHBMCPoa97Qj4lO9tR7yRs
 | 
				
			||||||
 | 
					XHPvtORmnQrAx+xH9l+GffgwOOmm3dn8jgHVkTv86Bt8yjgZzXrnEPq2GxfXZYfa
 | 
				
			||||||
 | 
					aKa4dr2I8elekeybiNjcAvfR5QVzdlMoqwcRGeD3LGAvxO9OOKztm3ED/czDeejt
 | 
				
			||||||
 | 
					oIL6UmSWhcQNP4pPBe8R8JRIycxetGaXc+EuyqGVMriCwplE57uVkM2RHgrsdQm2
 | 
				
			||||||
 | 
					z9fgp5N4wt6pxWxGovycYq7UxMUk8VAGqO9Qiq1GTtg46Np3GXWeA5pEPao93TDQ
 | 
				
			||||||
 | 
					w3dsbOnxPZVJw7dUbfpenPvElldFxNwK5QbnZFCwGFS6fgtN9h+zUGoT/QARAQAB
 | 
				
			||||||
 | 
					zSJGdXp6YmF3bHMgPGZ1enpiYXdsc0BmdXp6YmF3bHMucHc+wsGXBBMBCgBBAhsD
 | 
				
			||||||
 | 
					BQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAhkBFiEEb5k7JQVX57AWreVxO9zaLYeo
 | 
				
			||||||
 | 
					gdkFAmKVX2sFCRBGbWYACgkQO9zaLYeogdkqug/8CWrM8nTTsXqf3Fi81YLt7/0n
 | 
				
			||||||
 | 
					wPf6ar+sKeekTlrB7y3riIZPLt7aRbLdagOJQOleBZgHjKD9fu6bEh2gDE+1nK8r
 | 
				
			||||||
 | 
					205sjFwT3uGZOu8m/VUtb9v3pYxbqTIcjxaD00eLIvzpyOZVzwxU9lv/blp5lgbh
 | 
				
			||||||
 | 
					NSn8fs/Y8NBFB3dCswddFs2sIlZqARTKk5UAjWp4QUf72dOc7VyxsW/w4NmKJ33e
 | 
				
			||||||
 | 
					cHDxbR37CsQQoUbC36UuC1kz+JeTL/vP/WMRAVObUmhHWi6TZKPHbOJ4UdD7X22p
 | 
				
			||||||
 | 
					QfYQD43+cBkhLaxymAtnveveXih4DuSyjW4BWDzaAgcN931qZ/KtthSCkG4bB9lg
 | 
				
			||||||
 | 
					WTN4QLXftpJlT/BrSbU/b8EWng6vtO6lzXk3Zk4KRSroA44hxQc2vDe3qBXLj1Co
 | 
				
			||||||
 | 
					rCLbJF0C61dvaUybwchTpn44lLRd/p5eGniif5gBd1BIVLtXT//I2RnuXxkkUFZy
 | 
				
			||||||
 | 
					gNsRXqKAjK9HBcQzjrgz75GUBkUY4QdlHHeMdvIIfDuO5dt0v5Igsr4C6gsmQ0FE
 | 
				
			||||||
 | 
					BFXq4bLtD9283PqG3NcFaF108XGrNWJPWICJQ/ZynWVldWkVzxlJRAhka8h1Hr2Y
 | 
				
			||||||
 | 
					liRaOIKubntqcx4Jcp3FRL+/KT9ppm0CQpCbTMMyxmtxAD/kp2dEmIlYEPPKunS2
 | 
				
			||||||
 | 
					twuqwM4/5rIpE3tsiyrCwZcEEwEKAEECGwMFCwkIBwMFFQoJCAsFFgIDAQACHgEC
 | 
				
			||||||
 | 
					F4ACGQEWIQRvmTslBVfnsBat5XE73Noth6iB2QUCXSbTNgUJDIJHPgAKCRA73Not
 | 
				
			||||||
 | 
					h6iB2X5pD/98MIAnhLJysXnnsq6nvFeiLFLkeNmDQyPSaC3I6fvBoCHuAaTY6NWS
 | 
				
			||||||
 | 
					3xc3AY24JsSs8PHHsPoJCBY0NWpPYg9ogQXatk8oIHWynEWPGjzjlx8s1EuE+ayY
 | 
				
			||||||
 | 
					HCAOrEDDwgCjWtSOuv4XrSlKPXb3e156/CjuwW1nwULrbpsnDGnRC+IMwRdjOppA
 | 
				
			||||||
 | 
					xtt+aZkxstno4FjeGB3a0beAt9PzoXkyJFqLcB1XUgOOfcGZGsvlxCJ4hXepgYy2
 | 
				
			||||||
 | 
					YqpIIio3phaYcS/pHvh3rPBi+R2V0HLQeUUWVV4DvaDwcWSzp4NFTocR+p2Y75DL
 | 
				
			||||||
 | 
					S3YmeDAPzwy3sar9sdkPysb9I6JuHIpm3ndbhdXgpdIDdii+A3lYMpvR93zYIInY
 | 
				
			||||||
 | 
					j7QZimq1M0TpBoznoIvGm9qFWV1IUSx78g/CDYbK3VOZLRk+IyLiYrHAfVcwhhWZ
 | 
				
			||||||
 | 
					nTnK/jVqZD1hPu9KfYxDi4y08vguLdQLshG4X5kOYpp2wB0nz0ewbLk+RsB0p1S2
 | 
				
			||||||
 | 
					ftZozRWxcJId9b+kYwy3XItz7+FuV16bIeUZdrDVS2uhhUlneRMhKU+1/mO4YKNX
 | 
				
			||||||
 | 
					WRCxWr7njizWkGfE+0y8d9kLTT+0lU0MC4e7ab/RE51OgfgHEDCx3LMXVk+vJCEv
 | 
				
			||||||
 | 
					EJXPbXgvYtTk+79LzT33kGAWNc0HP/t7Tt+odl+F+xM/KIhVZ3zzbsLBgAQTAQoA
 | 
				
			||||||
 | 
					KgIbAwUJB4YfgAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAUCVmokKgIZAQAKCRA7
 | 
				
			||||||
 | 
					3Noth6iB2fCqD/9l4TL/JCjnt6xLoapTk/D+0aJSh2HkPsyzaTn+HxsbMmdc8UJN
 | 
				
			||||||
 | 
					lmGK2Kd8pdTvBhMWzPACED4G47PtMs7c2h8pEms8WP9NBPBUe/rXDDJsXCjaaMjF
 | 
				
			||||||
 | 
					mzBUyLty4+fa0OZdjm+SypNikgeL5+CkDrYd0Qb9U1j1890uWFY611yO0Uer3M0n
 | 
				
			||||||
 | 
					awaRM6nl6iFUJKsf+9reAXCzgdBKGssJIuN6m3UAh5L+7y1SdQFnAQP0/OAKWUPu
 | 
				
			||||||
 | 
					fqNiDOPjiRJuujjyFW6AacYQ01BaxOViDZtwKWjMeybE+r6/h/Mb7C0u7wwQsHK0
 | 
				
			||||||
 | 
					FNolCSP599AxnkmEeARY/knkwLozlSUrUYVaf1ZZhr3B8RXec66ViJv0O471tVaQ
 | 
				
			||||||
 | 
					i6E/EWFNEygzU/LItCi0saJoISY7QYFnKzNGUtLI761h/2uHfmp6c2No8FIpUeVu
 | 
				
			||||||
 | 
					JG4kdGSCMBdl2mbuxynjOVwJF6zoN5qaETVCzZD0XMdo6NZMtJXQteJBoXjbpvWY
 | 
				
			||||||
 | 
					8hNm69xp8vAtWKAnIT1h8BeGyoFzAJaYuEDiI94WgBZJgzkOsWGZaH+qAvanV08+
 | 
				
			||||||
 | 
					0gBl5IG/ob0Ji6iKNeY30cRjgYv22xRkr8h3rm7gc9/cEa1Tae6xK2jQvzjxgd48
 | 
				
			||||||
 | 
					GSGXTI6j5FoOQbP3CEeCRGwib05uRe+W8dO+42t93RWit46khY5GzuEGRcLBlwQT
 | 
				
			||||||
 | 
					AQoAKgIbAwUJB4YfgAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAUCVmokKgIZAQAh
 | 
				
			||||||
 | 
					CRA73Noth6iB2RYhBG+ZOyUFV+ewFq3lcTvc2i2HqIHZ8KoP/2XhMv8kKOe3rEuh
 | 
				
			||||||
 | 
					qlOT8P7RolKHYeQ+zLNpOf4fGxsyZ1zxQk2WYYrYp3yl1O8GExbM8AIQPgbjs+0y
 | 
				
			||||||
 | 
					ztzaHykSazxY/00E8FR7+tcMMmxcKNpoyMWbMFTIu3Lj59rQ5l2Ob5LKk2KSB4vn
 | 
				
			||||||
 | 
					4KQOth3RBv1TWPXz3S5YVjrXXI7RR6vczSdrBpEzqeXqIVQkqx/72t4BcLOB0Eoa
 | 
				
			||||||
 | 
					ywki43qbdQCHkv7vLVJ1AWcBA/T84ApZQ+5+o2IM4+OJEm66OPIVboBpxhDTUFrE
 | 
				
			||||||
 | 
					5WINm3ApaMx7JsT6vr+H8xvsLS7vDBCwcrQU2iUJI/n30DGeSYR4BFj+SeTAujOV
 | 
				
			||||||
 | 
					JStRhVp/VlmGvcHxFd5zrpWIm/Q7jvW1VpCLoT8RYU0TKDNT8si0KLSxomghJjtB
 | 
				
			||||||
 | 
					gWcrM0ZS0sjvrWH/a4d+anpzY2jwUilR5W4kbiR0ZIIwF2XaZu7HKeM5XAkXrOg3
 | 
				
			||||||
 | 
					mpoRNULNkPRcx2jo1ky0ldC14kGheNum9ZjyE2br3Gny8C1YoCchPWHwF4bKgXMA
 | 
				
			||||||
 | 
					lpi4QOIj3haAFkmDOQ6xYZlof6oC9qdXTz7SAGXkgb+hvQmLqIo15jfRxGOBi/bb
 | 
				
			||||||
 | 
					FGSvyHeubuBz39wRrVNp7rEraNC/OPGB3jwZIZdMjqPkWg5Bs/cIR4JEbCJvTm5F
 | 
				
			||||||
 | 
					75bx077ja33dFaK3jqSFjkbO4QZFwsF9BBMBCgAnBQJWaUc0AhsDBQkHhh+ABQsJ
 | 
				
			||||||
 | 
					CAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEDvc2i2HqIHZmGAP/2xKQrHwf/P14j2h
 | 
				
			||||||
 | 
					W8wjJR7JycxJJhtYdGXJ/FnGjArBfvqoMFPh4CwXjFhW07Xr4tcmlpYgOiHnHMTM
 | 
				
			||||||
 | 
					UBrg8oHQuTJoH8iO74y4WC5FPxSIfvTqxgUVb4eqoqFjwDm70bXzl5VGK0qZ1fPT
 | 
				
			||||||
 | 
					DF9KpbCqKbwqiUM9RhzCWNhNQ/UCmnFILGiS7aBZAc82vUK5dN/A/JbyNx0Rnqir
 | 
				
			||||||
 | 
					1fYAhBrccmS3cNDEsTGiNQ6zoQSD/lfErA6TJKE2Zw2kQWtT3CeNiB9DuvbOUahh
 | 
				
			||||||
 | 
					Gk54zO4Crypv7YhIlJ4sjLnustV08y2KDhmw+mm2H7muifUMkYP8TNj9Nu7BmNcH
 | 
				
			||||||
 | 
					q+CzQ87nphiznUY941fc1LMhrNKLOcWpoijCAg10l2kctRCPoEURn0t15h/p7Vpr
 | 
				
			||||||
 | 
					egt0yD3drUm3CpIUBFXue/awQlTjO67ix/45jwAw/xH7YbozSujYM+OqdYWhlZrI
 | 
				
			||||||
 | 
					/5Dc6LG/nI0lax5siw1BAUttahNcsQIAT84xTBdGYPhmtoJRzCedRwP90B9GZQ83
 | 
				
			||||||
 | 
					heXHP80oVAPpVIkCalJrw4YSrXIefTggBKEyXXJXLj23e2R7CrizyPsON6DMZkrk
 | 
				
			||||||
 | 
					QXnl/esMoWrrCToKSDwnW3bSS253kRJO+hiSzSG100SBfvy95A3VRJ6kFStDNgC1
 | 
				
			||||||
 | 
					Dhy+PcCPH+JWZCfSoA4fHaQGEA3ewsGUBBMBCgAnBQJWaUc0AhsDBQkHhh+ABQsJ
 | 
				
			||||||
 | 
					CAcDBRUKCQgLBRYCAwEAAh4BAheAACEJEDvc2i2HqIHZFiEEb5k7JQVX57AWreVx
 | 
				
			||||||
 | 
					O9zaLYeogdmYYA//bEpCsfB/8/XiPaFbzCMlHsnJzEkmG1h0Zcn8WcaMCsF++qgw
 | 
				
			||||||
 | 
					U+HgLBeMWFbTtevi1yaWliA6IeccxMxQGuDygdC5MmgfyI7vjLhYLkU/FIh+9OrG
 | 
				
			||||||
 | 
					BRVvh6qioWPAObvRtfOXlUYrSpnV89MMX0qlsKopvCqJQz1GHMJY2E1D9QKacUgs
 | 
				
			||||||
 | 
					aJLtoFkBzza9Qrl038D8lvI3HRGeqKvV9gCEGtxyZLdw0MSxMaI1DrOhBIP+V8Ss
 | 
				
			||||||
 | 
					DpMkoTZnDaRBa1PcJ42IH0O69s5RqGEaTnjM7gKvKm/tiEiUniyMue6y1XTzLYoO
 | 
				
			||||||
 | 
					GbD6abYfua6J9QyRg/xM2P027sGY1wer4LNDzuemGLOdRj3jV9zUsyGs0os5xami
 | 
				
			||||||
 | 
					KMICDXSXaRy1EI+gRRGfS3XmH+ntWmt6C3TIPd2tSbcKkhQEVe579rBCVOM7ruLH
 | 
				
			||||||
 | 
					/jmPADD/EfthujNK6Ngz46p1haGVmsj/kNzosb+cjSVrHmyLDUEBS21qE1yxAgBP
 | 
				
			||||||
 | 
					zjFMF0Zg+Ga2glHMJ51HA/3QH0ZlDzeF5cc/zShUA+lUiQJqUmvDhhKtch59OCAE
 | 
				
			||||||
 | 
					oTJdclcuPbd7ZHsKuLPI+w43oMxmSuRBeeX96wyhausJOgpIPCdbdtJLbneREk76
 | 
				
			||||||
 | 
					GJLNIbXTRIF+/L3kDdVEnqQVK0M2ALUOHL49wI8f4lZkJ9KgDh8dpAYQDd7NEmZ1
 | 
				
			||||||
 | 
					enpiYXdsc0BwaXZ4Lm9yZ8LBmAQTAQgAQhYhBG+ZOyUFV+ewFq3lcTvc2i2HqIHZ
 | 
				
			||||||
 | 
					BQJilV+mAhsDBQkQRm1mBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRA7
 | 
				
			||||||
 | 
					3Noth6iB2V+yEACG39jQ/Mew6+QCjl7gT1qu5nN2n49jHoIvYUDEjA9vjbJWOT/F
 | 
				
			||||||
 | 
					/H99NVjbQZYOQ4OGHMeQtUJvC+abTZooq1dFXo1UqwNIhaQMZZVBOz5rZ2LDRj89
 | 
				
			||||||
 | 
					qjfp3jMiuOgypKfRzuDZWECMkCEE7eRrJfnKlk87rUjOlz1OCKFCWl4GBR9uVNif
 | 
				
			||||||
 | 
					qR9xGmffOAQ6ZeRYxK2SSIg+5aDvzL9wvGrBNq6gPLI9SdAmOqYRNBgULjNLu5X1
 | 
				
			||||||
 | 
					ZhoFSMJQ+vrR1eYpQjh4B2dtZ95vjNeej7TTxkbQnrtxYH4eWA/d7xRs0wv/jtlf
 | 
				
			||||||
 | 
					qqQUlUmhvcU5W3lYZE7XnADTMrXb9uLRARzl6V7dlxRIBUJ65GO3OOCEi2a7Z4Wx
 | 
				
			||||||
 | 
					xBWVjbogBsnQFMVhpmVzJa+qg6n3x0NkU+heDVRhD+HseLjEIFNp566f5BpP+qC6
 | 
				
			||||||
 | 
					Cy5stEeWebRIdRZlxij62Hx6iT5WkOXHxuCFJaBbrIR611tTvq0HRMBtPvnmiaq3
 | 
				
			||||||
 | 
					EVopEE+NXQw9oih7/qSQMcBxHNNOoWT3uOTx9KjtQ3VYpR7rHaF/o4gttCPSYefv
 | 
				
			||||||
 | 
					IDhhdLTXqDmFgbPIG8esZRePKJZo6bf3US4hovAf8crxpAgTYnZZDFrNiIa7hS5W
 | 
				
			||||||
 | 
					/iGFrrzsyVWEjMnJ4baqGmBAx3QcuXQbF8dhdG0GHBnrVVEgrexQWr9b6M0eRnV6
 | 
				
			||||||
 | 
					emJhd2xzIDxhZG1pbkBmdXp6YmF3bHMucHc+wsGUBBMBCgA+AhsDBQsJCAcDBRUK
 | 
				
			||||||
 | 
					CQgLBRYCAwEAAh4BAheAFiEEb5k7JQVX57AWreVxO9zaLYeogdkFAmKVX3UFCRBG
 | 
				
			||||||
 | 
					bWYACgkQO9zaLYeogdnD9Q/+IWzNlg2AEPFPV1Tp+DedWD75qmRMWPBfqOjdu1Bf
 | 
				
			||||||
 | 
					wSnwaf5v/6RXK5ZrpKYWXMo9dGF03RRjh1WgZrZJE8wSey3HYnqr+g+OEyJ1BgFl
 | 
				
			||||||
 | 
					04zOTVHYy6BH87u4PDMWxcBZpu/jnA04swBUiytTs/fxU5B8NxHSx407MNJyOxwZ
 | 
				
			||||||
 | 
					hvQtsLBX81prDAfSLakkXx3ktMO5YgLt2t6gkwCo0yCvNKaOuFqInxis+JZUrf79
 | 
				
			||||||
 | 
					bTLlz9olKGyI0w+O39AznOkiSxO+0ehvFKFK/6jJ213MqdJx+zmtSrNu3tiO3vjG
 | 
				
			||||||
 | 
					XFnvIvmA/GjRztYy50XKDUoBGLQxDayrmzcOEuzPBs21CngfN1V70saoJtemq2xy
 | 
				
			||||||
 | 
					xh5IZ7l1L+Kd9Fz/FcytmdWfgy6S0YwQhGhoVGey7LUpMWbkWW+c/RShV/jhwtBD
 | 
				
			||||||
 | 
					5pbMxqA9nzGDYeB7PAH9hLIulnm/4aKIdqdZIKPaAm95WEeGnB0DTs7Q80Ie8ygb
 | 
				
			||||||
 | 
					MBlMRLKfPqwL5FVM04Qy/3cBf82lSdCf8IldPNVMvR5YGRHEvOAxhXivsgzjck0j
 | 
				
			||||||
 | 
					Io9zRereyOKrCMCrgYQatjd1PwA2jnkfBwLWe2uZ41SuCZHNf4gJivefxE/WZDiT
 | 
				
			||||||
 | 
					V3CLAQJMuMXnK2ZKGEoOVkXvkrOX7pyaKC1pfJ2ika9IXlVlOPuCmFa/QEu0d5Ve
 | 
				
			||||||
 | 
					yafCwZQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AWIQRvmTslBVfn
 | 
				
			||||||
 | 
					sBat5XE73Noth6iB2QUCXSbTNgUJDIJHPgAKCRA73Noth6iB2atUD/0RiSglgO4u
 | 
				
			||||||
 | 
					PkBWKVqlShhTsCPyCvuptGJjFZLWobtAn7EMnCl82PtBAeD7AvC+7Le1gotvYQyv
 | 
				
			||||||
 | 
					X5TFJIHOAKwIIJMTpwP2zvYcySkquMQjdt5B/joOr8+6aqQ4+MyJE8o2f+QAdWbd
 | 
				
			||||||
 | 
					WzKb7sF7OUT8v4WEtIGMWdSsi3JeioYNQks4KlNed3l1k3zWMswSyBJ96wSWtU78
 | 
				
			||||||
 | 
					ExKtYFpMwuf7bSFFgreq02CUbVridK/NqiQlkKnQM3h0TQT8/nZQTPkZJB2YUvKm
 | 
				
			||||||
 | 
					X9B1B+lW6wwU4JKm/5oqVNh9h36ldxEnrIdUaUnL2B/tBSu2n1yPaGNbxcqAD1WI
 | 
				
			||||||
 | 
					OEDcqvE+uIj0XArij0BAUIeYNNAm5tlpm83zA76e1+4TnH7LZqJcWGsIkXBvyBIa
 | 
				
			||||||
 | 
					6AeqFE5nh3sGQVav4fj1KXcrkZODBsMKiff2C/Syx3wv+5wq1RnJ/unBXvs5zUo4
 | 
				
			||||||
 | 
					2+aFe6yJsukWkpUqcqW4J/iCRpRSCLSIBrPW36I+Xgd0hAcodxp9VaV7q/OV9j4C
 | 
				
			||||||
 | 
					8vOs33ESzzfXUjneMgTVZi/uWYQhgyEaIa2j4vIrso8uKG1+2nlCMNyqSPYTuIat
 | 
				
			||||||
 | 
					9RxFB+Ie3/6r2Zud0OIJxPYgIyaueVSHeIbos/JVulVVoq0E/EBBGoY6+FPrrS+P
 | 
				
			||||||
 | 
					+WAayVMjUs78D8nhBgfStBbGowDopuKCHcLBfQQTAQoAJwIbAwUJB4YfgAULCQgH
 | 
				
			||||||
 | 
					AwUVCgkICwUWAgMBAAIeAQIXgAUCVmokKgAKCRA73Noth6iB2eotD/9u8QBGxjMp
 | 
				
			||||||
 | 
					lLb75WobjJ9gESiLAjv2n7/1Rs63Wu0ywanQX8TWT4IFo4vt9M08m20UnfnFcOwu
 | 
				
			||||||
 | 
					tfrg2e5ZXAJPAHMl6zZTMxB8ch6csgQz7h+hImD4AjJR16pXxYnvU7blCJMpIjAS
 | 
				
			||||||
 | 
					AuVUeaw1ECSlV30oLSCKuFkeLiJ2tvZw8GZenYErVyLlV1avxKZuwoQMGfGSYMAy
 | 
				
			||||||
 | 
					eCabr4y2y/yvQGaXMz+FqrF2Ne7xJEta0GSzJ3B2yfNRYtvwUrkreUJtsEmotx4x
 | 
				
			||||||
 | 
					IZRQylOjkrz6jR9LqWArBm0H09UEY9kqMrhM+yPvj4nYYK9NAtsRkEyY33XuMa65
 | 
				
			||||||
 | 
					BbXpRf6hioA41ORlAlYMUtsUkdkSCauMPa7PIQQQQMoqvMM6I/ICsIXdI3Dtry/n
 | 
				
			||||||
 | 
					eb7p1jbcrYnHCM+fg3muyZiAwQaNsFj2GxboR4sR3adeOescYL5AeCt1tbodw0rL
 | 
				
			||||||
 | 
					5FM0JCKv+SKDppMAuQiZVKimTDKq8qHtKF8AoOL1aj28J8EzKz4kRK5CWLNdt3+5
 | 
				
			||||||
 | 
					eWkkTDoODZmWr0lhapGx5xDaqalhs5bc8Kz0eqpdFUKlEeSz00kvxy42JkVJ1LJ7
 | 
				
			||||||
 | 
					BXCUnteqwGdwDDBWiDAW8gqtRVEj7iN76fCECNSQ//2iePGzSw3pb32Fb4Qngo/8
 | 
				
			||||||
 | 
					WXl3t7XTSioTWUNCsvKQlU5Tn/z0ZxCVeMLBlAQTAQoAJwIbAwUJB4YfgAULCQgH
 | 
				
			||||||
 | 
					AwUVCgkICwUWAgMBAAIeAQIXgAUCVmokKgAhCRA73Noth6iB2RYhBG+ZOyUFV+ew
 | 
				
			||||||
 | 
					Fq3lcTvc2i2HqIHZ6i0P/27xAEbGMymUtvvlahuMn2ARKIsCO/afv/VGzrda7TLB
 | 
				
			||||||
 | 
					qdBfxNZPggWji+30zTybbRSd+cVw7C61+uDZ7llcAk8AcyXrNlMzEHxyHpyyBDPu
 | 
				
			||||||
 | 
					H6EiYPgCMlHXqlfFie9TtuUIkykiMBIC5VR5rDUQJKVXfSgtIIq4WR4uIna29nDw
 | 
				
			||||||
 | 
					Zl6dgStXIuVXVq/Epm7ChAwZ8ZJgwDJ4JpuvjLbL/K9AZpczP4WqsXY17vEkS1rQ
 | 
				
			||||||
 | 
					ZLMncHbJ81Fi2/BSuSt5Qm2wSai3HjEhlFDKU6OSvPqNH0upYCsGbQfT1QRj2Soy
 | 
				
			||||||
 | 
					uEz7I++Pidhgr00C2xGQTJjfde4xrrkFtelF/qGKgDjU5GUCVgxS2xSR2RIJq4w9
 | 
				
			||||||
 | 
					rs8hBBBAyiq8wzoj8gKwhd0jcO2vL+d5vunWNtyticcIz5+Dea7JmIDBBo2wWPYb
 | 
				
			||||||
 | 
					FuhHixHdp1456xxgvkB4K3W1uh3DSsvkUzQkIq/5IoOmkwC5CJlUqKZMMqryoe0o
 | 
				
			||||||
 | 
					XwCg4vVqPbwnwTMrPiRErkJYs123f7l5aSRMOg4NmZavSWFqkbHnENqpqWGzltzw
 | 
				
			||||||
 | 
					rPR6ql0VQqUR5LPTSS/HLjYmRUnUsnsFcJSe16rAZ3AMMFaIMBbyCq1FUSPuI3vp
 | 
				
			||||||
 | 
					8IQI1JD//aJ48bNLDelvfYVvhCeCj/xZeXe3tdNKKhNZQ0Ky8pCVTlOf/PRnEJV4
 | 
				
			||||||
 | 
					wsF9BBMBCgAnBQJWaUdKAhsDBQkHhh+ABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheA
 | 
				
			||||||
 | 
					AAoJEDvc2i2HqIHZx+gQAL+EstzKeGhFeRku38bnrNu6Q11s08gNOLI2SJ/SHPnK
 | 
				
			||||||
 | 
					2gGvXwWzRynFILsETuydCAfA4q5FHoDOwxFAeWhSyjkjR/DafqCDtzU4I5fhaVFB
 | 
				
			||||||
 | 
					+hYMpkgJ2wu+lhqJpMK/yKX1lLyhspR0n6gok3o8Dkcek5OjEuVBkFWAzw0KmvMs
 | 
				
			||||||
 | 
					6yzbuvMX6/GaKO1iIqX34s/gYo2UsSgFEgiVpMeCs/LD+yDHXftYOAB4exgAum1z
 | 
				
			||||||
 | 
					L5yW611NA5at8k/Y+KVd+imZe0KlcOyRDIM1PfKdsHQ2O+m+Zg/YQJuWv3/ODYXP
 | 
				
			||||||
 | 
					UalY2CXRDz8Oo5aWiPengr4g9LxBydtairGnEqwNmxvBE3nDni74IhhnOH5Fzn2a
 | 
				
			||||||
 | 
					OgumLsZh4A69buIk7UOrHNaphBAhLIWGR6FXPa6sW9hKEI6BU+FRD8ayw3k8JqGX
 | 
				
			||||||
 | 
					iRbP+IKXg9k0asKDQpXUpPvDPPaEa/aESeXLigHBD0NTd+JjUFPK+ITJY89Kesiq
 | 
				
			||||||
 | 
					VQEbZiKPr5CxMrjU6C9Lkdsy4Nf23U2eNs2pd7NmvrtO+2iI8sW2kiKjhB5s7WOV
 | 
				
			||||||
 | 
					RTsN8wsJ7nBuKWMSztpzOBPpS18SWtj4etjvucbvbl2VtTKHYVsd0sjmK1Bz+WR5
 | 
				
			||||||
 | 
					721dmVSs6m9rAFjIiC9O5DIXT/STIMqMYpFJgDyPuc6x5G7wnXuNDTuvaQ1qj4q+
 | 
				
			||||||
 | 
					wsGUBBMBCgAnBQJWaUdKAhsDBQkHhh+ABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheA
 | 
				
			||||||
 | 
					ACEJEDvc2i2HqIHZFiEEb5k7JQVX57AWreVxO9zaLYeogdnH6BAAv4Sy3Mp4aEV5
 | 
				
			||||||
 | 
					GS7fxues27pDXWzTyA04sjZIn9Ic+craAa9fBbNHKcUguwRO7J0IB8DirkUegM7D
 | 
				
			||||||
 | 
					EUB5aFLKOSNH8Np+oIO3NTgjl+FpUUH6FgymSAnbC76WGomkwr/IpfWUvKGylHSf
 | 
				
			||||||
 | 
					qCiTejwORx6Tk6MS5UGQVYDPDQqa8yzrLNu68xfr8Zoo7WIipffiz+BijZSxKAUS
 | 
				
			||||||
 | 
					CJWkx4Kz8sP7IMdd+1g4AHh7GAC6bXMvnJbrXU0Dlq3yT9j4pV36KZl7QqVw7JEM
 | 
				
			||||||
 | 
					gzU98p2wdDY76b5mD9hAm5a/f84Nhc9RqVjYJdEPPw6jlpaI96eCviD0vEHJ21qK
 | 
				
			||||||
 | 
					sacSrA2bG8ETecOeLvgiGGc4fkXOfZo6C6YuxmHgDr1u4iTtQ6sc1qmEECEshYZH
 | 
				
			||||||
 | 
					oVc9rqxb2EoQjoFT4VEPxrLDeTwmoZeJFs/4gpeD2TRqwoNCldSk+8M89oRr9oRJ
 | 
				
			||||||
 | 
					5cuKAcEPQ1N34mNQU8r4hMljz0p6yKpVARtmIo+vkLEyuNToL0uR2zLg1/bdTZ42
 | 
				
			||||||
 | 
					zal3s2a+u077aIjyxbaSIqOEHmztY5VFOw3zCwnucG4pYxLO2nM4E+lLXxJa2Ph6
 | 
				
			||||||
 | 
					2O+5xu9uXZW1ModhWx3SyOYrUHP5ZHnvbV2ZVKzqb2sAWMiIL07kMhdP9JMgyoxi
 | 
				
			||||||
 | 
					kUmAPI+5zrHkbvCde40NO69pDWqPir7NH0Z1enpiYXdscyA8ZnV6emJhd2xzQGdt
 | 
				
			||||||
 | 
					YWlsLmNvbT7CwZQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AWIQRv
 | 
				
			||||||
 | 
					mTslBVfnsBat5XE73Noth6iB2QUCYpVfdQUJEEZtZgAKCRA73Noth6iB2RKaEACa
 | 
				
			||||||
 | 
					Y87MlrrGGnGQh5SlTD20KJ5THDHoEcwLUgIzTs8H8i5urXYV/7enRhJrIbOPQJhu
 | 
				
			||||||
 | 
					I6aLxRHKHm7gp1yTN38MqUq8FKt68PXJv9pD6FjHVezz3a3GLTzL9+Q34fFSAjvP
 | 
				
			||||||
 | 
					nJRYXikO9soMoLr/Mfm1I6+HkAFCQzkpFw+FB9+9AVIY0COhT8S3+5bPfVUl6guq
 | 
				
			||||||
 | 
					cWzj9ktfRe4FAvTlu/je7msD1lrz4lLPN7Eo2ReglIKzDywibfO4X7LdZ61dNpup
 | 
				
			||||||
 | 
					QrftvlDif84KmgllxnWAtBFesjYlm74rAgRsXpGxAYrfT6mdbbRo65xld1SROgla
 | 
				
			||||||
 | 
					USaWtyVCPrTRBaB3ADZbnv0EnMt2rguYd2+uFgMUKbPrSlEN15zVHKW+LfYnaDKq
 | 
				
			||||||
 | 
					MpdjTof5xxAI9jZuQS3aHSF2pY1MJw/vCvEZb40+2/pOlh7mB0NqiN1LedzWv6my
 | 
				
			||||||
 | 
					R+l6529CsiMhsXEWJtMS6lQgIXLgqQlKhwNXsr9CPXkR4SnvD8/x+N9HXi5EX749
 | 
				
			||||||
 | 
					xZ5rFrd1TMXocVLveUMn+cee98n2hz2u3KqnoJfTrEJaebesN8HPfZ+4dfClCI00
 | 
				
			||||||
 | 
					ngcRVHYoBoGrNVBHxW7iriQGhYDoWFZopRuAkfzg8d1+3rAO5cgxGCLoW4RDBo1M
 | 
				
			||||||
 | 
					GcwJpT71krVbslwy6+HVc/oAxST+V9ETzXwg1OLFc8LBlAQTAQoAPgIbAwULCQgH
 | 
				
			||||||
 | 
					AwUVCgkICwUWAgMBAAIeAQIXgBYhBG+ZOyUFV+ewFq3lcTvc2i2HqIHZBQJdJtM2
 | 
				
			||||||
 | 
					BQkMgkc+AAoJEDvc2i2HqIHZlHkP/Ra4+PHPcKUlevqmx2G9c+lVHf0SKnofKX8q
 | 
				
			||||||
 | 
					/hLVTk8wkl6g3X7xLv2JOHra/RI5Xdurx8ZZU8uZzQyr1D0PGmQ9wDjViJuPL0QA
 | 
				
			||||||
 | 
					fbfqGfVutcT6yTPSuMJbJu0ipxYRFSJFacWt8gYMHt8WU6PBL045viWvms9KBxBn
 | 
				
			||||||
 | 
					2TGMatk/MMpp7TqrVfs53iSuCcaHRZxBM8dNLm/n9/yYGql6uoWvEjx8sE8w53+h
 | 
				
			||||||
 | 
					67vVlv3Gdlw1HEDWscDKW8MVcmlfH0cVOthode1HEiHOflphp13Of8GvbMtzOgtF
 | 
				
			||||||
 | 
					92qYDd2lbixpgvT5rDqqY8JLWRMGSZRxnTqpBoWrkj4DxnCAXJyHod0LczUCdqVI
 | 
				
			||||||
 | 
					PcxWhH7UUtpEUDnjAcrxWXRVZq4HKm/qeUB4cX0Eo6vAMrrD90dCfn8S986c48mX
 | 
				
			||||||
 | 
					6lKbgyPFLp4DFgEeLby6kfKRR+6WHEqb3M7IOQ/GOiHEqhZtSCc+pB/yeUYe6164
 | 
				
			||||||
 | 
					gDaqXahuqI2niqzgyvMttGvTSIk1xZr+pK3Z9o+lN2+jwDu0QsuPFo1cCV8x1xrC
 | 
				
			||||||
 | 
					ELHCGf/HbbUvkwl9nrZe3Z3MYn65yKuhH0IOal2WR03yuFRIzV/MwikCli8OeUas
 | 
				
			||||||
 | 
					B2uMj+1a3IIA41HBF2DMakeswzBQ2IEkisYYj0A3LTxmmJkY2or/hXH2MMMn+ba1
 | 
				
			||||||
 | 
					FFKs+qoUwsF9BBMBCgAnBQJWaiQKAhsDBQkHhh+ABQsJCAcDBRUKCQgLBRYCAwEA
 | 
				
			||||||
 | 
					Ah4BAheAAAoJEDvc2i2HqIHZ+AkP/RxFY4zszDVjBiliGxy0c9Dvo2GomSteA6iv
 | 
				
			||||||
 | 
					8x/9p/okuzL83hYYqVnnqMlksLB7FW5h13T11swGtvkb1gVNK2wg3r2krn3Uxrlo
 | 
				
			||||||
 | 
					jQBe+rC12/2jH/hqGQ7Kg0xwJ8Mhismz84tMsNJKHfODRfmI8sGN7wwvA6KFY0Kg
 | 
				
			||||||
 | 
					pWAXJK9qrJC0B1c9vhfrgrYgCbVCIHS+0H68P2Z23wOf+lPILAMQvW2VpV/PWfzi
 | 
				
			||||||
 | 
					WBeLcVgVQ/udvLHNbgtTX6zo07plhBDrzR/AzB7CNvkDdQu2YA+S4a28FeaCXCVc
 | 
				
			||||||
 | 
					y+/PTILfVR5w8ZX70WhsODhNPUNyr2MO29S4Mi67CmMqXMhL493EOz/4PZ/33XFM
 | 
				
			||||||
 | 
					KDSgOJ0XwxoM9jHE7PVDRKNFr102oX+LEYRnoSk87a9IP3zFgX+/iSmg54nzv7BC
 | 
				
			||||||
 | 
					yyX8veaNitVw6fcdY5QlWd5UnhmFXODeGw0tSCmxujpk9FDDG69U6GbffwhjLiwh
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					q4XdEkiHOz19KrsFki2P91uuO1LpH76Nw4JCkAc1PcWhiBeBTs7qe1a1KbfvIuMu
 | 
				
			||||||
 | 
					jM5bEiT53GzqCDyasZRFEa8D0xT0Vpj8vC55Y9astCiwFVUepJqVCU/iBtLREEcs
 | 
				
			||||||
 | 
					cQwoJY9Se9L9oAlzyMjkVUIJkwCwHXg0/wAtW5IJ+prRN9CS39pXGSP1pBck8qBg
 | 
				
			||||||
 | 
					cnHOe1QpEh52D8a2/D2lx32oqzonkQfvZAe+Og/H/GtYxc5WEdDptr/Y2ghmZftE
 | 
				
			||||||
 | 
					37x/9n0H4D+dczqFyZpyx47VveIL0biM8n0rkZSzkY6VtWlZci2RaRdgAxnt1rOn
 | 
				
			||||||
 | 
					K3LSbvusfy9KtRkxxMTnGMVQJKSkZ3J2PpXDUvZNFXWxRkgkjl2OCWPQ+tXILfyF
 | 
				
			||||||
 | 
					3uf3hHfoo/xqYN8oOcj1pgHm4GSE7kjr7VHNKWgrJajJcshPIUHjNa+n2aLGk08D
 | 
				
			||||||
 | 
					Op5xjr6fhT7HSxJsedOOqx9z71uOmImXAYkdR2rohBQV2CTmxZ5FiQBACxHGOgFV
 | 
				
			||||||
 | 
					4lY/M3LH1ojjDRD1FDSeShPAxwCamVRyOiNOwXMiRJgj6CslnLks3rTp5GkkLOeT
 | 
				
			||||||
 | 
					URIC1KZM3cjlOARVN13t7CrQRriQovTuagvFaJNsYJA6mquYSRWZ2Mn7s4Udc04E
 | 
				
			||||||
 | 
					N+8lPydB2yaSKMFAG4UDLGqtzJ58mxfuLSILqTPAflKsp6MasQXRdysuA3qKzraU
 | 
				
			||||||
 | 
					J+6kGUPQ+lTzIy7cEdflaqUnuBpYKEY+6aePf8Kbb5EShm3ZHWnFCORVMqI0kg5B
 | 
				
			||||||
 | 
					q5BdNtCEj8apDkUv3VFQ3Y1iacjAJuI56VTimIlwDgZqVGMkHJ+70qi52SY96uEt
 | 
				
			||||||
 | 
					bie52dsiX9iEP315U1gavosd0P3w2TAfLMvf2Namh3mSqBc54NbNzahlIKbkPY9q
 | 
				
			||||||
 | 
					6a0koqb2HTp+0vHqjyG80y5s7kRzJwx+Vx91vof6VBHGzSEKCcZOT6V6ReaarRMj
 | 
				
			||||||
 | 
					oJYT94Ht/wDXrk9R0N7VTLbgyRDsRz9CPb1rJRTV0Zyg4uzMONN5Axy3rU/2ZvQV
 | 
				
			||||||
 | 
					JAgUBXX59p/yKftHvVRjZCNmDTTa+YI5EVGclRydozwM96bcQ3CIWRlbHX1q5vwc
 | 
				
			||||||
 | 
					k1BeTAxcdTSklYkwXncEgluvrTRKx7nHuafIDNOcjirMcKrnCgVzJNjKy8nO6pw2
 | 
				
			||||||
 | 
					PepGVSMFQaRoIjG3ykNxiqtYW49CpxxnJx+NdvpfkaVpSRyR5mf97IffsPwrG8P6
 | 
				
			||||||
 | 
					TEs6zz2+8RgMS3IPpWpq2p8FjHknrxXTR91cxXLZmLqd3BcTkFCPQ5rN8rnKNkUy
 | 
				
			||||||
 | 
					6uVllbcu30pkZdWBU5WuepO7KLEgIhACsxPHFV1tLl3JEPHuwrSiIcbvSpVIBAB9
 | 
				
			||||||
 | 
					uBTjG6Ja1MsaTdE5+RV/uls1pWOlsjLJcYLfwRjp9TWnBAVAaQAueQp5x9aupCVO
 | 
				
			||||||
 | 
					7kuepquWMFcuEHNjYoSnbMjdfarLW4WEjHJq3a2hX5iKfcNHGp38AdPeuKrWcmen
 | 
				
			||||||
 | 
					SoKMdTCmK200ikZyoNZdzK0h+Y/QVf1CRpLgSYwDwBWdN941MWZVCB+magCtNKEQ
 | 
				
			||||||
 | 
					ZJP5VMwLsEUZJrZsdPS0hM03B68itHNRRgoczII7H7PB/tMOTWDeN5k/lIc55Jro
 | 
				
			||||||
 | 
					L++kNthkVSehz82K5qd/JXCAmRxzjtTpy01Mqqs7IrXMuAIIvxxSRW+ex/Kp7axa
 | 
				
			||||||
 | 
					ZgSCi9S7Dr9K09yWq7YyQO7/AMRrZRvqzAx5IQF4pYJh/qpeR0B9K1SI51+dcnsw
 | 
				
			||||||
 | 
					61Tl06SRyIyrY7k4oatqNIdE7QPsbJQ9CP6VoKyqBlhg9qzo4poozHOoK44I7VoG
 | 
				
			||||||
 | 
					x3KGyPm5SRfX39Kl1EtDWNNsWSDjenI7imYyoqWB2hlEU3focVZuLCRR5ig46/Wp
 | 
				
			||||||
 | 
					ck9i1FlZciFwPSs6QOJQa1Yx+7YYwaoSRsX4pwYTVi/pU+yYDJHPavQ9J8u5QROx
 | 
				
			||||||
 | 
					O5eCa8ztVjjlUvLtb0rt9JuVRYnWboRxXWlz0pR+4VGfLUUvvNa50vaCRyf5iufv
 | 
				
			||||||
 | 
					bDaxaPg91NegJGJ4/MU5V1yPxrF1PTyEOFyPWvGoYqUJcsj3MRhYVY80Tzu50m1m
 | 
				
			||||||
 | 
					dnMYinII3rwCfcVS/sFv+fpfyro75HiOWQnHcVQ87/ZavXjOElc8CcJwdjnzdDP3
 | 
				
			||||||
 | 
					SKr3ExduDxV06Sqn5pz9AKr3djDHAzLI2R2NRJStqZFOH75PqauKFA5PNU4Yi+Pm
 | 
				
			||||||
 | 
					wKsNCMoFc8nnmoi7IdiVdpP3hVmGETPHEpHzsBz+dMitoeM5JrpdEs4Axn8ldsY4
 | 
				
			||||||
 | 
					PvVxTk+U0gkldl/7THp1l5C4z/H7tXPapqsUgO1RxWxqbwNn5M1yt75bMQq7R71t
 | 
				
			||||||
 | 
					Vk4qxO+pUZkuDkjBqWBHjbjkVV8kg5jP4VatdxbBOPWuQo0EhuHwscTEt6D+vpWn
 | 
				
			||||||
 | 
					bWwt+Ww8x/iHIT6f41YjmRLFFU4VRyT61WjleWbbGML79a6IpQV3uJ+9KyNG3iy2
 | 
				
			||||||
 | 
					erHrmtOGELsyuWzUem2jEDNbEsSW0Qlc4AHHua83E17ux7GFw6UbsrXEq28PTLnp
 | 
				
			||||||
 | 
					WHMXkYlyc46Grsspmfc30AqrMAcmueJvU1Mu5QtC9ZUkhYLgfMeK2Ll+MY68U3St
 | 
				
			||||||
 | 
					MNzejjIzzWylZXZxTjeVkTaJoryfvmXJ7A/yqeeG4FzLHcoQ0QGE/wBo9K7eGGLS
 | 
				
			||||||
 | 
					7AOVBk25RT0HufQf1rntWuE0eBr28YteSnckfdT6kev8ulcjquc7G0qKhE5bVF+y
 | 
				
			||||||
 | 
					KQwBuZONv9wVnwWMoh8xAu4D+IZP5043SzM1xO26RjzjoK29FC3pmyvyqnY5rshP
 | 
				
			||||||
 | 
					l3Ob2XO7Lc5tmmEm2RDuPSkurZzaSOfvBT+FdPeaUFuFfIHB+XpmtFPDUVxol1cS
 | 
				
			||||||
 | 
					TwrIsW+KHcCWOelXUxcKcU5bsz+qvmdzj1syI1dTgkA5HeojDI7MGO3b2Fd7daFY
 | 
				
			||||||
 | 
					WmmQSwajbySmJd8ZPIb0rENjEys6AHPoaVHGQq7BLDW2MpLdDBGgH40trcDTpxb3
 | 
				
			||||||
 | 
					CF7eTjn+H3qS5kkt8BIwQTjNRPBc3LrJIqrjgZom7uxoo8uqN3+zY1eC+iAmt/7x
 | 
				
			||||||
 | 
					HA9m9D6HvW3FYRXNuGjyU/2uq+x9653Rr2806VsReZbtxKnUCustChAu7B90BXDo
 | 
				
			||||||
 | 
					Ryvsw9PftXJUlOm9T0KEYVFsYOpaH5cbzJwVXOR3riLmZy5CrgV6nqkk+15JI1MP
 | 
				
			||||||
 | 
					lkERjI/z715bdhtxRRiunC1eeNzizCiqctCCJ0RwXOa6fS5LdlX5yB9a5RIljfMh
 | 
				
			||||||
 | 
					zW7pf2cnIYCvQoy1PMZ6p4fvovsgt2bcEOVPoO9a00KTKSpDZFcJpE8MMiMHPofe
 | 
				
			||||||
 | 
					ulS5ZFbDYHpXj5jSdOtzLZn0eXS9pS0eqM/VtNVkfKn2OKwf7LT0P5V15eO5BXzN
 | 
				
			||||||
 | 
					jf3WPBpv2Mf34vzrOniZJWNquEjKVzyQuxGHbj0rMvJS24Z46Vd/s+8B/eSIB9c0
 | 
				
			||||||
 | 
					86NFIoZ7gh/pgV70oyl0PkzMhGFxU67d4Xvim3MBs3wXVl7EU23cNOWbOMelZPTc
 | 
				
			||||||
 | 
					pK5ehjZ5AqgEk4FdKJBp9oICcMBlgPXvWXpTQm9gVYycuMk8d62NSuYIs4iTPrit
 | 
				
			||||||
 | 
					6LTTlcuStoc5fX8khO3JrGkuG3HeDz61qXt4CflCj6CqDSrKPnUGsZ6vcRFFICRg
 | 
				
			||||||
 | 
					4rQiwwycEiqS20b4KHFX4LaRR1yDWN7Ow7Fq0kklTZ1UGt/Tbb58sPxrN021KTlW
 | 
				
			||||||
 | 
					6Fc11ViIVA+Uu/b0FZ16umh1UKaj7zL8MkVvAe7YyMisy7u5LiT5jxWq1szIxYDP
 | 
				
			||||||
 | 
					tWHcgpIQa8693c9ClX51yiFgAKhSOS5laOIFmzkAmondsgdTnFJJmHLZwx9K2grs
 | 
				
			||||||
 | 
					qo9DM1BJYnX94UcHlf8AGt3w1DNI6Txt5aIvzMR1PtWVLp0l3EsoxkttAJ65/wDr
 | 
				
			||||||
 | 
					V2Ng9rptgs02DEAPLj7ynsfp6CliJtR5ImNGHNLmZo3syWUBv7nfK+MxRdWY+uO/
 | 
				
			||||||
 | 
					sO3WuLm06bWr43GoAAknqflQdq6eyil8QT3NxclwyFQu0/dzzVXUNMb+yLpJNzzG
 | 
				
			||||||
 | 
					BiiIOR23GuenKNNa7nY6SmrvZdDEuvCy2q7/ALOJI+zL6VJpzRaekrWyqjMAGyM9
 | 
				
			||||||
 | 
					Oa7iztVmsoIijwSCFWkgfgMuByprktQgggurmP5Ao6Yqo1vaKxk4xg1Kn1J7fSby
 | 
				
			||||||
 | 
					6uI5b5Int2G5QBzzXpDeHtOGiM0NnbhzF8xEYyv0PY/zri9QZzb2DRzsiJGNyqfv
 | 
				
			||||||
 | 
					DA/z+NdSLq0i0CS4F9M8yxHIklxnjtXDjFN2sZYmLT0NtNPtU09H8mNH8kCM7QD0
 | 
				
			||||||
 | 
					5/OvIvEWmKtzNJC7ADP7tTx+ldvHe6dcabFJcCRpGjBGJTjPfIzxXA6g8EU05iIA
 | 
				
			||||||
 | 
					k42g9KvLoPnZlFOK1OftopHk/wBWxO7oa37SyLRM6oZJFUnB6DAqhBLGt3E0hG0n
 | 
				
			||||||
 | 
					rmu706wbUcR21q8lmw+d0dVDnjIJPIAP613163s4q51U4Q5XKR55BJcKkd3s+Z13
 | 
				
			||||||
 | 
					nb0NdCi3VhbRanCixtI+HhB4bjrz0Nbi+EZotItZI7baEUiXMn3jkjpjI/8ArUzV
 | 
				
			||||||
 | 
					JoRpKW5jdHQhlV02cDjvWX1qNVJIdCKd3FkMN/b3kLNbglOktv8Axqe5UemP4a8s
 | 
				
			||||||
 | 
					1FikzhR3PWurmHlzCW3bZKOM9Mj0rmr/AOa5eSQfOxJP1rtwtNRbscmYVHKKT6GK
 | 
				
			||||||
 | 
					VkdvQVoWULcAPtOe5qo7jdyakguVSQAjNdsWrnmnV2EU2ABJuOc5FdTBdE2a7vvD
 | 
				
			||||||
 | 
					hjXF6fergKCR7Vt29wFcx5/1g7+tGOpRq0b9UduX13Sq2ezNdbnnrTvtPvWHJcbG
 | 
				
			||||||
 | 
					xmm/a/evCUT3HVOeMrEn5vypnmgZ3noPWoJLW6RSWdeOeHqgZtzEEZbOMk19I52P
 | 
				
			||||||
 | 
					kLDpGE9xjqM5rWsoUHYZ9SKxYiI5SWZSf5VeS6Az84FZNXNYSUToYZEilh3BSQ4w
 | 
				
			||||||
 | 
					QKy7+WW4LsGCL6tTrOGa4ZJCSsKHO71+lQXr7m2jhB0WiKcVqVKXM7mTPHnIZxn2
 | 
				
			||||||
 | 
					FVwrp0OR7VafHORUHKt8p4rJ6kjo5CDxW5o2bicRk4A5OaTRdKXVI5izhGjCkHbw
 | 
				
			||||||
 | 
					ea0v7GudOjeaNMjbgsvTFZpXlZGkdPe6E0rrHMHT7vQ4rpfDiLcyDDrgEA7jXDyz
 | 
				
			||||||
 | 
					uHCo2AQCRXR6FcmJd474z+FFXDrq9BVa7lB8m56t/YKSWwwy9OK4nWNHaOd8FTj3
 | 
				
			||||||
 | 
					q/F41nixBEgYYxyKwtb1q58zzJbc7WHVa8um6UKjjK5hhI4xO7SIBZiNScgufXtV
 | 
				
			||||||
 | 
					SWxkkk+8hHpuqG3123mbYz+W3o3FaMd1HJyNje4NetRp0WvdZ2Va9dO00Rx23yOZ
 | 
				
			||||||
 | 
					WBwvyRg4yfc9h/OoYorslDdHeEG1QBnAFaXnALxVO8v/ACIWYN81W8NSWrIWMqrY
 | 
				
			||||||
 | 
					uafrEmlC4UhAshDKzeorR07XtOne5F8HLSLtRwzEKeeCoPPf6V5trGrNKWRWOfaq
 | 
				
			||||||
 | 
					On6jJFcBnbgH9awlhKThJNb9epUsXVnuz1jxB4s0+1kb+w9CvJp9pC3DSBQvI+7k
 | 
				
			||||||
 | 
					semeOOtcFJqF9cNNLPYXUTSAcFc/U/StqxvROrZCk4q8GQY+UD6VdHL6MY+5chYy
 | 
				
			||||||
 | 
					pHRlbVLi7ksdP+xbXjaMLIxHC4A/+vWrd6drlp4Ulv57yzjtJoZFXFqGZuCNu4nI
 | 
				
			||||||
 | 
					zg89sUyAK0gwxH4101nCssAjll3x4+6/zAfnWVXBba7eRniMxe7Rz+j6LrWp+HYj
 | 
				
			||||||
 | 
					HrPlJBYrN5SwL8qtyoLHO4nB57VwcouYb6cS3TTL0Bbg/lXr9wsNrZPDAFSLbs2o
 | 
				
			||||||
 | 
					MDbkcfpXB31jp7yv+4IYkk4c/wBajD0VTb5mvkFLFupsjnIrpGuUJPyg9K9d0XVJ
 | 
				
			||||||
 | 
					7ZI2+0QxxMMRIse4kDB+Yk+tebLpenx8qjA9eoOK0dLitWukSeS7kZjtjImKCNug
 | 
				
			||||||
 | 
					4B6ZwazxWGdWF+x6FPExa5JHoqa75tgqrcPGRKyLtQbQQxBz/hXJePtRjhu7NnYS
 | 
				
			||||||
 | 
					SvbBWZeMkE5OO3WsmxtrU2V/DMqyTi/lijZZz8mGy2eeQQcA96d4kihS1sWiaF4r
 | 
				
			||||||
 | 
					aMQuTKrNkkY4zzxmuPDYfkmdEXHl5o6HNHVdzfJA7fjWdqCzTsX2FM+taJvoi2Fl
 | 
				
			||||||
 | 
					iyOykcVm314WyAeK9VQ5Thr1OZLUyWtG3fO+Kmjit0IU5J9RVeSXJ5NRh3ZhtU4r
 | 
				
			||||||
 | 
					eMkcxv2lsrYKTFDnjIrcjtp1QPuVlHccmuUt7rY6qxwa6Sxu8xMA2Qoz1xmumLi0
 | 
				
			||||||
 | 
					4sE7NM1JbHyz5kj/ACkbuF+b8qg22/8Aem/75pr6qL2wLhDGV+Qgndms/wA1favN
 | 
				
			||||||
 | 
					VBa8yPSniUrcjMW+lPADZ71kqAzd8k1tPaQBH2ksfUmsyJcTHHQGu+Sd9TyC5bQI
 | 
				
			||||||
 | 
					oGFUk+1b1pYxpEJZghOMqgHT3rGhIEiknjNaV1fNJ8sZwv8AOtYcqQXLctwm4LkF
 | 
				
			||||||
 | 
					vQdqwtRlBZgvapVcx/N3qnIhZy78DvXPXqO9io7EO1QgMgyx6DPaozLGH2iJWJ7m
 | 
				
			||||||
 | 
					nSE8k1BGC0pbHC1zcxR1Ph2Ty4bn5VUkjha2Zr1jZTAsRmNutYGg4Eb5PNXtSWKS
 | 
				
			||||||
 | 
					xmDruAQkD3AzWP27nRtGxWtr6FWGFVj6nmtf7crQ7wFGB2GK4uIYG4OV/Wtuzimk
 | 
				
			||||||
 | 
					0x2DbxnOQK6qk24NIypxi5pM7jwxDa3b5lYBj611+paFavp+eG46149p2oXULAxj
 | 
				
			||||||
 | 
					aQeSTjFdZD4omFv5cs5btXzdbLsVVnzU9jrr2htIytd8PQbm2HDdVI4rmIbTUoLp
 | 
				
			||||||
 | 
					Y4C5bPU/dArrLzWUl64qCHUY2z8oNepg8FWpr97L7v8AMzjjJRjytX9SaFbhoVTd
 | 
				
			||||||
 | 
					vfHLKMCmSaQ1y2Zn4/ug8UNqJ/h6ULqGST0r2YwVlocLk2yaHSYYFxGUQ+yD+dV7
 | 
				
			||||||
 | 
					3QIbs7nSMyDo6ja36cGmtfMzZ3GpUv8A5Pmauh2tawtShDpt1p8pKsXT0Iwa1I7j
 | 
				
			||||||
 | 
					KEvxjPXtTF1FenUe9L5sMpLD5W7leK5mlHbQZVfX4IZNqEuwrU07xJezsscMWNxw
 | 
				
			||||||
 | 
					KwptJsZboyIxVyctjoa6/wAPWlqksYUpuHTca8fMa1eEW0rry2OmSw/s+ZK78y9c
 | 
				
			||||||
 | 
					aZqEtmZpJGjJGeK8z15Lq2nfMr+/Ne06rqMcGmtG2NwGOteO69N58zY6GvDwOKqT
 | 
				
			||||||
 | 
					m3M1y1ub1RhWd9PHcqTIxBPc10FxOzWruD1xXLYKSHHY10djA9/BHAG27iMt/dHq
 | 
				
			||||||
 | 
					a+kws25cp14+ilFTRFFbXUsJuI7WR4s4LqmRkc1DJvjJ3Ky+xXFdlc38Gn2SWtsw
 | 
				
			||||||
 | 
					VI12rzyD6/jXO3etCViJNpz7V6c+VaJnj2OfnYtLGycMcjIOKvXdlGFLNIS3eo5p
 | 
				
			||||||
 | 
					YJCGAUMORkf4VDNdvIMPgj/YOP0rmnZsuOiKUixpwgz9aam4nJNOIic/6xgfQjpS
 | 
				
			||||||
 | 
					+UQMqwIHXFSmugFiByMggEd8jmtNB5SM8R/dsMEGsmB1ZwBWnkpZyehWtoMT2G2s
 | 
				
			||||||
 | 
					pW1lj+lN3GoUcLJsLAbl4Gak2+4/Oq5Rc7M6WdlLHJ5qpG8pJ24A9621tLbGPLz9
 | 
				
			||||||
 | 
					TmlEMKDiNPyo5GQUbPeZcucjHFXWbvmmOUWUYVVwpPFRNcDsMn1NNaATecq/fyAf
 | 
				
			||||||
 | 
					WpSYpVAjYFVH41mO5Y5JzSJIUcEfiawq01N3uNOxckt85wKj+y+XFkjk81cVop4g
 | 
				
			||||||
 | 
					6AgjggGoZy6RkiQFR2Yc1zToTjr0NI6staOyrbkgfeY1euSzxsgGSymqmkRebYqV
 | 
				
			||||||
 | 
					HQn+dWLlnhlKucYHFOlFTla5tKVkR2WkwxgS3R3KP4c4Gf6mtqGJZFzjYnQJ0/Ss
 | 
				
			||||||
 | 
					22zKwLcgdq1gwRRXfyKOxzmff6buJeI7GA6isF769s5NkpJXpXRXExZuDVCdFlUq
 | 
				
			||||||
 | 
					6hgf0qvQDJOoCU9dvtTkvgrcPiqdzAiTOkRLIP0quU9KVwsbq6gp480VbhnD4VZU
 | 
				
			||||||
 | 
					JPvXLYIPNOBYEMpz7inzisdkEbb1Un61XkkZAVO3P1rAXUJhCU3E+9VTJI5zuP50
 | 
				
			||||||
 | 
					3MVjoheEcNg/jTXv2IwOK54uwPLHP1o3yHnJqW7jsbyXrBx8351fTV/IUEz7D7Vy
 | 
				
			||||||
 | 
					m+Q8ZP4VbttOmuCGbKJ79TSt5BY6N/Ecko8oyPIOwpv2E3gGZDG7D7rL/hS6fZQQ
 | 
				
			||||||
 | 
					AKq5PcnvWo6oy7cY4yMcEVzVMHQm72s/I2pV50vhZzM2gXIfiWI4PPatOGFrGAMr
 | 
				
			||||||
 | 
					rnZgnNWxqbQ4S4QSp0Dkcj60rzQSD5Cv404Yb2bNK2LqVUlLoc3e3ZkbJnz/ALi/
 | 
				
			||||||
 | 
					41mTYHJJYetbGo6cOZIBg907VkYJyhBH1pVE09TAqOxUgqcoeRUiuSAc0xoirGMj
 | 
				
			||||||
 | 
					jqKdEhIwfWsXKwDZCRMD61PHIfvDv1FOkti0YOOlOigOMYpc6AABHIGHAauo0S3h
 | 
				
			||||||
 | 
					lQy38Ae1VSSHyATVbTNNVIVllhVyDuG4ZA9KfrmpSCw8rO0vwBQ5tq0TWMOrOc1C
 | 
				
			||||||
 | 
					WGa7lkhQQxljsVf4RniqeT/z2FPljLYO44qLyf8AaNdKvY55bm6H96GbjrTZLaFU
 | 
				
			||||||
 | 
					JRpD9TWfP8oyDn61u20STy/6wkNnIxio6rI7tzkAVZRmbiRhnsRUp3GNwc0vAFSF
 | 
				
			||||||
 | 
					SO1NKEnpRYRb09AySsxIH3QKS9bbCB6tRbh40ZWQ4bkZqK/JGwHuN1TVdqZcNze8
 | 
				
			||||||
 | 
					NlP7PVnBP7w1d1aJZ4QYxh06HHaqHhoB9L6/8tGP8q0r5xtWIdX6/wC7XnU9aiSO
 | 
				
			||||||
 | 
					uUVyXZWsYykecdBUs0uBjNPDeXDgdDVRzubNeo9WcoO3y9azdQuvLj2If3jdT6Cp
 | 
				
			||||||
 | 
					727S2hLN34HvWE06SuWLcn1pyqRjpctUpyV0hFOOtSYDe1RgxFgDKi+5NaNta2Tr
 | 
				
			||||||
 | 
					lr0s3oi8U4tSIaa3M8p9CKQIAeOPatGfSpJf+PSYj6mqraJqUalmuAf1od10D3e5
 | 
				
			||||||
 | 
					B5Y78UhU4wowKhmiv4iBuZueu2porfUpR8uD7FazU+lmPlt1ARjqetPCE+1IbfUo
 | 
				
			||||||
 | 
					2AktQRn72OlXorG6lTO1SfQNVx97oJ2RT2heTyfWtjT5xNAM9U4NZVxBPA2JYmT6
 | 
				
			||||||
 | 
					jin2MxguA2flbg1V7OwtzpIm2tmryPuXPrWch+Sp4WJ4pTTQEF0DlhWcJWGcmtO8
 | 
				
			||||||
 | 
					Ugn6ViyNhzQ37qYFjzj61Tu9jEFV/edyKC9N5J6VhVndWGQyxB03DqKjUICGJxmr
 | 
				
			||||||
 | 
					q7TlBgt3pifLvCcc+lc6pObFcRJgy7UjZvfFX9LtGurpUKgInLE+lVC7sACSSeK3
 | 
				
			||||||
 | 
					dPK20ZAI3Hlz6e1TVoqnHXc0px5pGy7CKEgquAMsfavPdVvjf6hLIvEQ+WMenv8A
 | 
				
			||||||
 | 
					jWx4g1zdAbSFyXb/AFh9BXMxj5WB4x/jWNONlqbzaTsjQSOJY0zmRto46Af407Ef
 | 
				
			||||||
 | 
					/PBfzpCVjiViwUY79TUX2uH+/XpxdkcbWpaknj2tlgwrNlKuvHHtVfeSeKeoJOWp
 | 
				
			||||||
 | 
					OXMKxKnTingHPNNVVPQrUq5DAEce1FwSJkfjByf6VZtkEk8YPdqhWM9elXrG2mMq
 | 
				
			||||||
 | 
					zgERLkZPc1cXd2G46XJJV/eHHT3rK1MjzV6gBAP1rYKnJ3HDVkarhZU7nBqcR8A6
 | 
				
			||||||
 | 
					e5s+GH2aa4A4Ep/pVm6mMl4+DwoGB796y/D0+LeeL0YGrRcm7k/3q5MKnztnTU+B
 | 
				
			||||||
 | 
					F2eXCAe1Vd5Azmmyybn57VXuJhHCzZ7cV3rRHMYmt3kv2oBQpReh6896r2Mct5IV
 | 
				
			||||||
 | 
					XaAOp9KlkTzlbeMg9verGhBY7qSFjy44J6f/AK65pwTfM1uaU6046JlK6iks5Slx
 | 
				
			||||||
 | 
					DtYDsc0+Ekxb4iNo64PSt06astxI8m52I+4R0rEvLQ6XcjY26Jxhge1Zumr9jZYm
 | 
				
			||||||
 | 
					a3s/kH2uUDKytj61Zi1WZB13fWshsguPelQknNNRlH4WH1qMvigbya/Mgx5SY+lI
 | 
				
			||||||
 | 
					2tMTkRhT7ViFyDjJoZj6mr5qv8wva0OkTZOt3WcK/HoaY2rTN1OD7VjiQ569qkgk
 | 
				
			||||||
 | 
					Ee+Z+QOFB9an94/tD9vR/kLkl/dsCBMdvo3NRrdSD720n1FLFYXN6Gmmyq4yiDvW
 | 
				
			||||||
 | 
					lpkEBg2/Z9spYAsR68UuWb+0CrU7/CaWl3BuLFC3UAqfwq/E+HwDWLp+2C6ubVMD
 | 
				
			||||||
 | 
					DBwP0/wrRVtrjHTtXWtYI55bly6IOM1hXWFkY9q1Lh8lfpXPao0jSKqMMckj14of
 | 
				
			||||||
 | 
					wEsje6aOU70zH2xVhWWVd6NkfyrPMzBQdykNirSW8kY8yJuT1WoRNy5CQZckYbGM
 | 
				
			||||||
 | 
					+tCD5pafbqXxIUKnoQaXy2V3I6GqlJQV2OMXJ6CJ8pyahvb828YRXy7dh2ovLlbS
 | 
				
			||||||
 | 
					MZ++egrCkdpGLsTkmuCcnN3Z0rRWQ7JZstyTyTUyj5TzxUCZxxzViNTtOepqBpEp
 | 
				
			||||||
 | 
					RSQZG3Y6A9qX9z/dFSy2KmAyKT0BIzVP7P8AX867UnYxa1LUMCA/d5q2kSgYxke9
 | 
				
			||||||
 | 
					NSFkcqylSDghutWVTnFWhRiQ/Y4WwWTr3pHsI1uVVHIUj5sHGK0I7XdjK1atLMNq
 | 
				
			||||||
 | 
					IyMqMDGKylJI6IUmybSvDC3cgJ3OODy2RW5rtjDptjBGCu4sdwHYYresrA2sKfZQ
 | 
				
			||||||
 | 
					VZ15U8is/wAZ2kMGkI0r7rszKFTPJzx/hXBDF3rJbI9KrhFTw7e8rHByTfvCUHFY
 | 
				
			||||||
 | 
					+qPvmjPt/Wr8heKQq6FcHGCelZuoNmReP4TXp4iV4HiQWupJpExinfHQgfzrVhfz
 | 
				
			||||||
 | 
					Jmf1JNYFgxN4qdNwIrcgwI2JOCV/OssMtzWo/dQ8yBiTWdfS5IQdKtMcBj2AzWRK
 | 
				
			||||||
 | 
					++QkdCa6JvoY26ku0CPODg1FscSBlJVhyDV1pPMt4lUYABzjuamjtgU3Sg47LWyp
 | 
				
			||||||
 | 
					c2xlzWZX/tK+CbGchcdAaqTgztmTJP15rTkdU4SMFunTpVEI0k20ZPPJqJUrO24/
 | 
				
			||||||
 | 
					aMriyeRFO1gB0JpBaPnArbMfkwhBncfemRW7eZyuT7Vp9WWxHMY5sZR/CT70w27r
 | 
				
			||||||
 | 
					xjNdRLCEh+6Rms1oiJM44z9aJ4ZR0BTuYrWrggnp3pRCWYHjap6VuyxZt8qAfwrM
 | 
				
			||||||
 | 
					6P0+asalHlLjLqatrqUKxqkyfdHDKafLq1tDGfLJeRhjLclfw/rVGO1SaMMrYI6q
 | 
				
			||||||
 | 
					RzTZLQxnOMIejD+tHsZIr2iE02V/7QaR+DIORW6HOawY08mZCD0NbG/ABHSmlZWG
 | 
				
			||||||
 | 
					ncmmc4X6VhXwMhbH3gMr9a17h/kX6ViPOHvJIu/GDUTdojtdFTIdAw71r2r7oExn
 | 
				
			||||||
 | 
					PTArLitpZb028MTOX5UDnHua7DTtKt9MgD3k8bzAZKg9PYetZe0UVcIwciGzt5RH
 | 
				
			||||||
 | 
					uk+6RkKeo96p6jfR2i7VbzJT2XoKS/1xrpWjtlMcP/jxHvXPTPzg8muec3N3kbq0
 | 
				
			||||||
 | 
					VaISytNKZHOWP6UiDBz6cimIGwCfpUuOOnSoY4oNx3Fh1POamtgZpSN2EU5f3NRA
 | 
				
			||||||
 | 
					M7hUXLyYA9qvRolmRC+eDyRV00r6lSvYvKexGMjFJsFTxRRy7zhti9SDin+Vaf3Z
 | 
				
			||||||
 | 
					P+/laVMTCm7NmlLCTqx5kbWswK0NtO6AOcRtKTwABwG/oayFBjk8vyyCDyMGuyuH
 | 
				
			||||||
 | 
					sNXtWjhZQJR93PcUzStP+0g286jz4gArnn8/UeleXh8Y4x5JnpVsNGUuaOhhWzkk
 | 
				
			||||||
 | 
					DyWz6KCan0/UbG01GYX25NrAhBwT+FdJZJ9mdoBGqOhKkY/rXmOt3P2jxFfTZ5Mz
 | 
				
			||||||
 | 
					twPSu2L55WZjXTo001uetReNvDitE5jvFKgADZkY+lcxq2v2F+d9uzpOZRKTJHwC
 | 
				
			||||||
 | 
					D8uD/P3rg4J0USecCzYwvNQyTP5MmSfu4q1haUWppHFPGVZrlbOiMRmlIK/N1ORz
 | 
				
			||||||
 | 
					WPrcXlSw/MCCDzjFZkEkifddh9DV29gmSzs5pZ0kMu5lTduKgevpWtSqmuXqYqm7
 | 
				
			||||||
 | 
					XKkBMF1GTjII/DNbRckYxnHFYLN8w7dK24m3RK47gUYeVnykyWgsjE28uB0Xk1k7
 | 
				
			||||||
 | 
					w3zCtmQKNIumH3gwH5f/AK65aKYxyMvVCa1qu0hL4EbdpKA2D2q1Jc7jgVjxygEM
 | 
				
			||||||
 | 
					DnBq/GAeSyr9TXRSqNxsc0o6llAXGM8nrVu0tVD579qopPDEctMn4HNR3OoI0LrE
 | 
				
			||||||
 | 
					7bnOCRkYUVt7WnDWT1GqNSWyNuZIY2/eSIuOuTk1EL+CM7YnCj171zG9z/EeaTyw
 | 
				
			||||||
 | 
					eSTmsnj/AOXQ3jgqrWx10N0shwJ0cHs/BpJrQBtyqdp/SuTV5E+65xVy01S4tpVI
 | 
				
			||||||
 | 
					kJTPzIeQRWlPGxlZSMp4WpDobzW21SB0IrJmtiJTgdDWxLP51iZo2Ur1U98d6pM5
 | 
				
			||||||
 | 
					kUOetdNWMWYJsqBQpDg4PerEcqtlWOQaglJLcDioCSDkVz83KOxYmg2NlT8p7VZU
 | 
				
			||||||
 | 
					5iQ9OBVIz/u8tTra68yMj0PFRNq+hpAvSFWRAOw5rmpn8vUgw6bhW8NxbywPmPAr
 | 
				
			||||||
 | 
					mJ3Mk7v/ALVctd2SN4K9zejuHtkd4ThiAD9M01LqWS7Uu5K8g5pbK3lurQyopKrH
 | 
				
			||||||
 | 
					lie1UwcMCOnTNck9zSOiFUFOKqMMPkrmrRO2bHtmkaRWOQMnoahBYiUBk44pG4GD
 | 
				
			||||||
 | 
					0HekYBX3A/Ie1DxM7KB0zk4ppXY9jX0C3E08ly69BhBWj/ZEmoXO9i0dsCN8vYn0
 | 
				
			||||||
 | 
					X1aqFhcx26upOFC4H867UeIdK0rTLVJSJ5Y13rFEMncR3PQfzrLFVJU0lBas7sHS
 | 
				
			||||||
 | 
					jV1lsi5pHhYXU8c1zF5VhFgJEP8AlofU10f/AAjejf8APin/AH1XG3njx5fD7xND
 | 
				
			||||||
 | 
					5M8n+rCdh9a5T/hJL/8A56v/AN9V5io1Z6yPV50tEjWjjazG9TsCnOB3rqPD16t/
 | 
				
			||||||
 | 
					dpGDi6Cn5jxken4dawWhUNlzuxzRZzvbagtxCmCD8uKmWqE9TV8Q3Fxoni26aMl4
 | 
				
			||||||
 | 
					mZZNh54IHK/iK4CWwu/tcsghmnjYlhIikjn19K9C1WL+2YFm3j7Q2ShPr3T8a5CW
 | 
				
			||||||
 | 
					WWFnDGaJCcSop+ZT64rrwtfl0ZhiKHtY27GKR8+1kYMpxjFE1sxjI2soPQkYzXQT
 | 
				
			||||||
 | 
					CaNIJy/nR5x5mM59/anz3IdywnG0jn5u9ej7e+y0PJlh+V6s5Q2lxEiM8LBW6Hrm
 | 
				
			||||||
 | 
					nXTbVRSuGQYIrXubxVfHnA1mXkiTRrtfcwbnis3q7j5uVOJScHOT+FadrcLFpyu/
 | 
				
			||||||
 | 
					ID7DWa/Wh5QLKSMn+MGqi2ndGKSbsyd9Wka1kgWNNsjbyT1/zxWaIwOtBbJ4FLkk
 | 
				
			||||||
 | 
					cmqbb3NEohwOlOBJ60zjtThUmkSQVIKjUgU7dQdEWiQGjNR5oyaZfOSE0w0maM0h
 | 
				
			||||||
 | 
					OVxyyyR/6t2X6Gpk1O4jG0kOPcVVamZ9auNScdmc9SnCW6NVNWRuZIse45olurdh
 | 
				
			||||||
 | 
					lXwT2IxWOeDxTg4HBrVYmdrPU5Xh6b8i9JKSmF6UthMvmtGD8xqgf9k063kMFysp
 | 
				
			||||||
 | 
					7HmiNbW7M3QcdjrYEC2tzcHlgjBT+FcXnPNdV5zDww0pPzOGz+dcsPu1daV7DgtW
 | 
				
			||||||
 | 
					b3h+4uTbXMRmP2cADyzyMk5/pVSQbJCPQ4rS0WLbpgOOZXJ/pWbcAiZ/rXI3dmjV
 | 
				
			||||||
 | 
					ooY7ZKn0o+VmyBj1NNP3aVTgYoJuN2/NjtnNOYq0hJOCOKfGpmuY4x16DsM9a6iP
 | 
				
			||||||
 | 
					wHHDamXULsxueSVIAX2qoOzbBpvRGBp7s1zHEQrBj/F2FXotPE1zIxOLaLlmPc0/
 | 
				
			||||||
 | 
					T9NsLTUwz3u+OIHenc+n4c12VzY219oNzFEgUkb0C8fMO1cmJr2ml0PXwNJxpOT6
 | 
				
			||||||
 | 
					nnl9KJbg7PugY4qrj61oyQrHErhD12t7Gofk9DRGasdTi7ndvCpWmi35wGArQksJ
 | 
				
			||||||
 | 
					cEsAgUcseMVkTavpthOokdrjBG5FPBrz1Tk9kDqJHTaHp32q1mVyGVOw71X8TeGG
 | 
				
			||||||
 | 
					uVS+sUBuI0xIuOJV/wAawrLx+RcrAlsLaywcKnUk9zXommXMd1ZpJAxaNl6HqD3/
 | 
				
			||||||
 | 
					AArOcZ0ZczLhNS2PLtGmcPNbugIHBR/1BHQ/zpmq+FN9tNf6RNJti/1tsXyyf7p7
 | 
				
			||||||
 | 
					ivRtS8O2j3RnFvlpAdxU96yp0h0vSJvMjaKMcu7HnHTpWlPFSjUvHZ9B1qEKtPXc
 | 
				
			||||||
 | 
					8faSTOQz/nTdzM3zM2D/AAmlmlVbiRMkYY4BGOM0iOHxgV76acT5mStKwjKWB9et
 | 
				
			||||||
 | 
					V5OY+fWreVwR7VUnXC8HPANYoezIt2OBR1poFO6Uy031HDil3UwtTc0WK57Eu7mn
 | 
				
			||||||
 | 
					g1CtSjoKRpCVx+aM02imaXHZppNBpD0pCbE3UZzUZODSbqdjL2g85BpM560mc0Hp
 | 
				
			||||||
 | 
					QTcXBPQ0bjkeopmSKcCDwRk00iOY6O+Pk+GrdP7yCubHSt/Xz5dnaQjsnNYPatqr
 | 
				
			||||||
 | 
					2Ih3Ov0VootFjlc5YkgD8awZG3sSeu41oWBP9mxqegQnH41ln/WMPeudIubHKCwZ
 | 
				
			||||||
 | 
					R0xmmE7c0sL4uEHrkVI8WGKnoDQR0LugQibVYXYfIrAlvxFbfjXUJpLiOGORhFjk
 | 
				
			||||||
 | 
					ZrlnuJIvL8j5RGQwGerVs3co1mxFwrfvFHzLVWsioyWyMW1m+zybuxHze9ehaEuo
 | 
				
			||||||
 | 
					lYSkLyQONwcDIZfr6150UKtg1q6b4h1PSkEVtP8AuQdwicZUH+lclei6i03O/CYv
 | 
				
			||||||
 | 
					2S5ZbHaS6JHJfXdqw8tgPNVm/iT1+o6VX/4RmD/n7Wo18dQXlvCt1ZG3u4TlLhDv
 | 
				
			||||||
 | 
					HPUEHBxU3/CYJ/z/AH/kA/41x+zqx0aO/wBvSnqmYOp+IdR1hSpkaKBj91eM1krC
 | 
				
			||||||
 | 
					FfLHPpU5BGAKaRjiuy6SsjJRSepBICGz0I5ro/CfiQ6ZfgXMrmEjgA9DXOykKuD1
 | 
				
			||||||
 | 
					qup2sDyAD2pSgpwszW/Kzr9Z8b6sdWm+zzPHE3RD2qjHqWo6vJsu7h5IVIYqelV5
 | 
				
			||||||
 | 
					7T7fZfaYfmeIDco5OKt6Xb3H9nySrARGiszuwxWVoKGi1WhpC+vM/MozaTbajJKy
 | 
				
			||||||
 | 
					krMzdjkZrMutHutLu0WXlGJwR9KXT797G+WVSNrnkH612HiLy5vD63IILFhg/hXf
 | 
				
			||||||
 | 
					BNI8GraUnI4VI953FgBQ8IBKhsjFKASc44qxHIhyoHIpXM7IySCuQeo4qMmrN2uJ
 | 
				
			||||||
 | 
					WI6NzVWtETJiig9aSlHWmR5EiVLmo1p2ahnVB2Q4GlzTQaCaOhdxc0Gm5pe1ILkb
 | 
				
			||||||
 | 
					0wGnsKiPBq0c1TRjt1ODCo6KLEqbRIeansoTcXkUfqwNV1Na+gxhtQRz2FVBXkkU
 | 
				
			||||||
 | 
					9U2S+JW/0yGMdFQY/OsU/drS15/M1R8dlArOeqqu8xQVonS2ibbZB/0zWsfrK341
 | 
				
			||||||
 | 
					vwj/AEYf9cx/KsD/AJbsPc1jEqRHAPnU7lGG7nFWbqdHkxHyoOcj+KqS8BuP4jTx
 | 
				
			||||||
 | 
					Wyir3MOZ7Dz0zS2tw1pcZz8jcHNPjXcKimTbkEU2rgnbU0WVJH3DAV+hqNrRs5HH
 | 
				
			||||||
 | 
					tVGK4aH5W+aP0PUVdhvBtGxt6/3DWLVjeM0xywPkAjmpPs0npUh1NEjwtuoI7nmo
 | 
				
			||||||
 | 
					/wC2G/uJ/wB80i20NWcr94Zp32hOveoo0Eo+8AehHc1dt9FvJ3XZbO2fX+dc8+Vb
 | 
				
			||||||
 | 
					npQk5aop7/MfgZFXYLNpVL+SxUcDjqa37PRLHTgZNSlTeoyYwckVTvvEiJKq2Vkk
 | 
				
			||||||
 | 
					aRfcDjjPqR3NZtyn8C0NvaxgvfZr6XBZ+HLB5roD7XMOVJ+6vZfxrA1TxRLOJIoG
 | 
				
			||||||
 | 
					IjYFRjgcjFY19fXN/OZrqZpZD3JqkRVQw6T5pu7OerjW04U1ZBtLHA/D8667Vz5X
 | 
				
			||||||
 | 
					hW2jYHcWGfyP+Nc9p1s01wpKnaCK2dduo3tvssbcwhWI+tdXQ84wV5C/Sk3KF+UY
 | 
				
			||||||
 | 
					boaYrfL+lLxmpIuRXiZt0fuDWd3rXeMzRS8ZCpmsnvWkdiJbhT0FMp6HBpscdySk
 | 
				
			||||||
 | 
					pSabUGzY4GlJpoNLQVcSlBpvegGgm44ioW61N1FRP1pomohtFFJVGA9TxW9oA/0g
 | 
				
			||||||
 | 
					sf4YyawQDtJAOBXQ6MPLs7mc8bUwKuC970Li/dZl3z+ZqE5/2sVU/ip5JZmY9Sc1
 | 
				
			||||||
 | 
					H3qG7srojsrHElonHWP+lc7IPLuXGPutXR6LGZLCM9cJj9awtRj8q/cEY/rUIqex
 | 
				
			||||||
 | 
					QU5V/wDeqRc5pGXbJIvbrTVcbeDzW8djl2LMfynOakfbIvvVYM3egSHccdqLDuI6
 | 
				
			||||||
 | 
					c9KhKYbK5X6VN5jE00nNFriuNJkP/LSk/ef89KfijFFkFx7OdwI9eoqWO/u7WfMN
 | 
				
			||||||
 | 
					1NGD/dcgGq7oWGRSFC8XutS0pLU1U2tUaenXuyZvNZmL9yauz2wILgEq3cVgRyAg
 | 
				
			||||||
 | 
					bjhxWhDdTImEfj3rJqxtGV0Pa1U8jJpot8yKowD3oM0jr80mB1qF7xIgfLO5j3pK
 | 
				
			||||||
 | 
					LYOSRoy30VjAY4wDJ2rLgkkknZpDuMgKtn86r4aR97nJ61KOCDWqjpYxlNt3FAI4
 | 
				
			||||||
 | 
					96d0DE9akVC5yDx3qGR180hfurxWSLNHSpYFnkgnI/eKAM/WsjUrX7JfyRD7vDL9
 | 
				
			||||||
 | 
					DSTK5kVl6jsKkuJ2v4U3f6+Ndv8AvCt0048vUyaZQpw4IpvelqCkSHkUgoU8YopG
 | 
				
			||||||
 | 
					t+ooPNKTTR1oNId9AooooEOJwtQnrUjntTKaJm7sSkNB606NS7hQMkmqMt3YtQRg
 | 
				
			||||||
 | 
					RhjyT0racG28OyZ4aRsVJY6WLeFbi5YAAcLVDV9TW82wxD92h61pGLjFyl1Npyi0
 | 
				
			||||||
 | 
					oQ6GUeOKb2pW600njFYibO88MKkmkxkHnkGsnxNbGDUFIGNwq34Ll3xTW5P3GDfg
 | 
				
			||||||
 | 
					eKt+LI2ks4pcAkHmlsw3RyEpwyu3cYqttGeAx9K0ltxJGjOOAelTbEQfKAKftLEe
 | 
				
			||||||
 | 
					yvqZixzMvyxnjrUnkTlR8i1eOeucDFMyo/iJpe0Y/ZJFb7Jct0Cj2zTltLv/AJ5A
 | 
				
			||||||
 | 
					/SpxIAc4P0q9BqUUKD9ydw96XtJIpU4mS8Nwn3rdh+FR4l/54t+VdEusQE4aNl/W
 | 
				
			||||||
 | 
					n/2ra/7X/fFP2suwOnHucwOKUMw70baTHtW5zXEcK5yw59RTTuUYWSnYoxmk0hps
 | 
				
			||||||
 | 
					Ztf+JyaegwcAU8J+NTRxKRk8UrWGNWPuaRj2qd2CrxVVjk5poGXVfbp+8D5uUH1P
 | 
				
			||||||
 | 
					SqKZMgFLvP3dxAPUZpI8Eu/rwKlxsUpczJ7aPzblVxxUN0hgvHA4IbP4Vf0yPdIz
 | 
				
			||||||
 | 
					noKrasu27Vz/ABLn9az6my0RnSYLbh3602pCARUeOaohqzHJ3pe9IvejvSKWwo60
 | 
				
			||||||
 | 
					GihqRXQSlFJSimJDD96lzSHrU1raS3U2yIf7x7AVSTeiMm7EccbzSCONSxPat61s
 | 
				
			||||||
 | 
					7bTYvNuCGn649KgNxb6Uhitx5k5HzP2rOlmkncu561ekPUIxuWb3Up7ptqsVi7Cq
 | 
				
			||||||
 | 
					HSnHgUwmocnLVl2UdgpCc0Z4pUXJpJXJbNXQNQOm3/mN9x12Nn06/wBK6bWdRsb3
 | 
				
			||||||
 | 
					TSq3Cb+uN3JriMU9RgVbppiU3saP2weUI4kZyo57YrOe9lLccD0pD7cUx0BNL2aW
 | 
				
			||||||
 | 
					wnJs0raRZYgTgnv7VbjAAKkDjpxWPaOYph/dJwRW0BtIJ71jONmbQd0RSRnfgUix
 | 
				
			||||||
 | 
					OXGBnBq5sG4nHQVYtYVRGlYZHapQNMzb6D5FZcq7DJIHFZ/lT/8APT9K33I5Y/xH
 | 
				
			||||||
 | 
					603C/wCVqlKxLhczX0K5jZis2f61XNhexjmPI9qvQ6/g4kg49VbNXoNWguM7Vfcv
 | 
				
			||||||
 | 
					XIqnKa3Qowg9mc6dyEiSMqR6ikyD0Oa6o/ZrghW2g+jDBrKvdLhBJBKZ6EdKcaqe
 | 
				
			||||||
 | 
					45UWloZQJU1KHJ702S3kg/219RUeVIyDWiaexjqtyfIYYJqM5zx0ppORnNITxTE2
 | 
				
			||||||
 | 
					BOFYkcinj5YkX2zUTA7VUfxHNTBTJIqDoSBUyZUEbGmR7bTfjrWVq8u+6WMfwLj8
 | 
				
			||||||
 | 
					633AtrQL0Cr+tcrNmWZ36knNRGN9TWbtoRA07ORSMjIcMCOM80gpiTAdTS0UnekN
 | 
				
			||||||
 | 
					DqKSigq4UdqSngAct+VBJJb25mO522RA8se/sKtz36RQ/Z7MMsfc9zVJ5mdQmcIO
 | 
				
			||||||
 | 
					gFRdKvnsrIjl11HEHOWpC1BPFNNQW3bYUmkoHoBzTxEx5PAppNk3GDJOKmC7F96F
 | 
				
			||||||
 | 
					QL0pxGTWsY2IbACjNBphNUApOaTrQBSovNIQ5Bj6mtuA+bAjd+lY2cCr+mS7kkTu
 | 
				
			||||||
 | 
					vzj6d/1xUVFpcum9bGxAgcAjnsamuF8u3C+pqHTmHmGM9D0qbUCA8anuK5Op0FG6
 | 
				
			||||||
 | 
					k2RgZwR0FUvPkq/bwi7uS758pOMe9aH2O0/uGhgjixlV3Dljwta+nw7I/mHyIMsR
 | 
				
			||||||
 | 
					61kDpH/vVvWv/Hpc/UV0VDnp7k6qGdS69aju1kRvLQ5T0NWP4o/rTLv/AFy1ibdT
 | 
				
			||||||
 | 
					MJYEg/kagkgjl6Da2KsT/wCuP1qMf6ympNbCcU9yjIrxNsYcetMRWZsfw1ZvfvD6
 | 
				
			||||||
 | 
					VFF2+ldC1RzSVmOVQS0pxjoK0NHtjNc7/wC7VBf+PU/71bPh/wC+9QzWBPrZMUIQ
 | 
				
			||||||
 | 
					D5mwPc1nWmiySHfPlF7L3P1rT8Qf663/AN4VcH3l+ldmEpxluZV5O5SnsoXh8uSM
 | 
				
			||||||
 | 
					EDp6isO80qSAl4SXT0/iH+NdLN96qlx0f/cror0o2M4SZyh60Ur/AH2+tJXltWOp
 | 
				
			||||||
 | 
					DsGkxTh0FJ3pLct7BgAcnmmU5u1NoIFzikoooAUAnoM1NHbluWOB6CmQ/eq2vWtY
 | 
				
			||||||
 | 
					xQhVjVB8oApj8mpG71EetaCY2lAwM0nenfw0hEbGo+pp7UwdaAHgU4HFIKKRIEF2
 | 
				
			||||||
 | 
					2jqTW1Z2UAiKGVo5/wC+p/Q1jxf69PqK2of+PtvrWNSTsa04olihmsZ1Fxu2g/JI
 | 
				
			||||||
 | 
					vOf/AK1WdSlScxmJgd3Bx2qbU/8AUQ/Ss5Okf1rBmqNKAJbxKo7fqal+1f7NQP2p
 | 
				
			||||||
 | 
					Kgo//9nCwZQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AWIQRvmTsl
 | 
				
			||||||
 | 
					BVfnsBat5XE73Noth6iB2QUCXSbTNgUJDIJHPgAKCRA73Noth6iB2XwvD/9leq6i
 | 
				
			||||||
 | 
					qKOaxxQrEHLRZr7mTFb/I2BXX8byVFppB/N9lzxOpn/BTkEPThx8Xhb0lsF56ce0
 | 
				
			||||||
 | 
					uShgWWvjq5UYvid+UyJbss8Ux7MJVOTD+RPLBCIQpN+C8rqzK/iqc4Siio57UqVb
 | 
				
			||||||
 | 
					k1s/Nql27hrvXp9WipFD4gG8oHZIygtCyT46fOMXUDucCftMz813oclrnw6z9ZqQ
 | 
				
			||||||
 | 
					ID//FxI8cS1Md1ZjrT+hcC+CvFNiSsHJAB2ZYR2EVdrGuGpt+84/2Q1SqNIfhbMb
 | 
				
			||||||
 | 
					wLbiILerxpqKmqttNBngPjuz4glWAJVHgIEd4EOIYxczEBDvRXjkMtBbjcv5kl01
 | 
				
			||||||
 | 
					4I7mqHkUO80NcXxNK+bVqrjO8FORRqxqUDhA6JE4yc5ICNJcP8Ver8Xk0F8MXJ2C
 | 
				
			||||||
 | 
					X8NwEEY0GSHyxhYMnYTN07/EGjxQHGJVueUtZ40hivZKhOBAsFyzrDv18hf8/Xhx
 | 
				
			||||||
 | 
					PoF0wLzAygFPPM+310C0NIiPSf+be0s5N0nmbc4Rj/PAyXTZeBkFHJk0ZeXyUtwN
 | 
				
			||||||
 | 
					TbArjf0Ug8b8T/PodtXTi47RTD/zTjADraExdpgSKUJxExHVKIRapE4+SrxW0dsW
 | 
				
			||||||
 | 
					FkQhFlqR0fO/EL0zcihOryUtOFP+bpuEOpw/MmuGR4tihelpCZLJCv28iugcCnBu
 | 
				
			||||||
 | 
					RV87UagufQYgVZFavSckQQsm5YoIdVEjKBd1bcLBfQQTAQoAJwUCVlJW3gIbAwUJ
 | 
				
			||||||
 | 
					B4YfgAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRA73Noth6iB2d+1D/9DMtUt
 | 
				
			||||||
 | 
					vKGTvn+1MZceYn7qIYTUxuzReeH9xYEluaR3UUtMq8WVtmbhfkAWecRl9U5EWSf9
 | 
				
			||||||
 | 
					P8hokcjERXrEcfIK+HAHVxHSGNQEkQ1IRL/NtyS8K91n6c8Z85vcoFbbAjCAtQI4
 | 
				
			||||||
 | 
					g6PV1R3ewGvExD6N3OtgnieW0m4a3V2gBP8wyvgEJcU7jG6ryJVC/21ugsXPdX/H
 | 
				
			||||||
 | 
					whzKG1pADy8i8QToxxNspeifCEbcKd+0lXNborX1pIBpG1KSLa1O9mduJqSm0dH7
 | 
				
			||||||
 | 
					wsVTSsUW/ZJBfIes6U92iPlozOaUfRBaGFYH8+IPYaDzyN705tqeQEFZYoXGSSuJ
 | 
				
			||||||
 | 
					DK7Wf5rUX4nbZGajoCkSoP00hia2pjzqeohHxJaykB8Z0fsYOlAJlBitQ6SYV1wj
 | 
				
			||||||
 | 
					4J2/HBf3lntT4FgFG+OtrGn1adDKUSe+2rs0HCaruug2LnW+y3NKM+BQQO/4j07F
 | 
				
			||||||
 | 
					3SlUvdVIYgonFc25emY2IScxfeI1wvlJcLGXLmTOpnEdcPZu1rTVRWgwTYnKXEd9
 | 
				
			||||||
 | 
					MRb5o/5KhSgr0zlgpbIJXTNYx64zDE7l+rtfmVyGLsvxsGPq/mbePnrqLVzDhU7I
 | 
				
			||||||
 | 
					WS7S4qmeITc3mkkJA8EmEgOJcG7zaGilkIPNz3REvWEGgJOq+1QT2G1KiC6kI+OC
 | 
				
			||||||
 | 
					WaKRgTDmj59znwLQEP9WOHdJMv+pSGlwp1p39c7BTQRWUlAIARAAxG4ZmBQynHZC
 | 
				
			||||||
 | 
					lCgPAf6AyudfsWkl2ofAcCMjSaoFy3NATKkZw3Gh3KSSDyxYhNt8J8X/bO2fZvkT
 | 
				
			||||||
 | 
					0ClhRfPkOz9FZmvVYV1JD4v8yES29GCoP1ivKiN1QHmGbCrX87CTifxWqElOY4K3
 | 
				
			||||||
 | 
					HbL0xqJHK5ea9jICfSAUGNQCTcGfX9o6Q6JJVkm18pKYh1AcinNkHBonbTytrgbl
 | 
				
			||||||
 | 
					JcRo4LMcgvLlTaPyDTWmh4lFQsWA4kfYi5KOe0Cb+EqKbCGZ5abef5fP92bIaPtm
 | 
				
			||||||
 | 
					pVl0R28dKntQOfhvGf5fWj2uEvvULmzXpyZLu2l0J3yxEtfbuYWePeDdBN5zSiY9
 | 
				
			||||||
 | 
					qGpP/LyMKTuvBdGaLtVhZ3GYXKlAnLvJfsbZSzXlJJlzKgIU+IGadLB7BXDemM1t
 | 
				
			||||||
 | 
					9IR3adp17NJbh1+Y4l7XDznFklvlgC09rHmq9mnxUKhfhIFnSW5Xwaa6IA/nCYU1
 | 
				
			||||||
 | 
					1Lx3JKQG5saVj3Fa4grhfG1IAA96NNxx9jckrDGM9ot1fqRwMvW5OsMXcr8myGMj
 | 
				
			||||||
 | 
					Hq+rZXzNVBpPVkuTP1PpvSKM9LhsTYXAl134+SJ3wX5+xmd1yzkRtNZg1a8VNdFT
 | 
				
			||||||
 | 
					RBxmm0pjUnGGKPDym8pKHpV8kciC+8yHzLrhJUYz/uKDshdAjHMfYTcTSIrCSKP4
 | 
				
			||||||
 | 
					ml0fO2UodsGlRhyV9bLLxMcJSVMNCcsAEQEAAcLBfAQYAQoAJgIbDBYhBG+ZOyUF
 | 
				
			||||||
 | 
					V+ewFq3lcTvc2i2HqIHZBQJha9PMBQkO24hUAAoJEDvc2i2HqIHZdv8P/3wUMIwU
 | 
				
			||||||
 | 
					EjC2STnN8dsESpGQEwaZy9scQa2Vh9WQ9J2/R1+wDhbllaqZGs0dlHW7e7o9dJp+
 | 
				
			||||||
 | 
					mVQB04jJTjtvSSQnh+kqfUuxBgG0blnDmnXGiSEwPR0W18hvsPnnlVLyObYnYeDv
 | 
				
			||||||
 | 
					fdSos+WURbNxyUq64HS74FDdz7qH+T4Fp0/vPKfcffdBNStNgUlcEOU5Rr8PAuZJ
 | 
				
			||||||
 | 
					NWaOajwwMnCiLGIRa4VXTPVePHx5H6Geg+998l1k+DkLGeypztWIt1sipRv6/1gR
 | 
				
			||||||
 | 
					ekARPNYzQgh+JK5199FVZ/a5HnXM7Ou9ofy12UtHHWi9mrCuXdj4BM+aS8KZvqPp
 | 
				
			||||||
 | 
					3p+IU91+rpN7Y/XNzcKhcERSSw6dmUzZDZj8MexaRD8FrDR/vgn+wHPc8zA5hTrt
 | 
				
			||||||
 | 
					07MC7PZdUUp++Y8Np6QYkJ5gT/6qcFf506gmDx77a4b/5qFxIxKr/3ZLA+D2oog6
 | 
				
			||||||
 | 
					ncTFTx+yjNNXXG0Dbg83sdFcKLaqVOj9ue3cmhAL4vMLLh0cySSNZruYU/bd1PsX
 | 
				
			||||||
 | 
					IA2m8RJ6g6LnmOCNzpUZRoKGkHnbSOrxLCBL3jpyfvTM4k+S/cQxsXGm5FDFSnki
 | 
				
			||||||
 | 
					zzKS5LYp7WlaBhr8G9NsyTqXgkd3sFouVB+ONRMbwBEUEZoVn4EgZARXkoOM/ESo
 | 
				
			||||||
 | 
					q8sXT3e6nfnuy4UBE32aseTgHvULlUePJMLVwsF8BBgBCgAmAhsMFiEEb5k7JQVX
 | 
				
			||||||
 | 
					57AWreVxO9zaLYeogdkFAl2zh6QFCQsjPCwACgkQO9zaLYeogdmhGA//dKXm1gL1
 | 
				
			||||||
 | 
					m64VohB5eTrkdjgg+SfzCcd5b1Afq9AXwIJ3RhecmYY79tdizxyppuGQVSGBI5Zl
 | 
				
			||||||
 | 
					j7N/JfGSI/HF4HtVJ6hkAqFxC16CsM43ijQnANhnzLW9QZXxMuc7JkQiMFvDR2Lq
 | 
				
			||||||
 | 
					QeGZ8S9/tMOAOE/5g6kNTLO08hIGy9b+kCHt0LzRwkTrub0Kw03QZeo3V145QZDw
 | 
				
			||||||
 | 
					GoU8iRKFYQ/WvmoT/5g5uJMk5qLbPCaLfTaV9RMJLcsyke++ocs6fV4EKxatm5y7
 | 
				
			||||||
 | 
					8E3Fq7DBmW5KuBNHdSNlEuMT/AoRQoYhlYL1icbpprdIC2Xyu8040cJb39viKBfL
 | 
				
			||||||
 | 
					XlwaeCCrTsuRctcsjVDxtJCa6vOZNu/OfmUZjVMFDLK50pfzytyOp/Oy59zcYSwg
 | 
				
			||||||
 | 
					8FYcatTthQU+pSXxQ5z8NEQ4XbJwf7GVeC5YsY+yl1NB3vGW4v2DyGA9gADNw/7e
 | 
				
			||||||
 | 
					kUXgSdxxQFWDDcZ50frscxPe6rNXxzL1HVnzYORsfnxvVgZ1+o0lNK8OcQ2xCccj
 | 
				
			||||||
 | 
					MHfLpYXXfnQleN1Fwzu9T2v6DVhWDVLxCi0TofWYASIOg5ZYUBhjomOsSTLiYSMs
 | 
				
			||||||
 | 
					s3aMjOnl9dlUwsmDV46ruxy26FGUGl6x2Xjptk319skZjwNX01DaT48fjUj9i2tO
 | 
				
			||||||
 | 
					oeROC7YGUWiumMM3J75sZuN1q4VHubTydwfCwWUEGAEKAA8FAlZSUAgCGwwFCQeG
 | 
				
			||||||
 | 
					H4AACgkQO9zaLYeogdntshAAvh5DeSb2C5wItsY/gnjbnGEjTf8W8Q5mDnelGj2S
 | 
				
			||||||
 | 
					B2zOWP/w66gycl4VC8pMmqNEcf4fr3n/tAEwSsn5rQgfzg0Wtf0FmMt26I6BlhZU
 | 
				
			||||||
 | 
					BQy5xMUePaJEkINTrGr6FBLi39OGVx1OicJ5DLQKsI8iCtYngWhIhkg0jZgQ6PWz
 | 
				
			||||||
 | 
					4DxGtA827/5SI09HLY+7FxQ4xU1CG6cN5V3fIdT4vkfsKwLYVwPjCdTVIdMBc+SR
 | 
				
			||||||
 | 
					xRPTYRbLVI6sKrb1fxfxBrksTefwcGFiNvnHgo3O2nw67j96kScbHqgCfYTB9FKB
 | 
				
			||||||
 | 
					X3z+HCAN77s5IREEW3GSoI4ipyOJxC4Xwc4ng6l36b68U91VvYxh+zvVMqdfwhEy
 | 
				
			||||||
 | 
					+7swLAaMf+iZhFZGbSkD21NZwBhOxRGY69f6XZBKw9KQjIoCbluuF3C9pQKQZlIY
 | 
				
			||||||
 | 
					fgx0iXJqH50BvuuTuqjnBDnOxYIgRjkhgs+s1SIdhI5G1bKhxc6XMxln88AaMF58
 | 
				
			||||||
 | 
					x6QtjLUM3cY6QYMOcbE/mEAsgxMPtE20Kk4CDo9urLqyHXnGcXD9eDBn3mXp3PTM
 | 
				
			||||||
 | 
					+px0UJ8fqXvhn7SOw9+T1nuKUEOrLfXaKuVdAOVg6aLcTlKy2hYNstFFU/sjT6PH
 | 
				
			||||||
 | 
					DPHZi2B6QGWYDl2KVZPo6uKMZhN6P1tjHZP4RZnf7cOSu1jpSTLUO2wqu2g+K1r/
 | 
				
			||||||
 | 
					tXo=
 | 
				
			||||||
 | 
					=lheU
 | 
				
			||||||
 | 
					-----END PGP PUBLIC KEY BLOCK-----
 | 
				
			||||||
@ -14,6 +14,7 @@ from urllib.request import urlopen
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from basicswap.rpc import callrpc
 | 
					from basicswap.rpc import callrpc
 | 
				
			||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
 | 
					from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
 | 
				
			||||||
 | 
					from bin.basicswap_prepare import downloadPIVXParams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST_HTTP_HOST = os.getenv('TEST_HTTP_HOST', '127.0.0.1')  # Set to 0.0.0.0 when used in docker
 | 
					TEST_HTTP_HOST = os.getenv('TEST_HTTP_HOST', '127.0.0.1')  # Set to 0.0.0.0 when used in docker
 | 
				
			||||||
@ -33,6 +34,10 @@ LTC_BASE_PORT = 34792
 | 
				
			|||||||
LTC_BASE_RPC_PORT = 35792
 | 
					LTC_BASE_RPC_PORT = 35792
 | 
				
			||||||
LTC_BASE_ZMQ_PORT = 36792
 | 
					LTC_BASE_ZMQ_PORT = 36792
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PIVX_BASE_PORT = 34892
 | 
				
			||||||
 | 
					PIVX_BASE_RPC_PORT = 35892
 | 
				
			||||||
 | 
					PIVX_BASE_ZMQ_PORT = 36892
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PREFIX_SECRET_KEY_REGTEST = 0x2e
 | 
					PREFIX_SECRET_KEY_REGTEST = 0x2e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,6 +78,11 @@ def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_P
 | 
				
			|||||||
            fp.write('stakethreadconddelayms=1000\n')
 | 
					            fp.write('stakethreadconddelayms=1000\n')
 | 
				
			||||||
            fp.write('smsgsregtestadjust=0\n')
 | 
					            fp.write('smsgsregtestadjust=0\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if conf_file == 'pivx.conf':
 | 
				
			||||||
 | 
					            params_dir = os.path.join(datadir, 'pivx-params')
 | 
				
			||||||
 | 
					            downloadPIVXParams(params_dir)
 | 
				
			||||||
 | 
					            fp.write(f'paramsdir={params_dir}\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for i in range(0, num_nodes):
 | 
					        for i in range(0, num_nodes):
 | 
				
			||||||
            if node_id == i:
 | 
					            if node_id == i:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ from tests.basicswap.common import (
 | 
				
			|||||||
    BASE_PORT, BASE_RPC_PORT,
 | 
					    BASE_PORT, BASE_RPC_PORT,
 | 
				
			||||||
    BTC_BASE_PORT, BTC_BASE_RPC_PORT,
 | 
					    BTC_BASE_PORT, BTC_BASE_RPC_PORT,
 | 
				
			||||||
    LTC_BASE_PORT,
 | 
					    LTC_BASE_PORT,
 | 
				
			||||||
 | 
					    PIVX_BASE_PORT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
 | 
					from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,9 +46,6 @@ XMR_BASE_P2P_PORT = 17792
 | 
				
			|||||||
XMR_BASE_RPC_PORT = 29798
 | 
					XMR_BASE_RPC_PORT = 29798
 | 
				
			||||||
XMR_BASE_WALLET_RPC_PORT = 29998
 | 
					XMR_BASE_WALLET_RPC_PORT = 29998
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LTC_BASE_RPC_PORT = 35792
 | 
					 | 
				
			||||||
LTC_BASE_ZMQ_PORT = 36792
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
 | 
					EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -192,6 +190,33 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
 | 
				
			|||||||
            for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
 | 
					            for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
 | 
				
			||||||
                fp.write(opt + '\n')
 | 
					                fp.write(opt + '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if 'pivx' in coins_array:
 | 
				
			||||||
 | 
					        # Pruned nodes don't provide blocks
 | 
				
			||||||
 | 
					        with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'r') as fp:
 | 
				
			||||||
 | 
					            lines = fp.readlines()
 | 
				
			||||||
 | 
					        with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'w') as fp:
 | 
				
			||||||
 | 
					            for line in lines:
 | 
				
			||||||
 | 
					                if not line.startswith('prune'):
 | 
				
			||||||
 | 
					                    fp.write(line)
 | 
				
			||||||
 | 
					            fp.write('port={}\n'.format(PIVX_BASE_PORT + node_id + port_ofs))
 | 
				
			||||||
 | 
					            fp.write('bind=127.0.0.1\n')
 | 
				
			||||||
 | 
					            fp.write('dnsseed=0\n')
 | 
				
			||||||
 | 
					            fp.write('discover=0\n')
 | 
				
			||||||
 | 
					            fp.write('listenonion=0\n')
 | 
				
			||||||
 | 
					            fp.write('upnp=0\n')
 | 
				
			||||||
 | 
					            if use_rpcauth:
 | 
				
			||||||
 | 
					                salt = generate_salt(16)
 | 
				
			||||||
 | 
					                rpc_user = 'test_pivx_' + str(node_id)
 | 
				
			||||||
 | 
					                rpc_pass = 'test_pivx_pwd_' + str(node_id)
 | 
				
			||||||
 | 
					                fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
 | 
				
			||||||
 | 
					                settings['chainclients']['pivx']['rpcuser'] = rpc_user
 | 
				
			||||||
 | 
					                settings['chainclients']['pivx']['rpcpassword'] = rpc_pass
 | 
				
			||||||
 | 
					            for ip in range(num_nodes):
 | 
				
			||||||
 | 
					                if ip != node_id:
 | 
				
			||||||
 | 
					                    fp.write('connect=127.0.0.1:{}\n'.format(PIVX_BASE_PORT + ip + port_ofs))
 | 
				
			||||||
 | 
					            for opt in EXTRA_CONFIG_JSON.get('pivx{}'.format(node_id), []):
 | 
				
			||||||
 | 
					                fp.write(opt + '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if 'monero' in coins_array:
 | 
					    if 'monero' in coins_array:
 | 
				
			||||||
        with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
 | 
					        with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
 | 
				
			||||||
            fp.write('p2p-bind-ip=127.0.0.1\n')
 | 
					            fp.write('p2p-bind-ip=127.0.0.1\n')
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										552
									
								
								tests/basicswap/extended/test_pivx.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										552
									
								
								tests/basicswap/extended/test_pivx.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,552 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copyright (c) 2022 tecnovert
 | 
				
			||||||
 | 
					# Distributed under the MIT software license, see the accompanying
 | 
				
			||||||
 | 
					# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					basicswap]$ python tests/basicswap/extended/test_pivx.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import basicswap.config as cfg
 | 
				
			||||||
 | 
					from basicswap.basicswap import (
 | 
				
			||||||
 | 
					    BasicSwap,
 | 
				
			||||||
 | 
					    Coins,
 | 
				
			||||||
 | 
					    SwapTypes,
 | 
				
			||||||
 | 
					    BidStates,
 | 
				
			||||||
 | 
					    TxStates,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from basicswap.util import (
 | 
				
			||||||
 | 
					    COIN,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from basicswap.basicswap_util import (
 | 
				
			||||||
 | 
					    TxLockTypes,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from basicswap.util.address import (
 | 
				
			||||||
 | 
					    toWIF,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from basicswap.rpc import (
 | 
				
			||||||
 | 
					    callrpc_cli,
 | 
				
			||||||
 | 
					    waitForRPC,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from basicswap.contrib.key import (
 | 
				
			||||||
 | 
					    ECKey,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from basicswap.http_server import (
 | 
				
			||||||
 | 
					    HttpThread,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from tests.basicswap.common import (
 | 
				
			||||||
 | 
					    checkForks,
 | 
				
			||||||
 | 
					    stopDaemons,
 | 
				
			||||||
 | 
					    wait_for_offer,
 | 
				
			||||||
 | 
					    wait_for_bid,
 | 
				
			||||||
 | 
					    wait_for_bid_tx_state,
 | 
				
			||||||
 | 
					    wait_for_in_progress,
 | 
				
			||||||
 | 
					    read_json_api,
 | 
				
			||||||
 | 
					    TEST_HTTP_HOST,
 | 
				
			||||||
 | 
					    TEST_HTTP_PORT,
 | 
				
			||||||
 | 
					    BASE_PORT,
 | 
				
			||||||
 | 
					    BASE_RPC_PORT,
 | 
				
			||||||
 | 
					    BASE_ZMQ_PORT,
 | 
				
			||||||
 | 
					    PREFIX_SECRET_KEY_REGTEST,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from bin.basicswap_run import startDaemon
 | 
				
			||||||
 | 
					from bin.basicswap_prepare import downloadPIVXParams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger()
 | 
				
			||||||
 | 
					logger.level = logging.DEBUG
 | 
				
			||||||
 | 
					if not len(logger.handlers):
 | 
				
			||||||
 | 
					    logger.addHandler(logging.StreamHandler(sys.stdout))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NUM_NODES = 3
 | 
				
			||||||
 | 
					PIVX_NODE = 3
 | 
				
			||||||
 | 
					BTC_NODE = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					delay_event = threading.Event()
 | 
				
			||||||
 | 
					stop_test = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def prepareOtherDir(datadir, nodeId, conf_file='pivx.conf'):
 | 
				
			||||||
 | 
					    node_dir = os.path.join(datadir, str(nodeId))
 | 
				
			||||||
 | 
					    if not os.path.exists(node_dir):
 | 
				
			||||||
 | 
					        os.makedirs(node_dir)
 | 
				
			||||||
 | 
					    filePath = os.path.join(node_dir, conf_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(filePath, 'w+') as fp:
 | 
				
			||||||
 | 
					        fp.write('regtest=1\n')
 | 
				
			||||||
 | 
					        fp.write('[regtest]\n')
 | 
				
			||||||
 | 
					        fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
 | 
				
			||||||
 | 
					        fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fp.write('daemon=0\n')
 | 
				
			||||||
 | 
					        fp.write('printtoconsole=0\n')
 | 
				
			||||||
 | 
					        fp.write('server=1\n')
 | 
				
			||||||
 | 
					        fp.write('discover=0\n')
 | 
				
			||||||
 | 
					        fp.write('listenonion=0\n')
 | 
				
			||||||
 | 
					        fp.write('bind=127.0.0.1\n')
 | 
				
			||||||
 | 
					        fp.write('findpeers=0\n')
 | 
				
			||||||
 | 
					        fp.write('debug=1\n')
 | 
				
			||||||
 | 
					        fp.write('debugexclude=libevent\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fp.write('fallbackfee=0.01\n')
 | 
				
			||||||
 | 
					        fp.write('acceptnonstdtxn=0\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if conf_file == 'pivx.conf':
 | 
				
			||||||
 | 
					            params_dir = os.path.join(datadir, 'pivx-params')
 | 
				
			||||||
 | 
					            downloadPIVXParams(params_dir)
 | 
				
			||||||
 | 
					            fp.write(f'paramsdir={params_dir}\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if conf_file == 'bitcoin.conf':
 | 
				
			||||||
 | 
					            fp.write('wallet=wallet.dat\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def prepareDir(datadir, nodeId, network_key, network_pubkey):
 | 
				
			||||||
 | 
					    node_dir = os.path.join(datadir, str(nodeId))
 | 
				
			||||||
 | 
					    if not os.path.exists(node_dir):
 | 
				
			||||||
 | 
					        os.makedirs(node_dir)
 | 
				
			||||||
 | 
					    filePath = os.path.join(node_dir, 'particl.conf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(filePath, 'w+') as fp:
 | 
				
			||||||
 | 
					        fp.write('regtest=1\n')
 | 
				
			||||||
 | 
					        fp.write('[regtest]\n')
 | 
				
			||||||
 | 
					        fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
 | 
				
			||||||
 | 
					        fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fp.write('daemon=0\n')
 | 
				
			||||||
 | 
					        fp.write('printtoconsole=0\n')
 | 
				
			||||||
 | 
					        fp.write('server=1\n')
 | 
				
			||||||
 | 
					        fp.write('discover=0\n')
 | 
				
			||||||
 | 
					        fp.write('listenonion=0\n')
 | 
				
			||||||
 | 
					        fp.write('bind=127.0.0.1\n')
 | 
				
			||||||
 | 
					        fp.write('findpeers=0\n')
 | 
				
			||||||
 | 
					        fp.write('debug=1\n')
 | 
				
			||||||
 | 
					        fp.write('debugexclude=libevent\n')
 | 
				
			||||||
 | 
					        fp.write('zmqpubsmsg=tcp://127.0.0.1:' + str(BASE_ZMQ_PORT + nodeId) + '\n')
 | 
				
			||||||
 | 
					        fp.write('wallet=wallet.dat\n')
 | 
				
			||||||
 | 
					        fp.write('fallbackfee=0.01\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fp.write('acceptnonstdtxn=0\n')
 | 
				
			||||||
 | 
					        fp.write('minstakeinterval=5\n')
 | 
				
			||||||
 | 
					        fp.write('smsgsregtestadjust=0\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(0, NUM_NODES):
 | 
				
			||||||
 | 
					            if nodeId == i:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            fp.write('addnode=127.0.0.1:%d\n' % (BASE_PORT + i))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if nodeId < 2:
 | 
				
			||||||
 | 
					            fp.write('spentindex=1\n')
 | 
				
			||||||
 | 
					            fp.write('txindex=1\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    basicswap_dir = os.path.join(datadir, str(nodeId), 'basicswap')
 | 
				
			||||||
 | 
					    if not os.path.exists(basicswap_dir):
 | 
				
			||||||
 | 
					        os.makedirs(basicswap_dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pivxdatadir = os.path.join(datadir, str(PIVX_NODE))
 | 
				
			||||||
 | 
					    btcdatadir = os.path.join(datadir, str(BTC_NODE))
 | 
				
			||||||
 | 
					    settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
 | 
				
			||||||
 | 
					    settings = {
 | 
				
			||||||
 | 
					        'debug': True,
 | 
				
			||||||
 | 
					        'zmqhost': 'tcp://127.0.0.1',
 | 
				
			||||||
 | 
					        'zmqport': BASE_ZMQ_PORT + nodeId,
 | 
				
			||||||
 | 
					        'htmlhost': '127.0.0.1',
 | 
				
			||||||
 | 
					        'htmlport': 12700 + nodeId,
 | 
				
			||||||
 | 
					        'network_key': network_key,
 | 
				
			||||||
 | 
					        'network_pubkey': network_pubkey,
 | 
				
			||||||
 | 
					        'chainclients': {
 | 
				
			||||||
 | 
					            'particl': {
 | 
				
			||||||
 | 
					                'connection_type': 'rpc',
 | 
				
			||||||
 | 
					                'manage_daemon': False,
 | 
				
			||||||
 | 
					                'rpcport': BASE_RPC_PORT + nodeId,
 | 
				
			||||||
 | 
					                'datadir': node_dir,
 | 
				
			||||||
 | 
					                'bindir': cfg.PARTICL_BINDIR,
 | 
				
			||||||
 | 
					                'blocks_confirmed': 2,  # Faster testing
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'pivx': {
 | 
				
			||||||
 | 
					                'connection_type': 'rpc',
 | 
				
			||||||
 | 
					                'manage_daemon': False,
 | 
				
			||||||
 | 
					                'rpcport': BASE_RPC_PORT + PIVX_NODE,
 | 
				
			||||||
 | 
					                'datadir': pivxdatadir,
 | 
				
			||||||
 | 
					                'bindir': cfg.PIVX_BINDIR,
 | 
				
			||||||
 | 
					                'use_csv': False,
 | 
				
			||||||
 | 
					                'use_segwit': False,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'bitcoin': {
 | 
				
			||||||
 | 
					                'connection_type': 'rpc',
 | 
				
			||||||
 | 
					                'manage_daemon': False,
 | 
				
			||||||
 | 
					                'rpcport': BASE_RPC_PORT + BTC_NODE,
 | 
				
			||||||
 | 
					                'datadir': btcdatadir,
 | 
				
			||||||
 | 
					                'bindir': cfg.BITCOIN_BINDIR,
 | 
				
			||||||
 | 
					                'use_segwit': True,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'check_progress_seconds': 2,
 | 
				
			||||||
 | 
					        'check_watched_seconds': 4,
 | 
				
			||||||
 | 
					        'check_expired_seconds': 60
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    with open(settings_path, 'w') as fp:
 | 
				
			||||||
 | 
					        json.dump(settings, fp, indent=4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def partRpc(cmd, node_id=0):
 | 
				
			||||||
 | 
					    return callrpc_cli(cfg.PARTICL_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(node_id)), 'regtest', cmd, cfg.PARTICL_CLI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def btcRpc(cmd):
 | 
				
			||||||
 | 
					    return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)), 'regtest', cmd, cfg.BITCOIN_CLI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def pivxRpc(cmd):
 | 
				
			||||||
 | 
					    return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), 'regtest', cmd, cfg.PIVX_CLI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def signal_handler(sig, frame):
 | 
				
			||||||
 | 
					    global stop_test
 | 
				
			||||||
 | 
					    print('signal {} detected.'.format(sig))
 | 
				
			||||||
 | 
					    stop_test = True
 | 
				
			||||||
 | 
					    delay_event.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_coins_loop(cls):
 | 
				
			||||||
 | 
					    while not stop_test:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            pivxRpc('generatetoaddress 1 {}'.format(cls.pivx_addr))
 | 
				
			||||||
 | 
					            btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr))
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logging.warning('run_coins_loop ' + str(e))
 | 
				
			||||||
 | 
					        time.sleep(1.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_loop(self):
 | 
				
			||||||
 | 
					    while not stop_test:
 | 
				
			||||||
 | 
					        for c in self.swap_clients:
 | 
				
			||||||
 | 
					            c.update()
 | 
				
			||||||
 | 
					        time.sleep(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def make_part_cli_rpc_func(node_id):
 | 
				
			||||||
 | 
					    node_id = node_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def rpc_func(method, params=None, wallet=None):
 | 
				
			||||||
 | 
					        nonlocal node_id
 | 
				
			||||||
 | 
					        cmd = method
 | 
				
			||||||
 | 
					        if params:
 | 
				
			||||||
 | 
					            for p in params:
 | 
				
			||||||
 | 
					                cmd += ' "' + p + '"'
 | 
				
			||||||
 | 
					        return partRpc(cmd, node_id)
 | 
				
			||||||
 | 
					    return rpc_func
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Test(unittest.TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def setUpClass(cls):
 | 
				
			||||||
 | 
					        super(Test, cls).setUpClass()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        eckey = ECKey()
 | 
				
			||||||
 | 
					        eckey.generate()
 | 
				
			||||||
 | 
					        cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
 | 
				
			||||||
 | 
					        cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if os.path.isdir(cfg.TEST_DATADIRS):
 | 
				
			||||||
 | 
					            logging.info('Removing ' + cfg.TEST_DATADIRS)
 | 
				
			||||||
 | 
					            for name in os.listdir(cfg.TEST_DATADIRS):
 | 
				
			||||||
 | 
					                if name == 'pivx-params':
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                fullpath = os.path.join(cfg.TEST_DATADIRS, name)
 | 
				
			||||||
 | 
					                if os.path.isdir(fullpath):
 | 
				
			||||||
 | 
					                    shutil.rmtree(fullpath)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    os.remove(fullpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(NUM_NODES):
 | 
				
			||||||
 | 
					            prepareDir(cfg.TEST_DATADIRS, i, cls.network_key, cls.network_pubkey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        prepareOtherDir(cfg.TEST_DATADIRS, PIVX_NODE)
 | 
				
			||||||
 | 
					        prepareOtherDir(cfg.TEST_DATADIRS, BTC_NODE, 'bitcoin.conf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cls.daemons = []
 | 
				
			||||||
 | 
					        cls.swap_clients = []
 | 
				
			||||||
 | 
					        cls.http_threads = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        btc_data_dir = os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE))
 | 
				
			||||||
 | 
					        if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')):
 | 
				
			||||||
 | 
					            callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
 | 
				
			||||||
 | 
					        cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
 | 
				
			||||||
 | 
					        logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
 | 
				
			||||||
 | 
					        cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), cfg.PIVX_BINDIR, cfg.PIVXD))
 | 
				
			||||||
 | 
					        logging.info('Started %s %d', cfg.PIVXD, cls.daemons[-1].pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(NUM_NODES):
 | 
				
			||||||
 | 
					            data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
 | 
				
			||||||
 | 
					            if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')):
 | 
				
			||||||
 | 
					                callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
 | 
				
			||||||
 | 
					            cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
 | 
				
			||||||
 | 
					            logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(NUM_NODES):
 | 
				
			||||||
 | 
					            rpc = make_part_cli_rpc_func(i)
 | 
				
			||||||
 | 
					            waitForRPC(rpc)
 | 
				
			||||||
 | 
					            if i == 0:
 | 
				
			||||||
 | 
					                rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
 | 
				
			||||||
 | 
					            elif i == 1:
 | 
				
			||||||
 | 
					                rpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true'])
 | 
				
			||||||
 | 
					                rpc('getnewextaddress', ['lblExtTest'])
 | 
				
			||||||
 | 
					                rpc('rescanblockchain')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            basicswap_dir = os.path.join(os.path.join(cfg.TEST_DATADIRS, str(i)), 'basicswap')
 | 
				
			||||||
 | 
					            settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
 | 
				
			||||||
 | 
					            with open(settings_path) as fs:
 | 
				
			||||||
 | 
					                settings = json.load(fs)
 | 
				
			||||||
 | 
					            fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
 | 
				
			||||||
 | 
					            cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)))
 | 
				
			||||||
 | 
					            cls.swap_clients[-1].setDaemonPID(Coins.BTC, cls.daemons[0].pid)
 | 
				
			||||||
 | 
					            cls.swap_clients[-1].setDaemonPID(Coins.PIVX, cls.daemons[1].pid)
 | 
				
			||||||
 | 
					            cls.swap_clients[-1].setDaemonPID(Coins.PART, cls.daemons[2 + i].pid)
 | 
				
			||||||
 | 
					            cls.swap_clients[-1].start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
 | 
				
			||||||
 | 
					            cls.http_threads.append(t)
 | 
				
			||||||
 | 
					            t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        waitForRPC(pivxRpc)
 | 
				
			||||||
 | 
					        num_blocks = 1352  # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
 | 
				
			||||||
 | 
					        logging.info('Mining %d pivx blocks', num_blocks)
 | 
				
			||||||
 | 
					        cls.pivx_addr = pivxRpc('getnewaddress mining_addr')
 | 
				
			||||||
 | 
					        pivxRpc('generatetoaddress {} {}'.format(num_blocks, cls.pivx_addr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ro = pivxRpc('getblockchaininfo')
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            assert (ro['bip9_softforks']['csv']['status'] == 'active')
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            logging.info('pivx: csv is not active')
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            assert (ro['bip9_softforks']['segwit']['status'] == 'active')
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            logging.info('pivx: segwit is not active')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        waitForRPC(btcRpc)
 | 
				
			||||||
 | 
					        cls.btc_addr = btcRpc('getnewaddress mining_addr bech32')
 | 
				
			||||||
 | 
					        logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
 | 
				
			||||||
 | 
					        btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ro = btcRpc('getblockchaininfo')
 | 
				
			||||||
 | 
					        checkForks(ro)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ro = pivxRpc('getwalletinfo')
 | 
				
			||||||
 | 
					        print('pivxRpc', ro)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        signal.signal(signal.SIGINT, signal_handler)
 | 
				
			||||||
 | 
					        cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
 | 
				
			||||||
 | 
					        cls.update_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,))
 | 
				
			||||||
 | 
					        cls.coins_update_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Wait for height, or sequencelock is thrown off by genesis blocktime
 | 
				
			||||||
 | 
					        num_blocks = 3
 | 
				
			||||||
 | 
					        logging.info('Waiting for Particl chain height %d', num_blocks)
 | 
				
			||||||
 | 
					        for i in range(60):
 | 
				
			||||||
 | 
					            particl_blocks = cls.swap_clients[0].callrpc('getblockchaininfo')['blocks']
 | 
				
			||||||
 | 
					            print('particl_blocks', particl_blocks)
 | 
				
			||||||
 | 
					            if particl_blocks >= num_blocks:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            delay_event.wait(1)
 | 
				
			||||||
 | 
					        assert (particl_blocks >= num_blocks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def tearDownClass(cls):
 | 
				
			||||||
 | 
					        global stop_test
 | 
				
			||||||
 | 
					        logging.info('Finalising')
 | 
				
			||||||
 | 
					        stop_test = True
 | 
				
			||||||
 | 
					        cls.update_thread.join()
 | 
				
			||||||
 | 
					        cls.coins_update_thread.join()
 | 
				
			||||||
 | 
					        for t in cls.http_threads:
 | 
				
			||||||
 | 
					            t.stop()
 | 
				
			||||||
 | 
					            t.join()
 | 
				
			||||||
 | 
					        for c in cls.swap_clients:
 | 
				
			||||||
 | 
					            c.finalise()
 | 
				
			||||||
 | 
					            c.fp.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stopDaemons(cls.daemons)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super(Test, cls).tearDownClass()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_02_part_pivx(self):
 | 
				
			||||||
 | 
					        logging.info('---------- Test PART to PIVX')
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[0].postOffer(Coins.PART, Coins.PIVX, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_offer(delay_event, swap_clients[1], offer_id)
 | 
				
			||||||
 | 
					        offers = swap_clients[1].listOffers()
 | 
				
			||||||
 | 
					        assert (len(offers) == 1)
 | 
				
			||||||
 | 
					        for offer in offers:
 | 
				
			||||||
 | 
					            if offer.offer_id == offer_id:
 | 
				
			||||||
 | 
					                bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        swap_clients[0].acceptBid(bid_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0 = read_json_api(1800)
 | 
				
			||||||
 | 
					        js_1 = read_json_api(1801)
 | 
				
			||||||
 | 
					        assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					        assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_03_pivx_part(self):
 | 
				
			||||||
 | 
					        logging.info('---------- Test PIVX to PART')
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[1].postOffer(Coins.PIVX, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_offer(delay_event, swap_clients[0], offer_id)
 | 
				
			||||||
 | 
					        offers = swap_clients[0].listOffers()
 | 
				
			||||||
 | 
					        for offer in offers:
 | 
				
			||||||
 | 
					            if offer.offer_id == offer_id:
 | 
				
			||||||
 | 
					                bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[1], bid_id)
 | 
				
			||||||
 | 
					        swap_clients[1].acceptBid(bid_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_in_progress(delay_event, swap_clients[0], bid_id, sent=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0 = read_json_api(1800)
 | 
				
			||||||
 | 
					        js_1 = read_json_api(1801)
 | 
				
			||||||
 | 
					        assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					        assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_04_pivx_btc(self):
 | 
				
			||||||
 | 
					        logging.info('---------- Test PIVX to BTC')
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_offer(delay_event, swap_clients[1], offer_id)
 | 
				
			||||||
 | 
					        offers = swap_clients[1].listOffers()
 | 
				
			||||||
 | 
					        for offer in offers:
 | 
				
			||||||
 | 
					            if offer.offer_id == offer_id:
 | 
				
			||||||
 | 
					                bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id)
 | 
				
			||||||
 | 
					        swap_clients[0].acceptBid(bid_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0 = read_json_api(1800)
 | 
				
			||||||
 | 
					        js_1 = read_json_api(1801)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					        assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_05_refund(self):
 | 
				
			||||||
 | 
					        # Seller submits initiate txn, buyer doesn't respond
 | 
				
			||||||
 | 
					        logging.info('---------- Test refund, PIVX to BTC')
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST,
 | 
				
			||||||
 | 
					                                             TxLockTypes.ABS_LOCK_BLOCKS, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_offer(delay_event, swap_clients[1], offer_id)
 | 
				
			||||||
 | 
					        offers = swap_clients[1].listOffers()
 | 
				
			||||||
 | 
					        for offer in offers:
 | 
				
			||||||
 | 
					            if offer.offer_id == offer_id:
 | 
				
			||||||
 | 
					                bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id)
 | 
				
			||||||
 | 
					        swap_clients[1].abandonBid(bid_id)
 | 
				
			||||||
 | 
					        swap_clients[0].acceptBid(bid_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0 = read_json_api(1800)
 | 
				
			||||||
 | 
					        js_1 = read_json_api(1801)
 | 
				
			||||||
 | 
					        assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					        assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_06_self_bid(self):
 | 
				
			||||||
 | 
					        logging.info('---------- Test same client, BTC to PIVX')
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0_before = read_json_api(1800)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_offer(delay_event, swap_clients[0], offer_id)
 | 
				
			||||||
 | 
					        offers = swap_clients[0].listOffers()
 | 
				
			||||||
 | 
					        for offer in offers:
 | 
				
			||||||
 | 
					            if offer.offer_id == offer_id:
 | 
				
			||||||
 | 
					                bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id)
 | 
				
			||||||
 | 
					        swap_clients[0].acceptBid(bid_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid_tx_state(delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60)
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0 = read_json_api(1800)
 | 
				
			||||||
 | 
					        assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
 | 
				
			||||||
 | 
					        assert (js_0['num_recv_bids'] == js_0_before['num_recv_bids'] + 1 and js_0['num_sent_bids'] == js_0_before['num_sent_bids'] + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_07_error(self):
 | 
				
			||||||
 | 
					        logging.info('---------- Test error, BTC to PIVX, set fee above bid value')
 | 
				
			||||||
 | 
					        swap_clients = self.swap_clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_0_before = read_json_api(1800)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_offer(delay_event, swap_clients[0], offer_id)
 | 
				
			||||||
 | 
					        offers = swap_clients[0].listOffers()
 | 
				
			||||||
 | 
					        for offer in offers:
 | 
				
			||||||
 | 
					            if offer.offer_id == offer_id:
 | 
				
			||||||
 | 
					                bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id)
 | 
				
			||||||
 | 
					        swap_clients[0].acceptBid(bid_id)
 | 
				
			||||||
 | 
					        swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate'] = 10.0
 | 
				
			||||||
 | 
					        swap_clients[0].getChainClientSettings(Coins.PIVX)['override_feerate'] = 10.0
 | 
				
			||||||
 | 
					        wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pass_99_delay(self):
 | 
				
			||||||
 | 
					        global stop_test
 | 
				
			||||||
 | 
					        logging.info('Delay')
 | 
				
			||||||
 | 
					        for i in range(60 * 5):
 | 
				
			||||||
 | 
					            if stop_test:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            time.sleep(1)
 | 
				
			||||||
 | 
					            print('delay', i)
 | 
				
			||||||
 | 
					        stop_test = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    unittest.main()
 | 
				
			||||||
@ -43,6 +43,8 @@ class Test(BaseTest):
 | 
				
			|||||||
            cls.test_coin_from = Coins.BTC
 | 
					            cls.test_coin_from = Coins.BTC
 | 
				
			||||||
        if not hasattr(cls, 'start_ltc_nodes'):
 | 
					        if not hasattr(cls, 'start_ltc_nodes'):
 | 
				
			||||||
            cls.start_ltc_nodes = False
 | 
					            cls.start_ltc_nodes = False
 | 
				
			||||||
 | 
					        if not hasattr(cls, 'start_pivx_nodes'):
 | 
				
			||||||
 | 
					            cls.start_pivx_nodes = False
 | 
				
			||||||
        super(Test, cls).setUpClass()
 | 
					        super(Test, cls).setUpClass()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ from basicswap.util import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Test(unittest.TestCase):
 | 
					class Test(unittest.TestCase):
 | 
				
			||||||
    REQUIRED_SETTINGS = {'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': True}
 | 
					    REQUIRED_SETTINGS = {'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': True, 'connection_type': 'rpc'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_serialise_num(self):
 | 
					    def test_serialise_num(self):
 | 
				
			||||||
        def test_case(v, nb=None):
 | 
					        def test_case(v, nb=None):
 | 
				
			||||||
 | 
				
			|||||||
@ -64,6 +64,7 @@ class Test(BaseTest):
 | 
				
			|||||||
    def setUpClass(cls):
 | 
					    def setUpClass(cls):
 | 
				
			||||||
        cls.start_ltc_nodes = True
 | 
					        cls.start_ltc_nodes = True
 | 
				
			||||||
        cls.start_xmr_nodes = False
 | 
					        cls.start_xmr_nodes = False
 | 
				
			||||||
 | 
					        cls.start_pivx_nodes = False
 | 
				
			||||||
        super(Test, cls).setUpClass()
 | 
					        super(Test, cls).setUpClass()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
 | 
					        btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
 | 
				
			||||||
 | 
				
			|||||||
@ -80,6 +80,8 @@ from tests.basicswap.common import (
 | 
				
			|||||||
    BTC_BASE_RPC_PORT,
 | 
					    BTC_BASE_RPC_PORT,
 | 
				
			||||||
    LTC_BASE_PORT,
 | 
					    LTC_BASE_PORT,
 | 
				
			||||||
    LTC_BASE_RPC_PORT,
 | 
					    LTC_BASE_RPC_PORT,
 | 
				
			||||||
 | 
					    PIVX_BASE_PORT,
 | 
				
			||||||
 | 
					    PIVX_BASE_RPC_PORT,
 | 
				
			||||||
    PREFIX_SECRET_KEY_REGTEST,
 | 
					    PREFIX_SECRET_KEY_REGTEST,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from bin.basicswap_run import startDaemon, startXmrDaemon
 | 
					from bin.basicswap_run import startDaemon, startXmrDaemon
 | 
				
			||||||
@ -91,6 +93,7 @@ NUM_NODES = 3
 | 
				
			|||||||
NUM_XMR_NODES = 3
 | 
					NUM_XMR_NODES = 3
 | 
				
			||||||
NUM_BTC_NODES = 3
 | 
					NUM_BTC_NODES = 3
 | 
				
			||||||
NUM_LTC_NODES = 3
 | 
					NUM_LTC_NODES = 3
 | 
				
			||||||
 | 
					NUM_PIVX_NODES = 3
 | 
				
			||||||
TEST_DIR = cfg.TEST_DATADIRS
 | 
					TEST_DIR = cfg.TEST_DATADIRS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
XMR_BASE_P2P_PORT = 17792
 | 
					XMR_BASE_P2P_PORT = 17792
 | 
				
			||||||
@ -150,7 +153,7 @@ def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]):
 | 
				
			|||||||
    return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
 | 
					    return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_ltc=False, with_xmr=False):
 | 
					def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set()):
 | 
				
			||||||
    basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
 | 
					    basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
 | 
				
			||||||
    if not os.path.exists(basicswap_dir):
 | 
					    if not os.path.exists(basicswap_dir):
 | 
				
			||||||
        os.makedirs(basicswap_dir)
 | 
					        os.makedirs(basicswap_dir)
 | 
				
			||||||
@ -201,7 +204,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
 | 
				
			|||||||
        'debug_ui': True,
 | 
					        'debug_ui': True,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if with_xmr:
 | 
					    if Coins.XMR in with_coins:
 | 
				
			||||||
        settings['chainclients']['monero'] = {
 | 
					        settings['chainclients']['monero'] = {
 | 
				
			||||||
            'connection_type': 'rpc',
 | 
					            'connection_type': 'rpc',
 | 
				
			||||||
            'manage_daemon': False,
 | 
					            'manage_daemon': False,
 | 
				
			||||||
@ -214,7 +217,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
 | 
				
			|||||||
            'bindir': cfg.XMR_BINDIR,
 | 
					            'bindir': cfg.XMR_BINDIR,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if with_ltc:
 | 
					    if Coins.LTC in with_coins:
 | 
				
			||||||
        settings['chainclients']['litecoin'] = {
 | 
					        settings['chainclients']['litecoin'] = {
 | 
				
			||||||
            'connection_type': 'rpc',
 | 
					            'connection_type': 'rpc',
 | 
				
			||||||
            'manage_daemon': False,
 | 
					            'manage_daemon': False,
 | 
				
			||||||
@ -226,6 +229,18 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l
 | 
				
			|||||||
            'use_segwit': True,
 | 
					            'use_segwit': True,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if Coins.PIVX in with_coins:
 | 
				
			||||||
 | 
					        settings['chainclients']['pivx'] = {
 | 
				
			||||||
 | 
					            'connection_type': 'rpc',
 | 
				
			||||||
 | 
					            'manage_daemon': False,
 | 
				
			||||||
 | 
					            'rpcport': PIVX_BASE_RPC_PORT + node_id,
 | 
				
			||||||
 | 
					            'rpcuser': 'test' + str(node_id),
 | 
				
			||||||
 | 
					            'rpcpassword': 'test_pass' + str(node_id),
 | 
				
			||||||
 | 
					            'datadir': os.path.join(datadir, 'pivx_' + str(node_id)),
 | 
				
			||||||
 | 
					            'bindir': cfg.PIVX_BINDIR,
 | 
				
			||||||
 | 
					            'use_segwit': False,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(settings_path, 'w') as fp:
 | 
					    with open(settings_path, 'w') as fp:
 | 
				
			||||||
        json.dump(settings, fp, indent=4)
 | 
					        json.dump(settings, fp, indent=4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -238,6 +253,10 @@ def ltcCli(cmd, node_id=0):
 | 
				
			|||||||
    return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(TEST_DIR, 'ltc_' + str(node_id)), 'regtest', cmd, cfg.LITECOIN_CLI)
 | 
					    return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(TEST_DIR, 'ltc_' + str(node_id)), 'regtest', cmd, cfg.LITECOIN_CLI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def pivxCli(cmd, node_id=0):
 | 
				
			||||||
 | 
					    return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(TEST_DIR, 'pivx_' + str(node_id)), 'regtest', cmd, cfg.PIVX_CLI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def signal_handler(sig, frame):
 | 
					def signal_handler(sig, frame):
 | 
				
			||||||
    logging.info('signal {} detected.'.format(sig))
 | 
					    logging.info('signal {} detected.'.format(sig))
 | 
				
			||||||
    test_delay_event.set()
 | 
					    test_delay_event.set()
 | 
				
			||||||
@ -283,6 +302,8 @@ def run_coins_loop(cls):
 | 
				
			|||||||
                btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
 | 
					                btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
 | 
				
			||||||
            if cls.ltc_addr is not None:
 | 
					            if cls.ltc_addr is not None:
 | 
				
			||||||
                ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
 | 
					                ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
 | 
				
			||||||
 | 
					            if cls.pivx_addr is not None:
 | 
				
			||||||
 | 
					                pivxCli('generatetoaddress 1 {}'.format(cls.pivx_addr))
 | 
				
			||||||
            if cls.xmr_addr is not None:
 | 
					            if cls.xmr_addr is not None:
 | 
				
			||||||
                callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
 | 
					                callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
@ -304,6 +325,8 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
    def setUpClass(cls):
 | 
					    def setUpClass(cls):
 | 
				
			||||||
        if not hasattr(cls, 'start_ltc_nodes'):
 | 
					        if not hasattr(cls, 'start_ltc_nodes'):
 | 
				
			||||||
            cls.start_ltc_nodes = False
 | 
					            cls.start_ltc_nodes = False
 | 
				
			||||||
 | 
					        if not hasattr(cls, 'start_pivx_nodes'):
 | 
				
			||||||
 | 
					            cls.start_pivx_nodes = False
 | 
				
			||||||
        if not hasattr(cls, 'start_xmr_nodes'):
 | 
					        if not hasattr(cls, 'start_xmr_nodes'):
 | 
				
			||||||
            cls.start_xmr_nodes = True
 | 
					            cls.start_xmr_nodes = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -316,12 +339,14 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
        cls.part_daemons = []
 | 
					        cls.part_daemons = []
 | 
				
			||||||
        cls.btc_daemons = []
 | 
					        cls.btc_daemons = []
 | 
				
			||||||
        cls.ltc_daemons = []
 | 
					        cls.ltc_daemons = []
 | 
				
			||||||
 | 
					        cls.pivx_daemons = []
 | 
				
			||||||
        cls.xmr_daemons = []
 | 
					        cls.xmr_daemons = []
 | 
				
			||||||
        cls.xmr_wallet_auth = []
 | 
					        cls.xmr_wallet_auth = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cls.xmr_addr = None
 | 
					        cls.xmr_addr = None
 | 
				
			||||||
        cls.btc_addr = None
 | 
					        cls.btc_addr = None
 | 
				
			||||||
        cls.ltc_addr = None
 | 
					        cls.ltc_addr = None
 | 
				
			||||||
 | 
					        cls.pivx_addr = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.propagate = False
 | 
					        logger.propagate = False
 | 
				
			||||||
        logger.handlers = []
 | 
					        logger.handlers = []
 | 
				
			||||||
@ -333,7 +358,14 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if os.path.isdir(TEST_DIR):
 | 
					        if os.path.isdir(TEST_DIR):
 | 
				
			||||||
            logging.info('Removing ' + TEST_DIR)
 | 
					            logging.info('Removing ' + TEST_DIR)
 | 
				
			||||||
            shutil.rmtree(TEST_DIR)
 | 
					            for name in os.listdir(TEST_DIR):
 | 
				
			||||||
 | 
					                if name == 'pivx-params':
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                fullpath = os.path.join(TEST_DIR, name)
 | 
				
			||||||
 | 
					                if os.path.isdir(fullpath):
 | 
				
			||||||
 | 
					                    shutil.rmtree(fullpath)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    os.remove(fullpath)
 | 
				
			||||||
        if not os.path.exists(TEST_DIR):
 | 
					        if not os.path.exists(TEST_DIR):
 | 
				
			||||||
            os.makedirs(TEST_DIR)
 | 
					            os.makedirs(TEST_DIR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -391,6 +423,17 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT))
 | 
					                    waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if cls.start_pivx_nodes:
 | 
				
			||||||
 | 
					                for i in range(NUM_PIVX_NODES):
 | 
				
			||||||
 | 
					                    data_dir = prepareDataDir(TEST_DIR, i, 'pivx.conf', 'pivx_', base_p2p_port=PIVX_BASE_PORT, base_rpc_port=PIVX_BASE_RPC_PORT)
 | 
				
			||||||
 | 
					                    if os.path.exists(os.path.join(cfg.PIVX_BINDIR, 'pivx-wallet')):
 | 
				
			||||||
 | 
					                        callrpc_cli(cfg.PIVX_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'pivx-wallet')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    cls.pivx_daemons.append(startDaemon(os.path.join(TEST_DIR, 'pivx_' + str(i)), cfg.PIVX_BINDIR, cfg.PIVXD))
 | 
				
			||||||
 | 
					                    logging.info('Started %s %d', cfg.PIVXD, cls.part_daemons[-1].pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if cls.start_xmr_nodes:
 | 
					            if cls.start_xmr_nodes:
 | 
				
			||||||
                for i in range(NUM_XMR_NODES):
 | 
					                for i in range(NUM_XMR_NODES):
 | 
				
			||||||
                    prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
 | 
					                    prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
 | 
				
			||||||
@ -417,7 +460,14 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
            cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
 | 
					            cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for i in range(NUM_NODES):
 | 
					            for i in range(NUM_NODES):
 | 
				
			||||||
                prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, cls.start_ltc_nodes, cls.start_xmr_nodes)
 | 
					                start_nodes = set()
 | 
				
			||||||
 | 
					                if cls.start_ltc_nodes:
 | 
				
			||||||
 | 
					                    start_nodes.add(Coins.LTC)
 | 
				
			||||||
 | 
					                if cls.start_xmr_nodes:
 | 
				
			||||||
 | 
					                    start_nodes.add(Coins.XMR)
 | 
				
			||||||
 | 
					                if cls.start_pivx_nodes:
 | 
				
			||||||
 | 
					                    start_nodes.add(Coins.PIVX)
 | 
				
			||||||
 | 
					                prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes)
 | 
				
			||||||
                basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
 | 
					                basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
 | 
				
			||||||
                settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
 | 
					                settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
 | 
				
			||||||
                with open(settings_path) as fs:
 | 
					                with open(settings_path) as fs:
 | 
				
			||||||
@ -481,6 +531,18 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
 | 
					                checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if cls.start_pivx_nodes:
 | 
				
			||||||
 | 
					                num_blocks = 400
 | 
				
			||||||
 | 
					                cls.pivx_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=PIVX_BASE_RPC_PORT)
 | 
				
			||||||
 | 
					                logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr)
 | 
				
			||||||
 | 
					                callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Switch addresses so wallet amounts stay constant
 | 
				
			||||||
 | 
					                num_blocks = 100
 | 
				
			||||||
 | 
					                cls.pivx_addr = cls.swap_clients[0].ci(Coins.PIVX).pubkey_to_address(void_block_rewards_pubkey)
 | 
				
			||||||
 | 
					                logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr)
 | 
				
			||||||
 | 
					                callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            num_blocks = 100
 | 
					            num_blocks = 100
 | 
				
			||||||
            if cls.start_xmr_nodes:
 | 
					            if cls.start_xmr_nodes:
 | 
				
			||||||
                cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
 | 
					                cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
 | 
				
			||||||
@ -537,6 +599,7 @@ class BaseTest(unittest.TestCase):
 | 
				
			|||||||
        stopDaemons(cls.part_daemons)
 | 
					        stopDaemons(cls.part_daemons)
 | 
				
			||||||
        stopDaemons(cls.btc_daemons)
 | 
					        stopDaemons(cls.btc_daemons)
 | 
				
			||||||
        stopDaemons(cls.ltc_daemons)
 | 
					        stopDaemons(cls.ltc_daemons)
 | 
				
			||||||
 | 
					        stopDaemons(cls.pivx_daemons)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(BaseTest, cls).tearDownClass()
 | 
					        super(BaseTest, cls).tearDownClass()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user