def run(self): """Scans for signed transactions and updates status if multisig threshold achieved""" self.logger.info("Starting..") to_block = w3.eth.blockNumber - self.config.eth_confirmations self.catch_up(to_block) self.event_listener.start() self.logger.info("Done catching up") while not self.stop_signal.is_set(): for transaction in Swap.objects(status=Status.SWAP_RETRY): self._retry(transaction) for transaction in Swap.objects(status=Status.SWAP_UNSIGNED): self.logger.debug(f"Checking unsigned tx {transaction.id}") if Signatures.objects(tx_id=transaction.id).count( ) >= self.config.signatures_threshold: self.logger.info( f"Found tx {transaction.id} with enough signatures to broadcast" ) transaction.status = Status.SWAP_SIGNED transaction.save() self.logger.info( f"Set status of tx {transaction.id} to signed") else: self.logger.debug( f"Tx {transaction.id} does not have enough signatures") self.stop_signal.wait(self.config.sleep_interval)
def _create_and_broadcast(self, tx: Swap): # reacts to signed tx in the DB that are ready to be sent to secret20 signatures = [ signature.signed_tx for signature in Signatures.objects(tx_id=tx.id) ] if len(signatures ) < self.config['signatures_threshold']: # sanity check self.logger.error( msg=f"Tried to sign tx {tx.id}, without enough signatures" f" (required: {self.config['signatures_threshold']}, have: {len(signatures)})" ) return try: signed_tx = self._create_multisig(tx.unsigned_tx, signatures) scrt_tx_hash = self._broadcast(signed_tx) self.logger.info( f"Broadcasted {tx.id} successfully - {scrt_tx_hash}") tx.status = Status.SWAP_SUBMITTED tx.dst_tx_hash = scrt_tx_hash tx.save() self.logger.info(f"Changed status of tx {tx.id} to submitted") except (RuntimeError, OperationError) as e: self.logger.error( msg=f"Failed to create multisig and broadcast, error: {e}")
def _retry(self, tx: Swap): for signature in Signatures.objects(tx_id=tx.id): signature.delete() tx.status = Status.SWAP_UNSIGNED tx.sequence = self.sequence tx.save() self.sequence = self.sequence + 1
def _scan_swap(self): while not self.stop_event.is_set(): failed_prev = False for tx in Commands.objects(status=Status.SWAP_SIGNED): self._create_and_broadcast(tx) for tx in Swap.objects(status=Status.SWAP_SIGNED, src_network="Ethereum"): # if there are 2 transactions that depend on each other (sequence number), and the first fails we mark # the next as "retry" if failed_prev: self.logger.info(f"Previous TX failed, retrying {tx.id}") _set_retry(tx) continue self.logger.info(f"Found tx ready for broadcasting {tx.id}") failed_prev = not self._create_and_broadcast(tx) failed_prev = False for tx in Swap.objects(status=Status.SWAP_SUBMITTED, src_network="Ethereum"): if failed_prev: self.logger.info(f"Previous TX failed, retrying {tx.id}") _set_retry(tx) continue failed_prev = not self._broadcast_validation(tx) self.logger.debug('done scanning for swaps. sleeping..') self.stop_event.wait(self.config.sleep_interval)
def _scan_swap(self): while not self.stop_event.is_set(): for tx in Swap.objects(status=Status.SWAP_SIGNED): self.logger.info(f"Found tx ready for broadcasting {tx.id}") self._create_and_broadcast(tx) sleep(SCRT_BLOCK_TIME) for tx in Swap.objects(status=Status.SWAP_SUBMITTED): self._broadcast_validation(tx) self.logger.debug('done scanning for swaps. sleeping..') self.stop_event.wait(self.config['sleep_interval'])
def _handle(self, event: AttributeDict): """Extracts tx data from @event and add unsigned_tx to db""" if not self.contract.verify_destination(event): return amount = str(self.contract.extract_amount(event)) try: block_number, tx_hash, recipient, token = self.contract.parse_swap_event( event) if token is None: token = 'native' except ValueError: return try: s20 = self._get_s20(token) mint = mint_json(amount, tx_hash, recipient, s20.address) unsigned_tx = create_unsigned_tx(self.config.scrt_swap_address, mint, self.config.chain_id, self.config.enclave_key, self.config.swap_code_hash, self.multisig.address) tx = Swap(src_tx_hash=tx_hash, status=Status.SWAP_UNSIGNED, unsigned_tx=unsigned_tx, src_coin=token, dst_coin=s20.name, dst_address=s20.address, src_network="Ethereum", sequence=self.sequence, amount=amount) tx.save(force_insert=True) self.sequence = self.sequence + 1 self.logger.info( f"saved new Ethereum -> Secret transaction {tx_hash}, for {amount} {s20.name}" ) # SwapTrackerObject.update_last_processed(src=Source.ETH.value, update_val=block_number) except (IndexError, AttributeError, KeyError) as e: self.logger.error(f"Failed on tx {tx_hash}, block {block_number}, " f"due to error: {e}") except RuntimeError as e: self.logger.error( f"Failed to create swap tx for eth hash {tx_hash}, block {block_number}. Error: {e}" ) except NotUniqueError as e: self.logger.error( f"Tried to save duplicate TX, might be a catch up issue - {e}") # return block_number, tx_hash, recipient, s20 SwapTrackerObject.update_last_processed('Ethereum', block_number)
def _broadcast_and_save(self, msg: message.Submit, swap: Swap, swap_id: str): try: tx_hash = self._broadcast_transaction(msg) swap.dst_tx_hash = tx_hash swap.status = Status.SWAP_SUBMITTED self.pending_txs.append(swap_id) except (ValueError, TransactionNotFound) as e: self.logger.critical(f"Failed to broadcast transaction for msg {repr(msg)}: {e}") finally: try: swap.save() except (DuplicateKeyError, NotUniqueError): pass
def run(self): """Scans the db for unsigned swap tx and signs them""" self.logger.info("Starting..") while not self.stop_event.is_set(): failed = False for tx in Swap.objects(status=Status.SWAP_UNSIGNED): # if there are 2 transactions that depend on each other (sequence number), and the first fails we mark # the next as "retry" if failed: tx.status = Status.SWAP_RETRY continue self.logger.info(f"Found new unsigned swap event {tx}") try: self._validate_and_sign(tx) self.logger.info( f"Signed transaction successfully id:{tx.id}") except ValueError as e: self.logger.error( f'Failed to sign transaction: {tx} error: {e}') failed = True for tx in Commands.objects(status=Status.SWAP_UNSIGNED): self.logger.info(f"Found new unsigned swap event {tx}") try: self._sign_add_token(sender="", document=tx) self.logger.info( f"Signed transaction successfully id:{tx.id}") except ValueError as e: self.logger.error( f'Failed to sign transaction: {tx} error: {e}') self.stop_event.wait(self.config.sleep_interval)
def test_3_confirm_tx(web3_provider, ethr_signers, configuration: Config, erc20_contract, ethr_leader): secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ERC").dst_address assert increase_block_number(web3_provider, configuration.eth_confirmations) # To allow the new EthrSigner to "catch up", we start it after the event submission event in Ethereum # ethr_signers[-1].start() sleep(configuration.sleep_interval + 3) # Validate the tx is confirmed in the smart contract last_nonce = SwapTrackerObject.last_processed(src=secret_token_addr) assert last_nonce > -1 # account for fee fee = erc20_contract.contract.functions.balanceOf(PAYABLE_ADDRESS).call() assert fee > 0 assert TRANSFER_AMOUNT_ERC == erc20_contract.contract.functions.balanceOf( ethr_leader.signer.address).call() + fee assert increase_block_number(web3_provider, configuration.eth_confirmations) sleep(configuration.sleep_interval) last_nonce = SwapTrackerObject.last_processed(secret_token_addr) swap = Swap.objects().get(src_tx_hash=f'{last_nonce}|{secret_token_addr}') assert swap.status == Status.SWAP_CONFIRMED
def test_3_confirm_and_finalize_eth_tx(web3_provider, ethr_signers, configuration: Config): # To allow the new EthrSigner to "catch up", we start it after the event submission event in Ethereum secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ETH").dst_address prev_bal = web3_provider.eth.getBalance(zero_address, "latest") prev_bal_fee = web3_provider.eth.getBalance(PAYABLE_ADDRESS, "latest") ethr_signers[-1].start() sleep(1) assert increase_block_number(web3_provider, configuration.eth_confirmations) sleep(configuration.sleep_interval * 5) # Validate the tx is confirmed in the smart contract last_nonce = SwapTrackerObject.last_processed(secret_token_addr) # ethr_signers[-1].signer.multisig_contract.contract.functions.confirmations(last_nonce, # ethr_signers[-1].account).call() assert last_nonce >= 0 bal_fee = web3_provider.eth.getBalance(PAYABLE_ADDRESS, "latest") assert bal_fee > prev_bal_fee bal = web3_provider.eth.getBalance(zero_address, "latest") assert bal > prev_bal last_nonce = SwapTrackerObject.last_processed(secret_token_addr) swap = Swap.objects().get(src_tx_hash=f'{last_nonce}|{secret_token_addr}') assert swap.status == Status.SWAP_CONFIRMED configuration.eth_start_block = web3_provider.eth.blockNumber
def _handle_swap(self, swap_data: str, src_token: str, dst_token: str, retry=False): swap_json = swap_query_res(swap_data) # this is an id, and not the TX hash since we don't actually know where the TX happened, only the id of the # swap reported by the contract swap_id = get_swap_id(swap_json) dest_address = swap_json['destination'] self.logger.debug(f'{swap_json}') amount = int(swap_json['amount']) swap_failed = False fee = 0 data = b'' nonce = int(swap_json['nonce']) swap = None try: if dst_token == 'native': data, tx_dest, tx_amount, tx_token, fee = self._tx_native_params(amount, dest_address, retry) else: self.erc20.address = dst_token data, tx_dest, tx_amount, tx_token, fee = self._tx_erc20_params(amount, dest_address, dst_token, retry) if retry: original_nonce = nonce nonce = int(self.multisig_wallet.get_token_nonce(swap_retry_address) + 1) # + 1 to advance the counter swap = Swap.objects.get(src_tx_hash=swap_id) swap.status = Status.SWAP_FAILED self.update_retry_db(f"{original_nonce}|{tx_token}", f"{nonce}|{swap_retry_address.lower()}") tx_token = w3.toChecksumAddress(swap_retry_address) msg = message.Submit(w3.toChecksumAddress(tx_dest), tx_amount, # if we are swapping token, no ether should be rewarded nonce, tx_token, fee, data) except ValueError as e: self.logger.error(f"Error: {e}") swap_failed = True # this could have already been set by retry if not swap: swap = Swap(src_network="Secret", src_tx_hash=swap_id, unsigned_tx=data, src_coin=src_token, dst_coin=dst_token, dst_address=dest_address, amount=str(amount), dst_network="Ethereum", status=Status.SWAP_FAILED) if swap_failed or not self._validate_fee(amount, fee): self._save_failed_swap(swap, swap_id) else: self._broadcast_and_save(msg, swap, swap_id)
def _validate_and_sign(self, tx: Swap): """ Makes sure that the tx is valid and signs it :raises: ValueError """ if self._is_signed(tx): self.logger.debug( f"This signer already signed this transaction. Waiting for other signers... id:{tx.id}" ) return if not self._is_valid(tx): self.logger.error( f"Validation failed. Signer: {self.multisig.name}. Tx id:{tx.id}." ) tx.status = Status.SWAP_FAILED tx.save() raise ValueError try: signed_tx = self._sign_with_secret_cli(tx.unsigned_tx, tx.sequence) except RuntimeError as e: tx.status = Status.SWAP_FAILED tx.save() raise ValueError from e try: Signatures(tx_id=tx.id, signer=self.multisig.name, signed_tx=signed_tx).save() except OperationError as e: self.logger.error(f'Failed to save tx in database: {tx}') raise ValueError from e
def _broadcast_validation(self, document: Swap): # pylint: disable=unused-argument """validation of submitted broadcast signed tx **kwargs needs to be here even if unused, because this function gets passed arguments from mongo internals """ if not document.status == Status.SWAP_SUBMITTED: return tx_hash = document.dst_tx_hash try: res = query_data_success(tx_hash) if res and res["mint_from_ext_chain"]["status"] == "success": document.update(status=Status.SWAP_CONFIRMED) else: # maybe the block took a long time - we wait 60 seconds before we mark it as failed if (datetime.utcnow() - document.updated_on ).total_seconds() < BROADCAST_VALIDATION_COOLDOWN: return document.update(status=Status.SWAP_FAILED) self.logger.critical( f"Failed confirming broadcast for tx: {document}") except (IndexError, json.JSONDecodeError, RuntimeError) as e: self.logger.critical( f"Failed confirming broadcast for tx: {document}. Error: {e}") # This can fail, but if it does we want to crash - this can lead to duplicate amounts and confusion # Better to just stop and make sure everything is kosher before continuing document.update(status=Status.SWAP_FAILED)
def run(self): """Scans for signed transactions and updates status if multisig threshold achieved""" self.logger.info("Starting..") to_block = w3.eth.blockNumber - self.config.eth_confirmations self.catch_up(to_block) self.event_listener.start() self.logger.info("Done catching up") while not self.stop_signal.is_set(): for transaction in Swap.objects(status=Status.SWAP_RETRY, src_network="Ethereum"): self._retry(transaction) for transaction in Swap.objects(status=Status.SWAP_UNSIGNED): self.handle_unsigned_tx(transaction) for transaction in Commands.objects(status=Status.SWAP_UNSIGNED): self.handle_unsigned_tx(transaction) self.stop_signal.wait(self.config.sleep_interval)
def _scan_swap(self): """ Scans secret network contract for swap events """ self.logger.info(f'Starting for account {self.signer.address} with tokens: {self.token_map=}') while not self.stop_event.is_set(): num_of_tokens = TokenPairing.objects(src_network=self.network).count() if num_of_tokens != len(self.token_map.keys()): self._refresh_token_map() self.logger.info(f'Refreshed tracked tokens. Now tracking {len(self.token_map.keys())} tokens') for transaction in Swap.objects(status=Status.SWAP_RETRY, src_network="Secret"): # self._handle_swap(swap_data, token, self.token_map[token].address) try: token, nonce = _parse_db_tx(transaction) swap_data = query_scrt_swap(nonce, self.config.scrt_swap_address, token) # self._retry(transaction) self._handle_swap(swap_data, token, self.token_map[token].address, True) except Exception as e: # pylint: disable=broad-except self.logger.error(f'Failed to retry swap: {e}') transaction.update(status=Status.SWAP_FAILED) for token in self.token_map: try: swap_tracker = SwapTrackerObject.get_or_create(src=token) next_nonce = swap_tracker.nonce + 1 self.logger.debug(f'Scanning token {token} for query #{next_nonce}') swap_data = query_scrt_swap(next_nonce, self.config.scrt_swap_address, token) self._handle_swap(swap_data, token, self.token_map[token].address) swap_tracker.nonce = next_nonce swap_tracker.save() next_nonce += 1 except CalledProcessError as e: if b'ERROR: query result: encrypted: Failed to get swap for token' not in e.stderr: self.logger.error(f"Failed to query swap: stdout: {e.stdout} stderr: {e.stderr}") # if b'ERROR: query result: encrypted: Failed to get swap for key' not in e.stderr: self.stop_event.wait(self.config.sleep_interval)
def _broadcast_validation(self, document: Swap) -> bool: # pylint: disable=unused-argument """validation of submitted broadcast signed tx **kwargs needs to be here even if unused, because this function gets passed arguments from mongo internals """ if not document.status == Status.SWAP_SUBMITTED or not document.src_network == "Ethereum": return False tx_hash = document.dst_tx_hash try: res = query_data_success(tx_hash) if res and res["mint_from_ext_chain"]["status"] == "success": self.logger.info("Updated status to confirmed") document.update(status=Status.SWAP_CONFIRMED) return True # maybe the block took a long time - we wait 60 seconds before we mark it as failed # The returned value is just here to let us know if we need to retry the next transactions if (datetime.utcnow() - document.updated_on ).total_seconds() < BROADCAST_VALIDATION_COOLDOWN: return True # TX isn't on-chain. We can retry it document.update(status=Status.SWAP_RETRY) # update sequence number - just in case we failed because we are out of sync self.manager.update_sequence() self.logger.critical( f"Failed confirming broadcast for tx: {repr(document)}, Hash: {tx_hash}, res: {res}" ) return False except (ValueError, KeyError) as e: # TX failed for whatever reason. Might be a duplicate, out of gas, or any other reason self.logger.error( f"Failed confirming broadcast for tx: {repr(document)}. Error: {e}" ) # The DB update can fail, but if it does we want to crash - this can lead to # duplicate amounts and confusion. Better to just stop and make sure # everything is kosher before continuing document.update(status=Status.SWAP_FAILED) self.manager.update_sequence() return False
def _catch_up(self): """ Scans the DB for signed swap tx at startup """ # Note: As Collection.objects() call is cached, there shouldn't be collisions with DB signals for tx in Swap.objects(status=Status.SWAP_SIGNED).order_by('sequence'): self._create_and_broadcast(tx)
def _set_retry(tx: Swap): tx.status = Status.SWAP_RETRY tx.save()
def get_swap(nonce, token): return Swap.objects().get(src_tx_hash=build_hash(nonce, token))
def test_11_swap_erc_to_s20(scrt_leader, scrt_signers, web3_provider, configuration: Config, erc20_contract, multisig_wallet, ethr_leader): secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ERC").dst_address # scrt_leader.start() # for signer in scrt_signers: # signer.start() t1_address = get_key_signer( "t1", Path.joinpath(project_base_path(), configuration.path_to_keys))['address'] # swap ethr for secret20 token, deliver tokens to address of 'a'. # (we will use 'a' later to check it received the money) # add usdt to the whitelisted token list account = web3_provider.eth.account.from_key(configuration.leader_key) nonce = web3_provider.eth.getTransactionCount(account.address, "pending") tx = multisig_wallet.contract.functions.addToken(erc20_contract.address, 1) raw_tx = tx.buildTransaction(transaction={ 'from': account.address, 'gas': 3000000, 'nonce': nonce }) signed_tx = account.sign_transaction(raw_tx) tx_hash = web3_provider.eth.sendRawTransaction(signed_tx.rawTransaction) # Get transaction hash from deployed contract tx_receipt = web3_provider.eth.waitForTransactionReceipt(tx_hash) # this will likely fail since the test before also allocates the allowance - just ignore if it fails try: _ = erc20_contract.contract.functions.approve(multisig_wallet.address, TRANSFER_AMOUNT_ERC). \ transact({'from': web3_provider.eth.coinbase}) except ValueError: pass tx_hash = multisig_wallet\ .contract\ .functions\ .swapToken(t1_address.encode(), TRANSFER_AMOUNT_ERC, erc20_contract.address)\ .transact({'from': web3_provider.eth.coinbase}).hex().lower() assert TRANSFER_AMOUNT_ERC == erc20_contract.contract.functions.balanceOf( multisig_wallet.address).call() # increase number of blocks to reach the confirmation threshold assert increase_block_number(web3_provider, configuration.eth_confirmations - 1) sleep(configuration.sleep_interval + 2) assert Swap.objects(src_tx_hash=tx_hash).count( ) == 0 # verify blocks confirmation threshold wasn't meet assert increase_block_number(web3_provider, 1) # add the 'missing' confirmation block # give event listener and manager time to process tx sleep(configuration.sleep_interval + 2) assert Swap.objects( src_tx_hash=tx_hash).count() == 1 # verify swap event recorded swap = Swap.objects(src_tx_hash=tx_hash).get() sleep(1) # check signers were notified of the tx and signed it assert Signatures.objects(tx_id=swap.id).count() == len(scrt_signers) # give time for manager to process the signatures sleep(configuration.sleep_interval + 2) assert Swap.objects().get( src_tx_hash=tx_hash).status in (Status.SWAP_SUBMITTED, Status.SWAP_CONFIRMED) _, log = event_log(tx_hash, ['SwapToken'], web3_provider, multisig_wallet.contract) transfer_amount = multisig_wallet.extract_amount(log) dest = multisig_wallet.extract_addr(log) # validate swap tx on ethr delivers to the destination viewing_key_set = '{"set_viewing_key": {"key": "lol"}}' _ = run( f"secretcli tx compute execute {secret_token_addr} " f"'{viewing_key_set}' --from {dest} -b block -y | jq '.txhash'", shell=True, stdout=PIPE) sleep(6) balance = f'{{"balance": {{"key": "lol", "address": "{dest}"}} }}' res = run(f"secretcli q compute query {secret_token_addr} " f"'{balance}'", shell=True, stdout=PIPE) print(f"{res.stdout=}") amount = json.loads(res.stdout)["balance"]["amount"] print(f"swap amount: {transfer_amount}, dest balance amount: {amount}") # give scrt_leader time to multi-sign already existing signatures sleep(configuration.sleep_interval + 5) assert Swap.objects().get( src_tx_hash=tx_hash).status == Status.SWAP_CONFIRMED
def test_1_swap_eth_to_s20(setup, scrt_signers, scrt_leader, web3_provider, configuration: Config, multisig_wallet): secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ETH").dst_address scrt_leader.start() for signer in scrt_signers: signer.start() fee_collector = multisig_wallet.contract.functions.getFeeCollector().call() print(f"{fee_collector=}") t1_address = get_key_signer( "t1", Path.joinpath(project_base_path(), configuration.path_to_keys))['address'] # swap ethr for secret20 token, deliver tokens to address of 'a' # (we will use 'a' later to check it received the money) print( f"Creating new swap transaction at {web3_provider.eth.getBlock('latest').number + 1}" ) tx_hash = multisig_wallet.contract.functions.swap(t1_address.encode()). \ transact({'from': web3_provider.eth.coinbase, 'value': TRANSFER_AMOUNT_ETH}).hex().lower() # TODO: validate ethr increase of the smart contract # increase number of blocks to reach the confirmation threshold assert increase_block_number(web3_provider, configuration.eth_confirmations - 1) sleep(configuration.sleep_interval + 2) assert Swap.objects(src_tx_hash=tx_hash).count( ) == 0 # verify blocks confirmation threshold wasn't meet assert increase_block_number(web3_provider, 1) # add the 'missing' confirmation block # give event listener and manager time to process tx sleep(configuration.sleep_interval + 5) assert Swap.objects( src_tx_hash=tx_hash).count() == 1 # verify swap event recorded sleep(1) # check signers were notified of the tx and signed it assert Signatures.objects().count() == len(scrt_signers) sleep(5) if Swap.objects().get().status == Status.SWAP_SUBMITTED: sleep(configuration.sleep_interval + 5) assert Swap.objects().get().status == Status.SWAP_CONFIRMED # get tx details tx_hash = Swap.objects().get().src_tx_hash _, log = event_log(tx_hash, ['Swap'], web3_provider, multisig_wallet.contract) transfer_amount = log.args.amount dest = log.args.recipient.decode() # validate swap tx on ethr delivers to the destination viewing_key_set = '{"set_viewing_key": {"key": "lol"}}' tx_hash = run( f"secretcli tx compute execute {secret_token_addr} " f"'{viewing_key_set}' --from {dest} -b block -y | jq '.txhash'", shell=True, stdout=PIPE) sleep(6) balance = f'{{"balance": {{"key": "lol", "address": "{dest}"}} }}' res = run(f"secretcli q compute query {secret_token_addr} " f"'{balance}'", shell=True, stdout=PIPE) print(f"{res.stdout=}") amount = json.loads(res.stdout)["balance"]["amount"] print(f"swap amount: {transfer_amount}, dest balance amount: {amount}") assert int(amount) == log.args.amount
def test_1_swap_erc_to_s20(setup, scrt_leader, scrt_signers, web3_provider, configuration: Config, erc20_contract, multisig_wallet): scrt_leader.start() for signer in scrt_signers: signer.start() t1_address = get_key_signer( "t1", Path.joinpath(project_base_path(), configuration['path_to_keys']))['address'] # swap ethr for secret20 token, deliver tokens to address of 'a'. # (we will use 'a' later to check it received the money) tx_hash = erc20_contract.contract.functions.transfer(multisig_wallet.address, TRANSFER_AMOUNT, t1_address.encode()). \ transact({'from': web3_provider.eth.coinbase}).hex().lower() assert TRANSFER_AMOUNT == erc20_contract.contract.functions.balanceOf( multisig_wallet.address).call() # increase number of blocks to reach the confirmation threshold assert increase_block_number(web3_provider, configuration['eth_confirmations'] - 1) sleep(configuration['sleep_interval'] + 2) assert Swap.objects(src_tx_hash=tx_hash).count( ) == 0 # verify blocks confirmation threshold wasn't meet assert increase_block_number(web3_provider, 1) # add the 'missing' confirmation block # give event listener and manager time to process tx sleep(configuration['sleep_interval'] + 2) assert Swap.objects( src_tx_hash=tx_hash).count() == 1 # verify swap event recorded sleep(1) # check signers were notified of the tx and signed it assert Signatures.objects().count() == len(scrt_signers) # give time for manager to process the signatures sleep(configuration['sleep_interval'] + 2) assert Swap.objects().get().status == Status.SWAP_SUBMITTED # get tx details tx_hash = Swap.objects().get().src_tx_hash _, log = event_log(tx_hash, ['Transfer'], web3_provider, erc20_contract.contract) transfer_amount = erc20_contract.extract_amount(log) dest = erc20_contract.extract_addr(log) # validate swap tx on ethr delivers to the destination balance_query = '{"balance": {}}' tx_hash = run( f"secretcli tx compute execute {configuration['secret_contract_address']} " f"'{balance_query}' --from {dest} -b block -y | jq '.txhash'", shell=True, stdout=PIPE) tx_hash = tx_hash.stdout.decode().strip()[1:-1] res = run( f"secretcli q compute tx {tx_hash} | jq '.output_log' | jq '.[0].attributes' " f"| jq '.[3].value'", shell=True, stdout=PIPE).stdout.decode().strip()[1:-1] end_index = res.find(' ') amount = Decimal(res[:end_index]) print(f"tx amount: {transfer_amount}, swap amount: {amount}") # assert abs(transfer_amount - amount) < 1 ?????????????????????? # give scrt_leader time to multi-sign already existing signatures sleep(configuration['sleep_interval'] + 3) assert Swap.objects().get().status == Status.SWAP_CONFIRMED
def _handle_swap(self, swap_data: str, src_token: str, dst_token: str): swap_json = swap_query_res(swap_data) # this is an id, and not the TX hash since we don't actually know where the TX happened, only the id of the # swap reported by the contract swap_id = get_swap_id(swap_json) dest_address = swap_json['destination'] self.logger.info(f'{swap_json}') amount = int(swap_json['amount']) if dst_token == 'native': data, tx_dest, tx_amount, tx_token, fee = self._tx_native_params( amount, dest_address) else: self.erc20.address = dst_token data, tx_dest, tx_amount, tx_token, fee = self._tx_erc20_params( amount, dest_address, dst_token) if not self._validate_fee(amount, fee): self.logger.error("Tried to swap an amount too low to cover fee") swap = Swap(src_network="Secret", src_tx_hash=swap_id, unsigned_tx=data, src_coin=src_token, dst_coin=dst_token, dst_address=dest_address, amount=str(amount), dst_network="Ethereum", status=Status.SWAP_FAILED) try: swap.save() except (DuplicateKeyError, NotUniqueError): pass return msg = message.Submit( tx_dest, tx_amount, # if we are swapping token, no ether should be rewarded int(swap_json['nonce']), tx_token, fee, data) # todo: check we have enough ETH swap = Swap(src_network="Secret", src_tx_hash=swap_id, unsigned_tx=data, src_coin=src_token, dst_coin=dst_token, dst_address=dest_address, amount=str(amount), dst_network="Ethereum", status=Status.SWAP_FAILED) try: tx_hash = self._broadcast_transaction(msg) swap.dst_tx_hash = tx_hash swap.status = Status.SWAP_SUBMITTED except (ValueError, TransactionNotFound) as e: self.logger.critical( f"Failed to broadcast transaction for msg {repr(msg)}: {e}") finally: try: swap.save() except (DuplicateKeyError, NotUniqueError): pass