def test_resending_mined_transaction_raises(deploy_client: JSONRPCClient) -> None: """ If a mined transaction is re-sent the exception `EthereumNonceTooLow` is raised. """ # Use a _fixed_ gas price strategy so that both transactions are identical. deploy_client.web3.eth.setGasPriceStrategy(make_fixed_gas_price_strategy(GasPrice(2000000000))) contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode(to_checksum_address(address))) > 0 # Create a new instance of the JSONRPCClient, this will store the current available nonce client_invalid_nonce = JSONRPCClient(deploy_client.web3, deploy_client.privkey) check_block = deploy_client.get_checking_block() gas_estimate = contract_proxy.estimate_gas(check_block, "ret") assert gas_estimate, "Gas estimation should not fail here" startgas = safe_gas_limit(gas_estimate) txhash = contract_proxy.transact("ret", startgas) deploy_client.poll(txhash) # At this point `client_invalid_nonce` has a nonce that is `1` too low, # since a transaction was sent using `deploy_client` above and these two # instances share the same underlying private key. # # Note that the same function is called in this test. with pytest.raises(EthereumNonceTooLow): client_invalid_nonce.new_contract_proxy( abi=contract_proxy.contract.abi, contract_address=contract_proxy.contract_address ).transact("ret", startgas)
def test_resending_pending_transaction_raises(deploy_client: JSONRPCClient) -> None: """ If a pending transaction is re-sent the exception `EthereumNonceTooLow` is raised. This tests is only sufficient because of the companion test `test_resending_mined_transaction_raises` which shows that if the transaction has been mined a different exception is raised. """ # Use a _fixed_ gas price strategy so that both transactions are identical. deploy_client.web3.eth.setGasPriceStrategy(make_fixed_gas_price_strategy(GasPrice(2000000000))) contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode(to_checksum_address(address))) > 0 # Create a new instance of the JSONRPCClient, this will store the current available nonce client_invalid_nonce = JSONRPCClient(web3=deploy_client.web3, privkey=deploy_client.privkey) check_block = deploy_client.get_checking_block() gas_estimate = contract_proxy.estimate_gas(check_block, "ret") assert gas_estimate, "Gas estimation should not fail here" startgas = safe_gas_limit(gas_estimate) # At this point `client_invalid_nonce` has a nonce that is `1` too low, # since a transaction was sent using `deploy_client` above and these two # instances share the same underlying private key. # # Note that it is assumed this runs fast enough so that the first transaction is not # mined before second is sent. contract_proxy.transact("ret", startgas) with pytest.raises(EthereumNonceTooLow): client_invalid_nonce.new_contract_proxy( abi=contract_proxy.contract.abi, contract_address=contract_proxy.contract_address ).transact("ret", startgas)
def test_reusing_nonce_with_lower_gas_raises(deploy_client: JSONRPCClient) -> None: """ If a _new_ transaction is sent but with a lower gas the exception `ReplacementTransactionUnderpriced` is raised. """ # Use a _decreasing_ gas price strategy so that the second transactions is # lower than the first. deploy_client.web3.eth.setGasPriceStrategy( make_decreasing_gas_price_strategy(GasPrice(2000000000)) ) contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode(to_checksum_address(address))) > 0 client_invalid_nonce = JSONRPCClient(web3=deploy_client.web3, privkey=deploy_client.privkey) check_block = deploy_client.get_checking_block() gas_estimate = contract_proxy.estimate_gas(check_block, "ret") assert gas_estimate, "Gas estimation should not fail here" startgas = safe_gas_limit(gas_estimate) contract_proxy.transact("ret", startgas) # At this point `client_invalid_nonce` has a nonce that is `1` too low, # since a transaction was sent using `deploy_client` above and these two # instances share the same underlying private key. # # Note that the same function is called in this test but the gas is decreasing. with pytest.raises(ReplacementTransactionUnderpriced): client_invalid_nonce.new_contract_proxy( abi=contract_proxy.contract.abi, contract_address=contract_proxy.contract_address ).transact("ret_str", contract_proxy.estimate_gas(check_block, "ret_str"))
def test_filter_start_block_inclusive(deploy_client): """ A filter includes events from the block given in from_block """ contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") check_block = deploy_client.get_checking_block() # call the create event function twice and wait for confirmation each time startgas = safe_gas_limit( contract_proxy.estimate_gas(check_block, "createEvent", 1)) transaction_1 = contract_proxy.transact("createEvent", startgas, 1) deploy_client.poll(transaction_1) transaction_2 = contract_proxy.transact("createEvent", startgas, 2) deploy_client.poll(transaction_2) result_1 = deploy_client.get_filter_events(contract_proxy.contract_address) block_number_events = get_list_of_block_numbers(result_1) block_number_event_1 = block_number_events[0] block_number_event_2 = block_number_events[1] # inclusive from_block should return both events result_2 = deploy_client.get_filter_events(contract_proxy.contract_address, from_block=block_number_event_1) assert get_list_of_block_numbers(result_2) == block_number_events # a higher from_block must not contain the first event result_3 = deploy_client.get_filter_events( contract_proxy.contract_address, from_block=block_number_event_1 + 1) assert get_list_of_block_numbers(result_3) == [block_number_event_2]
def send_transaction(): check_block = deploy_client.get_checking_block() startgas = contract_proxy.estimate_gas(check_block, "waste_storage", iterations) startgas = safe_gas_limit(startgas) transaction = contract_proxy.transact("waste_storage", startgas, iterations) deploy_client.poll(transaction) return deploy_client.get_transaction_receipt(transaction)
def test_transact_throws_opcode(deploy_client: JSONRPCClient) -> None: """ The receipt status field of a transaction that hit an assert or require is 0x0 """ contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") address = contract_proxy.address assert len(deploy_client.web3.eth.getCode(address)) > 0 # the method always fails, so the gas estimation returns 0 here, using a # hardcoded a value to circumvent gas estimation. estimated_gas = safe_gas_limit(22000) gas_price = gas_price_for_fast_transaction(deploy_client.web3) block = deploy_client.get_block(BLOCK_ID_LATEST) estimated_transaction_fail_assert = TransactionEstimated( from_address=address, data=SmartContractCall(contract_proxy, "fail_assert", (), {}, value=0), eth_node=deploy_client.eth_node, extra_log_details={}, estimated_gas=estimated_gas, gas_price=gas_price, approximate_block=(block["hash"], block["number"]), ) transaction_fail_assert_sent = deploy_client.transact( estimated_transaction_fail_assert) transaction_fail_assert_mined = deploy_client.poll_transaction( transaction_fail_assert_sent) msg = "Transaction must have failed" assert not was_transaction_successfully_mined( transaction_fail_assert_mined), msg estimated_transaction_fail_require = TransactionEstimated( from_address=address, data=SmartContractCall(contract_proxy, "fail_require", (), {}, value=0), eth_node=deploy_client.eth_node, extra_log_details={}, estimated_gas=estimated_gas, gas_price=gas_price, approximate_block=(block["hash"], block["number"]), ) transaction_fail_require_sent = deploy_client.transact( estimated_transaction_fail_require) transaction_fail_require_mined = deploy_client.poll_transaction( transaction_fail_require_sent) msg = "Transaction must have failed" assert not was_transaction_successfully_mined( transaction_fail_require_mined), msg
def test_transact_opcode_oog(deploy_client: JSONRPCClient) -> None: """ The receipt status field of a transaction that did NOT throw is 0x0. """ contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode( to_checksum_address(address))) > 0 # divide the estimate by 2 to run into out-of-gas check_block = deploy_client.get_checking_block() startgas = safe_gas_limit( contract_proxy.estimate_gas(check_block, "loop", 1000)) // 2 transaction = contract_proxy.transact("loop", startgas, 1000) receipt = deploy_client.poll(transaction) assert check_transaction_threw(receipt=receipt), "must not be empty"
def test_transact_throws_opcode(deploy_client: JSONRPCClient) -> None: """ The receipt status field of a transaction that hit an assert or require is 0x0 """ contract_proxy, _ = deploy_rpc_test_contract(deploy_client, "RpcTest") address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode( to_checksum_address(address))) > 0 # the gas estimation returns 0 here, so hardcode a value startgas = safe_gas_limit(22000) transaction = contract_proxy.transact("fail_assert", startgas) receipt = deploy_client.poll(transaction) assert check_transaction_threw(receipt=receipt), "must not be empty" transaction = contract_proxy.transact("fail_require", startgas) receipt = deploy_client.poll(transaction) assert check_transaction_threw(receipt=receipt), "must not be empty"
def _deposit( self, beneficiary: Address, token: Token, total_deposit: TokenAmount, amount_to_deposit: TokenAmount, log_details: Dict[str, Any], ) -> None: token.approve(allowed_address=Address(self.address), allowance=amount_to_deposit) checking_block = self.client.get_checking_block() gas_limit = self.proxy.estimate_gas(checking_block, "deposit", to_checksum_address(beneficiary), total_deposit) if not gas_limit: failed_at = self.proxy.rpc_client.get_block("latest") failed_at_blocknumber = failed_at["number"] self.proxy.rpc_client.check_for_insufficient_eth( transaction_name="deposit", transaction_executed=False, required_gas=self.gas_measurements["UserDeposit.deposit"], block_identifier=failed_at_blocknumber, ) latest_deposit = self.get_total_deposit( address=self.node_address, block_identifier=failed_at_blocknumber) amount_to_deposit = TokenAmount(total_deposit - latest_deposit) allowance = token.allowance( owner=self.node_address, spender=Address(self.address), block_identifier=failed_at_blocknumber, ) whole_balance = self.whole_balance( block_identifier=failed_at_blocknumber) whole_balance_limit = self.whole_balance_limit( block_identifier=failed_at_blocknumber) if allowance < amount_to_deposit: msg = ( "The allowance is insufficient. Check concurrent deposits " "for the same user deposit but different proxies.") raise RaidenRecoverableError(msg) if token.balance_of(self.node_address, failed_at_blocknumber) < amount_to_deposit: msg = "The address doesnt have enough tokens" raise RaidenRecoverableError(msg) if latest_deposit < total_deposit: msg = "Deposit amount did not increase after deposit transaction" raise RaidenRecoverableError(msg) if whole_balance + amount_to_deposit > UINT256_MAX: msg = ( f"Current whole balance is {whole_balance}. " f"The new deposit of {amount_to_deposit} would lead to an overflow." ) raise RaidenRecoverableError(msg) if whole_balance + amount_to_deposit > whole_balance_limit: msg = ( f"Current whole balance is {whole_balance}. " f"With the new deposit of {amount_to_deposit}, the deposit " f"limit of {whole_balance_limit} would be exceeded.") raise RaidenRecoverableError(msg) raise RaidenRecoverableError("Deposit failed of unknown reason") else: gas_limit = safe_gas_limit(gas_limit) log_details["gas_limit"] = gas_limit transaction_hash = self.proxy.transact( "deposit", gas_limit, to_checksum_address(beneficiary), total_deposit) receipt = self.client.poll(transaction_hash) failed_receipt = check_transaction_threw(receipt=receipt) if failed_receipt: failed_at_blocknumber = failed_receipt["blockNumber"] latest_deposit = self.get_total_deposit( address=self.node_address, block_identifier=failed_at_blocknumber) amount_to_deposit = TokenAmount(total_deposit - latest_deposit) allowance = token.allowance( owner=self.node_address, spender=Address(self.address), block_identifier=failed_at_blocknumber, ) whole_balance = self.whole_balance( block_identifier=failed_at_blocknumber) whole_balance_limit = self.whole_balance_limit( block_identifier=failed_at_blocknumber) if latest_deposit >= total_deposit: msg = "Deposit amount already increased after another transaction" raise RaidenRecoverableError(msg) if allowance < amount_to_deposit: msg = ( "The allowance is insufficient. Check concurrent deposits " "for the same token network but different proxies.") raise RaidenRecoverableError(msg) # Because we acquired the lock for the token, and the gas estimation succeeded, # We know that the account had enough balance for the deposit transaction. if token.balance_of(self.node_address, failed_at_blocknumber) < amount_to_deposit: msg = ( f"Transaction failed and balance decreased unexpectedly. " f"This could be a bug in Raiden or a mallicious " f"ERC20 Token.") raise RaidenRecoverableError(msg) if whole_balance + amount_to_deposit > UINT256_MAX: msg = ( f"Current whole balance is {whole_balance}. " f"The new deposit of {amount_to_deposit} caused an overflow." ) raise RaidenRecoverableError(msg) if whole_balance + amount_to_deposit > whole_balance_limit: msg = ( f"Current whole balance is {whole_balance}. " f"With the new deposit of {amount_to_deposit}, the deposit " f"limit of {whole_balance_limit} was exceeded.") raise RaidenRecoverableError(msg) if latest_deposit < total_deposit: msg = "Deposit amount did not increase after deposit transaction" raise RaidenRecoverableError(msg) raise RaidenRecoverableError( "Deposit failed of unknown reason")
def _init( self, monitoring_service_address: MonitoringServiceAddress, one_to_n_address: OneToNAddress, log_details: Dict[str, Any], ) -> None: checking_block = self.client.get_checking_block() gas_limit = self.proxy.estimate_gas( checking_block, "init", to_checksum_address(monitoring_service_address), to_checksum_address(one_to_n_address), ) if not gas_limit: failed_at = self.proxy.rpc_client.get_block("latest") failed_at_blocknumber = failed_at["number"] self.proxy.rpc_client.check_for_insufficient_eth( transaction_name="init", transaction_executed=False, required_gas=self.gas_measurements["UserDeposit.init"], block_identifier=failed_at_blocknumber, ) existing_monitoring_service_address = self.monitoring_service_address( block_identifier=failed_at_blocknumber) existing_one_to_n_address = self.one_to_n_address( block_identifier=failed_at_blocknumber) if existing_monitoring_service_address != EMPTY_ADDRESS: msg = ( f"MonitoringService contract address was set to " f"{to_checksum_address(existing_monitoring_service_address)}" ) raise RaidenRecoverableError(msg) if existing_one_to_n_address != EMPTY_ADDRESS: msg = (f"OneToN contract address was set to " f"{to_checksum_address(existing_one_to_n_address)}") raise RaidenRecoverableError(msg) raise RaidenRecoverableError("Deposit failed of unknown reason") else: gas_limit = safe_gas_limit(gas_limit) log_details["gas_limit"] = gas_limit transaction_hash = self.proxy.transact( "init", gas_limit, to_checksum_address(monitoring_service_address), to_checksum_address(one_to_n_address), ) receipt = self.client.poll(transaction_hash) failed_receipt = check_transaction_threw(receipt=receipt) if failed_receipt: failed_at_blocknumber = failed_receipt["blockNumber"] existing_monitoring_service_address = self.monitoring_service_address( block_identifier=failed_at_blocknumber) existing_one_to_n_address = self.one_to_n_address( block_identifier=failed_at_blocknumber) if existing_monitoring_service_address != EMPTY_ADDRESS: msg = ( f"MonitoringService contract address was set to " f"{to_checksum_address(existing_monitoring_service_address)}" ) raise RaidenRecoverableError(msg) if existing_one_to_n_address != EMPTY_ADDRESS: msg = (f"OneToN contract address was set to " f"{to_checksum_address(existing_one_to_n_address)}") raise RaidenRecoverableError(msg) raise RaidenRecoverableError( "Deposit failed of unknown reason")
def _register_secret_batch( self, secrets_to_register: List[Secret], transaction_result: AsyncResult, log_details: Dict[str, Any], ) -> None: estimated_transaction = self.client.estimate_gas( self.proxy, "registerSecretBatch", log_details, secrets_to_register ) msg = None transaction_mined = None if estimated_transaction is not None: estimated_transaction.estimated_gas = safe_gas_limit( estimated_transaction.estimated_gas, len(secrets_to_register) * GAS_REQUIRED_PER_SECRET_IN_BATCH, ) try: transaction_sent = self.client.transact(estimated_transaction) transaction_mined = self.client.poll_transaction(transaction_sent) except Exception as e: # pylint: disable=broad-except msg = f"Unexpected exception {e} at sending registerSecretBatch transaction." # Clear `open_secret_transactions` regardless of the transaction being # successfully executed or not. with self._open_secret_transactions_lock: for secret in secrets_to_register: self.open_secret_transactions.pop(secret) # As of version `0.4.0` of the contract has *no* asserts or requires. # Therefore the only reason for the transaction to fail is if there is # a bug. unrecoverable_error = transaction_mined is None or not was_transaction_successfully_mined( transaction_mined ) exception: Union[RaidenRecoverableError, RaidenUnrecoverableError] if unrecoverable_error: # If the transaction was sent it must not fail. If this happened # some of our assumptions is broken therefore the error is # unrecoverable if transaction_mined is not None: receipt = transaction_mined.receipt if receipt["gasUsed"] == transaction_mined.startgas: # The transaction failed and all gas was used. This can # happen because of: # # - A compiler bug if an invalid opcode was executed. # - A configuration bug if an assert was executed, # because version 0.4.0 of the secret registry does not have an # assert. # - An ethereum client bug if the gas_limit was # underestimated. # # Safety cannot be guaranteed under any of these cases, # this error is unrecoverable. error = ( "Secret registration failed because of a bug in either " "the solidity compiler, the running ethereum client, or " "a configuration error in Raiden." ) else: # The transaction failed and *not* all gas was used. This # can happen because of: # # - A compiler bug if a revert was introduced. # - A configuration bug, because for 0.4.0 the secret # registry does not have a revert. error = ( "Secret registration failed because of a configuration " "bug or compiler bug. Please double check the secret " "smart contract is at version 0.4.0, if it is then a " "compiler bug was hit." ) exception = RaidenUnrecoverableError(error) transaction_result.set_exception(exception) raise exception # If gas_limit is set and there is no receipt then an exception was # raised while sending the transaction. This should only happen if # the account is being used concurrently, which is not supported. # This can happen because: # # - The nonce of the transaction was already used # - The nonce was reused *and* the account didn't have enough ether # to pay for the gas # # Safety cannot be guaranteed under any of these cases, this error # is unrecoverable. *Note*: This assumes the ethereum client # takes into account the current transactions in the pool. if estimated_transaction is not None: assert msg, "Unexpected control flow, an exception should have been raised." error = ( f"Sending the the transaction for registerSecretBatch " f"failed with: `{msg}`. This happens if the same ethereum " f"account is being used by more than one program which is not " f"supported." ) exception = RaidenUnrecoverableError(error) transaction_result.set_exception(exception) raise exception # gas_limit can fail because: # # - The Ethereum client detected the transaction could not # successfully execute, this happens if an assert/revert is hit. # - The account is lacking funds to pay for the gas. # # Either of these is a bug. The contract does not use # assert/revert, and the account should always be funded self.client.check_for_insufficient_eth( transaction_name="registerSecretBatch", transaction_executed=True, required_gas=GAS_REQUIRED_PER_SECRET_IN_BATCH * len(secrets_to_register), block_identifier=self.client.get_checking_block(), ) error = "Call to registerSecretBatch couldn't be done" exception = RaidenRecoverableError(error) transaction_result.set_exception(exception) raise exception # The local **MUST** transaction_result be set before waiting for the # other results, otherwise we have a dead-lock assert transaction_mined is not None, MYPY_ANNOTATION transaction_result.set(transaction_mined.transaction_hash)
def _add_token( self, token_address: TokenAddress, channel_participant_deposit_limit: TokenAmount, token_network_deposit_limit: TokenAmount, log_details: Dict[Any, Any], ) -> Tuple[TransactionHash, TokenNetworkAddress]: token_network_address = None kwargs = { "_token_address": token_address, "_channel_participant_deposit_limit": channel_participant_deposit_limit, "_token_network_deposit_limit": token_network_deposit_limit, } estimated_transaction = self.rpc_client.estimate_gas( self.proxy, "createERC20TokenNetwork", log_details, **kwargs) if estimated_transaction is not None: estimated_transaction.estimated_gas = safe_gas_limit( estimated_transaction.estimated_gas, self.gas_measurements[ "TokenNetworkRegistry createERC20TokenNetwork"], ) transaction_sent = self.rpc_client.transact(estimated_transaction) transaction_mined = self.rpc_client.poll_transaction( transaction_sent) receipt = transaction_mined.receipt if not was_transaction_successfully_mined(transaction_mined): failed_at_blocknumber = BlockNumber(receipt["blockNumber"]) max_token_networks = self.get_max_token_networks( block_identifier=failed_at_blocknumber) token_networks_created = self.get_token_network_created( block_identifier=failed_at_blocknumber) already_registered = self.get_token_network( token_address=token_address, block_identifier=failed_at_blocknumber) deprecation_executor = self.get_deprecation_executor( block_identifier=failed_at_blocknumber) settlement_timeout_min = self.settlement_timeout_min( block_identifier=failed_at_blocknumber) settlement_timeout_max = self.settlement_timeout_max( block_identifier=failed_at_blocknumber) chain_id = self.get_chain_id( block_identifier=failed_at_blocknumber) secret_registry_address = self.get_secret_registry_address( block_identifier=failed_at_blocknumber) try: # Creating a new instance to run the constructor checks. token_proxy = Token( jsonrpc_client=self.rpc_client, token_address=token_address, contract_manager=self.proxy_manager.contract_manager, block_identifier=failed_at_blocknumber, ) except AddressWithoutCode: # This cannot be an unrecoverable error, since the ERC20 # code is external. raise RaidenRecoverableError( "Token disappeared! The address " f"{to_checksum_address(token_address)} did have code at " f"block {log_details['given_block_identifier']}, however " f"at block {failed_at_blocknumber} when the registration " "transaction was mined the address didn't have code " "anymore.") check_transaction_failure(transaction_mined, self.rpc_client) check_address_has_code_handle_pruned_block( client=self.rpc_client, address=Address(secret_registry_address), contract_name=CONTRACT_SECRET_REGISTRY, expected_code=decode_hex( self.proxy_manager.contract_manager. get_runtime_hexcode(CONTRACT_SECRET_REGISTRY)), given_block_identifier=failed_at_blocknumber, ) if token_networks_created >= max_token_networks: raise RaidenRecoverableError( "The number of existing token networks reached the maximum allowed" ) if already_registered: # Race condition lost, the token network was created in a different # transaction which got mined first. raise RaidenRecoverableError( "The token was already registered in the TokenNetworkRegistry." ) if deprecation_executor == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The deprecation executor property for the " "TokenNetworkRegistry is invalid.") if chain_id == 0: raise RaidenUnrecoverableError( "The chain ID property for the TokenNetworkRegistry is invalid." ) if chain_id != self.rpc_client.chain_id: raise RaidenUnrecoverableError( f"The provided chain ID {chain_id} does not match the " f"network Raiden is running on: {self.rpc_client.chain_id}." ) if secret_registry_address == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The secret registry address for the token network is invalid." ) if settlement_timeout_min == 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_max <= settlement_timeout_min: raise RaidenUnrecoverableError( "The maximum settlement timeout for the token network " "should be larger than the minimum settlement timeout." ) total_supply = token_proxy.total_supply( block_identifier=failed_at_blocknumber) if not total_supply or total_supply <= 0: raise RaidenRecoverableError( f"The given token address is not a valid ERC20 token, " f"total_supply() returned an invalid value {total_supply}." ) # At this point, the TokenNetworkRegistry fails to instantiate # a new TokenNetwork. raise RaidenUnrecoverableError( "createERC20TokenNetwork failed for an unknown reason, even " "though the gas estimation succeeded.") succeeded_at_blockhash = receipt["blockHash"] token_network_address = self.get_token_network( token_address, succeeded_at_blockhash) if token_network_address is None: msg = "createERC20TokenNetwork succeeded but token network address is Null" raise RaidenUnrecoverableError(msg) else: # `estimated_transaction` is None # The latest block can not be used reliably because of reorgs, # therefore every call using this block has to handle pruned data. failed_at_block = self.rpc_client.get_block(BLOCK_ID_LATEST) failed_at_blockhash = failed_at_block["hash"].hex() failed_at_blocknumber = failed_at_block["number"] max_token_networks = self.get_max_token_networks( block_identifier=failed_at_blocknumber) token_networks_created = self.get_token_network_created( block_identifier=failed_at_blocknumber) already_registered = self.get_token_network( token_address=token_address, block_identifier=failed_at_blocknumber) deprecation_executor = self.get_deprecation_executor( block_identifier=failed_at_blocknumber) settlement_timeout_min = self.settlement_timeout_min( block_identifier=failed_at_blocknumber) settlement_timeout_max = self.settlement_timeout_max( block_identifier=failed_at_blocknumber) chain_id = self.get_chain_id( block_identifier=failed_at_blocknumber) secret_registry_address = self.get_secret_registry_address( block_identifier=failed_at_blocknumber) try: # Creating a new instance to run the constructor checks. token_proxy = Token( jsonrpc_client=self.rpc_client, token_address=token_address, contract_manager=self.proxy_manager.contract_manager, block_identifier=failed_at_blocknumber, ) except AddressWithoutCode: # This cannot be an unrecoverable error, since the ERC20 # code is external. raise RaidenRecoverableError( "Token disappeared! The address " "{to_checksum_address(token_address)} did have code at " "block {log_details['given_block_identifier']}, however " "at block {failed_at_blocknumber} when the registration " "transaction was mined the address didn't have code " "anymore.") check_address_has_code_handle_pruned_block( client=self.rpc_client, address=Address(secret_registry_address), contract_name=CONTRACT_SECRET_REGISTRY, expected_code=decode_hex( self.proxy_manager.contract_manager.get_runtime_hexcode( CONTRACT_SECRET_REGISTRY)), given_block_identifier=failed_at_blocknumber, ) required_gas = self.gas_measurements[ "TokenNetworkRegistry createERC20TokenNetwork"] self.rpc_client.check_for_insufficient_eth( transaction_name="createERC20TokenNetwork", transaction_executed=False, required_gas=required_gas, block_identifier=failed_at_blocknumber, ) if token_networks_created >= max_token_networks: raise RaidenRecoverableError( "The number of existing token networks reached the maximum allowed" ) if already_registered: # Race condition lost, the token network was created in a different # transaction which got mined first. raise RaidenRecoverableError( "The token was already registered in the TokenNetworkRegistry." ) if deprecation_executor == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The deprecation executor property for the TokenNetworkRegistry is invalid." ) if chain_id == 0: raise RaidenUnrecoverableError( "The chain ID property for the TokenNetworkRegistry is invalid." ) if chain_id != self.rpc_client.chain_id: raise RaidenUnrecoverableError( f"The provided chain ID {chain_id} does not match the " f"network Raiden is running on: {self.rpc_client.chain_id}." ) if secret_registry_address == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The secret registry address for the token network is invalid." ) if settlement_timeout_min <= 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_max <= settlement_timeout_min: raise RaidenUnrecoverableError( "The maximum settlement timeout for the token network " "should be larger than the minimum settlement timeout.") total_supply = token_proxy.total_supply( block_identifier=failed_at_blocknumber) if not total_supply or total_supply <= 0: raise RaidenRecoverableError( f"The given token address is not a valid ERC20 token, " f"total_supply() returned an invalid value {total_supply}." ) # At this point, the TokenNetworkRegistry fails to instantiate # a new TokenNetwork. raise RaidenUnrecoverableError( f"createERC20TokenNetwork gas estimation failed for an unknown " f"reason. Reference block {failed_at_blockhash} " f"{failed_at_blocknumber}.") return ( TransactionHash(transaction_mined.transaction_hash), TokenNetworkAddress(token_network_address), )
def _add_token( self, token_address: TokenAddress, channel_participant_deposit_limit: TokenAmount, token_network_deposit_limit: TokenAmount, log_details: Dict[Any, Any], ) -> TokenNetworkAddress: token_network_address = None checking_block = self.rpc_client.get_checking_block() kwargs = { "_token_address": token_address, "_channel_participant_deposit_limit": channel_participant_deposit_limit, "_token_network_deposit_limit": token_network_deposit_limit, } gas_limit = self.proxy.estimate_gas(checking_block, "createERC20TokenNetwork", **kwargs) if gas_limit: gas_limit = safe_gas_limit( gas_limit, self.gas_measurements[ "TokenNetworkRegistry createERC20TokenNetwork"]) log_details["gas_limit"] = gas_limit transaction_hash = self.proxy.transact("createERC20TokenNetwork", gas_limit, **kwargs) receipt = self.rpc_client.poll(transaction_hash) failed_receipt = check_transaction_threw(receipt=receipt) if failed_receipt: failed_at_blocknumber = failed_receipt["blockNumber"] max_token_networks = self.get_max_token_networks( block_identifier=failed_at_blocknumber) token_networks_created = self.get_token_network_created( block_identifier=failed_at_blocknumber) already_registered = self.get_token_network( token_address=token_address, block_identifier=failed_at_blocknumber) deprecation_executor = self.get_deprecation_executor( block_identifier=failed_at_blocknumber) settlement_timeout_min = self.settlement_timeout_min( block_identifier=failed_at_blocknumber) settlement_timeout_max = self.settlement_timeout_max( block_identifier=failed_at_blocknumber) chain_id = self.get_chain_id( block_identifier=failed_at_blocknumber) secret_registry_address = self.get_secret_registry_address( block_identifier=failed_at_blocknumber) if failed_receipt["cumulativeGasUsed"] == gas_limit: msg = ( f"createERC20TokenNetwork failed and all gas was used " f"({gas_limit}). Estimate gas may have underestimated " f"createERC20TokenNetwork, or succeeded even though an assert is " f"triggered, or the smart contract code has an " f"conditional assert.") raise RaidenRecoverableError(msg) if token_networks_created >= max_token_networks: raise RaidenRecoverableError( "The number of existing token networks reached the maximum allowed" ) if already_registered: # Race condition lost, the token network was created in a different # transaction which got mined first. raise RaidenRecoverableError( "The token was already registered in the TokenNetworkRegistry." ) if deprecation_executor == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The deprecation executor property for the " "TokenNetworkRegistry is invalid.") if chain_id == 0: raise RaidenUnrecoverableError( "The chain ID property for the TokenNetworkRegistry is invalid." ) if secret_registry_address == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The secret registry address for the token network is invalid." ) if settlement_timeout_min == 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_min == 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_max <= settlement_timeout_min: raise RaidenUnrecoverableError( "The maximum settlement timeout for the token network " "should be larger than the minimum settlement timeout." ) # At this point, the TokenNetworkRegistry fails to instantiate # a new TokenNetwork. raise RaidenUnrecoverableError( "createERC20TokenNetwork failed for an unknown reason") token_network_address = self.get_token_network( token_address, receipt["blockHash"]) if token_network_address is None: msg = "createERC20TokenNetwork succeeded but token network address is Null" raise RaidenUnrecoverableError(msg) else: # The latest block can not be used reliably because of reorgs, # therefore every call using this block has to handle pruned data. failed_at = self.proxy.rpc_client.get_block("latest") failed_at_blocknumber = failed_at["number"] max_token_networks = self.get_max_token_networks( block_identifier=failed_at_blocknumber) token_networks_created = self.get_token_network_created( block_identifier=failed_at_blocknumber) already_registered = self.get_token_network( token_address=token_address, block_identifier=failed_at_blocknumber) deprecation_executor = self.get_deprecation_executor( block_identifier=failed_at_blocknumber) settlement_timeout_min = self.settlement_timeout_min( block_identifier=failed_at_blocknumber) settlement_timeout_max = self.settlement_timeout_max( block_identifier=failed_at_blocknumber) chain_id = self.get_chain_id( block_identifier=failed_at_blocknumber) secret_registry_address = self.get_secret_registry_address( block_identifier=failed_at_blocknumber) required_gas = (gas_limit if gas_limit else self.gas_measurements[ "TokenNetworkRegistry createERC20TokenNetwork"]) self.proxy.rpc_client.check_for_insufficient_eth( transaction_name="createERC20TokenNetwork", transaction_executed=False, required_gas=required_gas, block_identifier=failed_at_blocknumber, ) if token_networks_created >= max_token_networks: raise RaidenRecoverableError( "The number of existing token networks reached the maximum allowed" ) if already_registered: # Race condition lost, the token network was created in a different # transaction which got mined first. raise RaidenRecoverableError( "The token was already registered in the TokenNetworkRegistry." ) if deprecation_executor == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The deprecation executor property for the " "TokenNetworkRegistry is invalid.") if chain_id == 0: raise RaidenUnrecoverableError( "The chain ID property for the TokenNetworkRegistry is invalid." ) if chain_id != self.rpc_client.chain_id: raise RaidenUnrecoverableError( f"The provided chain ID {chain_id} does not match the " f"network Raiden is running on: {self.rpc_client.chain_id}." ) if secret_registry_address == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The secret registry address for the token network is invalid." ) if settlement_timeout_min == 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_min == 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_max <= settlement_timeout_min: raise RaidenUnrecoverableError( "The maximum settlement timeout for the token network " "should be larger than the minimum settlement timeout.") if self.get_token_network(token_address, failed_at_blocknumber): raise RaidenRecoverableError("Token already registered") # At this point, the TokenNetworkRegistry fails to instantiate # a new TokenNetwork. raise RaidenUnrecoverableError( "createERC20TokenNetwork failed for an unknown reason") return token_network_address