def transfer(self, to_address, amount): log_details = { 'node': pex(self.node_address), 'contract': pex(self.address), 'to_address': pex(to_address), 'amount': amount, } log.debug('transfer called', **log_details) startgas = GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL transaction_hash = self.proxy.transact( 'transfer', safe_gas_limit(startgas), to_checksum_address(to_address), amount, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('transfer failed', **log_details) raise TransactionThrew('Transfer', receipt_or_none) # TODO: check Transfer event (issue: #2598) log.info('transfer successful', **log_details)
def test_filter_end_block_inclusive(deploy_client): """ A filter includes events from the block given in from_block until and including end_block. """ contract_proxy = deploy_rpc_test_contract(deploy_client) # call the create event function twice and wait for confirmation each time startgas = safe_gas_limit( contract_proxy.estimate_gas('pending', '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 to_block should return first event result_2 = deploy_client.get_filter_events( contract_proxy.contract_address, to_block=block_number_event_1, ) assert get_list_of_block_numbers(result_2) == [block_number_event_1] # this should include the second event result_3 = deploy_client.get_filter_events( contract_proxy.contract_address, to_block=block_number_event_2, ) assert get_list_of_block_numbers(result_3) == block_number_events
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_duplicated_transaction_same_gas_price_raises(deploy_client): """ If the same transaction is sent twice a JSON RPC error is raised. """ gas_price = 2000000000 gas_price_strategy = make_fixed_gas_price_strategy(gas_price) deploy_client.web3.eth.setGasPriceStrategy(gas_price_strategy) contract_proxy = deploy_rpc_test_contract(deploy_client) address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode( to_checksum_address(address))) > 0 second_client = JSONRPCClient( web3=deploy_client.web3, privkey=deploy_client.privkey, ) second_proxy = second_client.new_contract_proxy( contract_proxy.contract.abi, contract_proxy.contract_address, ) check_block = deploy_client.get_checking_block() startgas = safe_gas_limit(contract_proxy.estimate_gas(check_block, 'ret')) with pytest.raises(TransactionAlreadyPending): second_proxy.transact('ret', startgas) contract_proxy.transact('ret', startgas)
def test_duplicated_transaction_same_gas_price_raises(deploy_client): """ If the same transaction is sent twice a JSON RPC error is raised. """ gas_price = 2000000000 gas_price_strategy = make_fixed_gas_price_strategy(gas_price) deploy_client.web3.eth.setGasPriceStrategy(gas_price_strategy) contract_proxy = deploy_rpc_test_contract(deploy_client) address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode(to_checksum_address(address))) > 0 second_client = JSONRPCClient( web3=deploy_client.web3, privkey=deploy_client.privkey, ) second_proxy = second_client.new_contract_proxy( contract_proxy.contract.abi, contract_proxy.contract_address, ) startgas = safe_gas_limit(contract_proxy.estimate_gas('pending', 'ret')) with pytest.raises(TransactionAlreadyPending): second_proxy.transact('ret', startgas) contract_proxy.transact('ret', startgas)
def transfer(self, to_address: Address, amount: TokenAmount): # Note that given_block_identifier is not used here as there # are no preconditions to check before sending the transaction log_details = { "node": pex(self.node_address), "contract": pex(self.address), "to_address": pex(to_address), "amount": amount, } log.debug("transfer called", **log_details) startgas = GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL transaction_hash = self.proxy.transact("transfer", safe_gas_limit(startgas), to_checksum_address(to_address), amount) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical("transfer failed", **log_details) raise TransactionThrew("Transfer", receipt_or_none) # TODO: check Transfer event (issue: #2598) log.info("transfer successful", **log_details)
def register_endpoint(self, node_address, endpoint): if node_address != self.client.address: raise ValueError("node_address doesnt match this node's address") log_details = { 'node': pex(self.node_address), 'node_address': pex(node_address), 'endpoint': endpoint, } log.debug('registerEndpoint called', **log_details) transaction_hash = self.proxy.transact( 'registerEndpoint', safe_gas_limit(GAS_REQUIRED_FOR_ENDPOINT_REGISTER), endpoint, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('registerEndpoint failed', **log_details) raise TransactionThrew('Register Endpoint', receipt_or_none) log.debug('registerEndpoint successful', **log_details)
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 _new_netting_channel(self, partner: typing.Address, settle_timeout: int): if self.channel_exists_and_not_settled(self.node_address, partner): raise DuplicatedChannelError( 'Channel with given partner address already exists') gas_limit = self.proxy.estimate_gas( 'openChannel', self.node_address, partner, settle_timeout, ) gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_OPEN_CHANNEL) transaction_hash = self.proxy.transact( 'openChannel', gas_limit, self.node_address, partner, settle_timeout, ) if not transaction_hash: raise RuntimeError('open channel transaction failed') self.client.poll(transaction_hash) if check_transaction_threw(self.client, transaction_hash): raise DuplicatedChannelError('Duplicated channel') return transaction_hash
def test_duplicated_transaction_different_gas_price_raises(deploy_client): """ If the same transaction is sent twice a JSON RPC error is raised. """ gas_price = 2000000000 deploy_client.web3.eth.setGasPriceStrategy( make_decreasing_gas_price_strategy(gas_price)) contract_proxy = deploy_rpc_test_contract(deploy_client) address = contract_proxy.contract_address assert len(deploy_client.web3.eth.getCode( to_checksum_address(address))) > 0 second_client = JSONRPCClient( web3=deploy_client.web3, privkey=deploy_client.privkey, ) second_proxy = second_client.new_contract_proxy( contract_proxy.contract.abi, contract_proxy.contract_address, ) startgas = safe_gas_limit(contract_proxy.estimate_gas('pending', 'ret')) with pytest.raises(ReplacementTransactionUnderpriced): second_proxy.transact('ret', startgas) contract_proxy.transact('ret', startgas)
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_filter_end_block_inclusive(deploy_client): """ A filter includes events from the block given in from_block until and including end_block. """ contract_proxy = deploy_rpc_test_contract(deploy_client) # call the create event function twice and wait for confirmation each time startgas = safe_gas_limit(contract_proxy.estimate_gas('pending', '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 to_block should return first event result_2 = deploy_client.get_filter_events( contract_proxy.contract_address, to_block=block_number_event_1, ) assert get_list_of_block_numbers(result_2) == [block_number_event_1] # this should include the second event result_3 = deploy_client.get_filter_events( contract_proxy.contract_address, to_block=block_number_event_2, ) assert get_list_of_block_numbers(result_3) == block_number_events
def approve(self, allowed_address: Address, allowance: TokenAmount): """ Aprove `allowed_address` to transfer up to `deposit` amount of token. Note: For channel deposit please use the channel proxy, since it does additional validations. """ log_details = { 'node': pex(self.node_address), 'contract': pex(self.address), 'allowed_address': pex(allowed_address), 'allowance': allowance, } checking_block = self.client.get_checking_block() error_prefix = 'Call to approve will fail' gas_limit = self.proxy.estimate_gas( checking_block, 'approve', to_checksum_address(allowed_address), allowance, ) if gas_limit: error_prefix = 'Call to approve failed' log.debug('approve called', **log_details) transaction_hash = self.proxy.transact( 'approve', safe_gas_limit(gas_limit), to_checksum_address(allowed_address), allowance, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: if transaction_executed: block = receipt_or_none['blockNumber'] else: block = checking_block self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name='approve', transaction_executed=transaction_executed, required_gas=GAS_REQUIRED_FOR_APPROVE, block_identifier=block, ) msg = self._check_why_approved_failed(allowance, block) error_msg = f'{error_prefix}. {msg}' log.critical(error_msg, **log_details) raise RaidenUnrecoverableError(error_msg) log.info('approve successful', **log_details)
def unlock( self, channel_identifier: typing.ChannelID, partner: typing.Address, merkle_tree_leaves: typing.MerkleTreeLeaves, ): log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'merkle_tree_leaves': merkle_tree_leaves, } if merkle_tree_leaves is None or not merkle_tree_leaves: log.info('skipping unlock, tree is empty', **log_details) return log.info('unlock called', **log_details) leaves_packed = b''.join(lock.encoded for lock in merkle_tree_leaves) gas_limit = self.proxy.estimate_gas( 'unlock', channel_identifier, self.node_address, partner, leaves_packed, ) gas_limit = safe_gas_limit(gas_limit, UNLOCK_TX_GAS_LIMIT) transaction_hash = self.proxy.transact( 'unlock', gas_limit, channel_identifier, self.node_address, partner, leaves_packed, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: channel_settled = self.channel_is_settled( participant1=self.node_address, participant2=partner, channel_identifier=channel_identifier, ) if channel_settled is False: log.critical('unlock failed. Channel is not in a settled state', **log_details) raise RaidenUnrecoverableError( 'Channel is not in a settled state. An unlock cannot be made', ) log.critical('unlock failed', **log_details) raise TransactionThrew('Unlock', receipt_or_none) log.info('unlock successful', **log_details)
def approve(self, allowed_address: Address, allowance: TokenAmount): """ Aprove `allowed_address` to transfer up to `deposit` amount of token. Note: For channel deposit please use the channel proxy, since it does additional validations. """ # Note that given_block_identifier is not used here as there # are no preconditions to check before sending the transaction log_details = { "node": pex(self.node_address), "contract": pex(self.address), "allowed_address": pex(allowed_address), "allowance": allowance, } checking_block = self.client.get_checking_block() error_prefix = "Call to approve will fail" gas_limit = self.proxy.estimate_gas( checking_block, "approve", to_checksum_address(allowed_address), allowance ) if gas_limit: error_prefix = "Call to approve failed" log.debug("approve called", **log_details) transaction_hash = self.proxy.transact( "approve", safe_gas_limit(gas_limit), to_checksum_address(allowed_address), allowance, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: if transaction_executed: block = receipt_or_none["blockNumber"] else: block = checking_block self.proxy.jsonrpc_client.check_for_insufficient_eth( address=self.node_address, transaction_name="approve", transaction_executed=transaction_executed, required_gas=GAS_REQUIRED_FOR_APPROVE, block_identifier=block, ) msg = self._check_why_approved_failed(allowance, block) error_msg = f"{error_prefix}. {msg}" log.critical(error_msg, **log_details) raise RaidenUnrecoverableError(error_msg) log.info("approve successful", **log_details)
def approve(self, allowed_address: Address, allowance: TokenAmount): """ Aprove `allowed_address` to transfer up to `deposit` amount of token. Note: For channel deposit please use the channel proxy, since it does additional validations. """ log_details = { 'node': pex(self.node_address), 'contract': pex(self.address), 'allowed_address': pex(allowed_address), 'allowance': allowance, } error_prefix = 'Call to approve will fail' gas_limit = self.proxy.estimate_gas( 'pending', 'approve', to_checksum_address(allowed_address), allowance, ) if gas_limit: error_prefix = 'Call to approve failed' log.debug('approve called', **log_details) transaction_hash = self.proxy.transact( 'approve', safe_gas_limit(gas_limit), to_checksum_address(allowed_address), allowance, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: if transaction_executed: block = receipt_or_none['blockNumber'] else: block = 'pending' self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name='approve', transaction_executed=transaction_executed, required_gas=GAS_REQUIRED_FOR_APPROVE, block_identifier=block, ) msg = self._check_why_approved_failed(allowance, block) error_msg = f'{error_prefix}. {msg}' log.critical(error_msg, **log_details) raise RaidenUnrecoverableError(error_msg) log.info('approve successful', **log_details)
def test_transact_opcode_oog(deploy_client): """ The receipt status field of a transaction that did NOT throw is 0x0. """ contract_proxy = deploy_rpc_test_contract(deploy_client) 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 startgas = safe_gas_limit(contract_proxy.estimate_gas('pending', 'loop', 1000)) // 2 transaction = contract_proxy.transact('loop', startgas, 1000) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction), 'must not be empty'
def test_transact_throws_opcode(deploy_client): """ The receipt status field of a transaction that threw is 0x0 """ contract_proxy = deploy_rpc_test_contract(deploy_client) 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(22_000) transaction = contract_proxy.transact('fail', startgas) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction), 'must not be empty'
def _register_secret_batch(self, secrets): gas_limit = self.proxy.estimate_gas('registerSecretBatch', secrets) gas_limit = safe_gas_limit( gas_limit, len(secrets) * GAS_REQUIRED_PER_SECRET_IN_BATCH) transaction_hash = self.proxy.transact('registerSecretBatch', gas_limit, secrets) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('registerSecretBatch', receipt_or_none) return transaction_hash
def test_endpointregistry_gas(endpoint_discovery_services): """ GAS_REQUIRED_FOR_ENDPOINT_REGISTER value must be equal to the gas required to call registerEndpoint. """ contract_discovery = endpoint_discovery_services[0] discovery_proxy = contract_discovery.discovery_proxy endpoint = host_port_to_endpoint("127.0.0.1", 44444) transaction_hash = discovery_proxy.proxy.transact( "registerEndpoint", safe_gas_limit(GAS_REQUIRED_FOR_ENDPOINT_REGISTER), endpoint) discovery_proxy.client.poll(transaction_hash) receipt = discovery_proxy.client.get_transaction_receipt(transaction_hash) msg = "the transaction failed, check if it was because of the gas being too low" assert receipt["status"] != 0, msg
def test_transact_opcode_oog(deploy_client): """ The receipt status field of a transaction that did NOT throw is 0x0. """ contract_proxy = deploy_rpc_test_contract(deploy_client) 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 startgas = safe_gas_limit(contract_proxy.estimate_gas('loop', 1000)) // 2 transaction = contract_proxy.transact('loop', startgas, 1000) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction), 'must not be empty'
def add_token(self, token_address: typing.TokenAddress): if not is_binary_address(token_address): raise InvalidAddress('Expected binary address format for token') log_details = { 'node': pex(self.node_address), 'token_address': pex(token_address), 'registry_address': pex(self.address), } log.debug('createERC20TokenNetwork called', **log_details) transaction_hash = self.proxy.transact( 'createERC20TokenNetwork', safe_gas_limit(GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK), token_address, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: if self.get_token_network(token_address): msg = 'Token already registered' log.info(f'createERC20TokenNetwork failed, {msg}', **log_details) raise RaidenRecoverableError(msg) log.critical(f'createERC20TokenNetwork failed', **log_details) raise TransactionThrew('createERC20TokenNetwork', receipt_or_none) token_network_address = self.get_token_network(token_address) if token_network_address is None: log.critical( 'createERC20TokenNetwork failed and check_transaction_threw didnt detect it', **log_details, ) raise RuntimeError('token_to_token_networks failed') log.info( 'createERC20TokenNetwork successful', token_network_address=pex(token_network_address), **log_details, ) return token_network_address
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_endpointregistry_gas(endpoint_discovery_services): """ GAS_REQUIRED_FOR_ENDPOINT_REGISTER value must be equal to the gas required to call registerEndpoint. """ contract_discovery = endpoint_discovery_services[0] discovery_proxy = contract_discovery.discovery_proxy endpoint = host_port_to_endpoint('127.0.0.1', 44444) transaction_hash = discovery_proxy.proxy.transact( 'registerEndpoint', safe_gas_limit(GAS_REQUIRED_FOR_ENDPOINT_REGISTER), endpoint, ) discovery_proxy.client.poll(transaction_hash) receipt = discovery_proxy.client.get_transaction_receipt(transaction_hash) msg = 'the transaction failed, check if it was because of the gas being too low' assert receipt['status'] != 0, msg
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 test_duplicated_transaction_different_gas_price_raises(deploy_client: JSONRPCClient) -> None: """ If the same transaction is sent twice a JSON RPC error is raised. """ gas_price = GasPrice(2000000000) deploy_client.web3.eth.setGasPriceStrategy(make_decreasing_gas_price_strategy(gas_price)) 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 second_client = JSONRPCClient(web3=deploy_client.web3, privkey=deploy_client.privkey) second_proxy = second_client.new_contract_proxy( abi=contract_proxy.contract.abi, contract_address=contract_proxy.contract_address ) check_block = deploy_client.get_checking_block() startgas = safe_gas_limit(contract_proxy.estimate_gas(check_block, "ret")) with pytest.raises(ReplacementTransactionUnderpriced): second_proxy.transact("ret", startgas) contract_proxy.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_duplicated_transaction_same_gas_price_raises(deploy_client: JSONRPCClient) -> None: """ If the same transaction is sent twice a JSON RPC error is raised. """ gas_price = GasPrice(2000000000) gas_price_strategy = make_fixed_gas_price_strategy(gas_price) deploy_client.web3.eth.setGasPriceStrategy(gas_price_strategy) 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 second_client = JSONRPCClient(web3=deploy_client.web3, privkey=deploy_client.privkey) second_proxy = second_client.new_contract_proxy( abi=contract_proxy.contract.abi, contract_address=contract_proxy.contract_address ) 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) with pytest.raises(TransactionAlreadyPending): second_proxy.transact("ret", startgas)
def register_secret_batch(self, secrets: List[Secret]): """Register a batch of secrets. Check if they are already registered at the given block identifier.""" secrets_to_register = list() secrethashes_to_register = list() secrethashes_not_sent = list() transaction_result = AsyncResult() wait_for = set() # secret registration has no preconditions: # # - The action does not depend on any state, it's always valid to call # it. # - This action is always susceptible to race conditions. # # Therefore this proxy only needs to detect if the secret is already # registered, to avoid sending obviously unecessary transactions, and # it has to handle race conditions. with self._open_secret_transactions_lock: verification_block_hash = self.client.get_confirmed_blockhash() for secret in secrets: secrethash = sha3(secret) secrethash_hex = encode_hex(secrethash) # Do the local test on `open_secret_transactions` first, then # if necessary do an RPC call. # # The call to `is_secret_registered` has two conflicting # requirements: # # - Avoid sending duplicated transactions for the same lock # - Operating on a consistent/confirmed view of the blockchain # (if a secret has been registered in a block that is not # confirmed it doesn't count yet, an optimization would be to # *not* send the transaction and wait for the confirmation) # # The code below respects the consistent blockchain view, # meaning that if this proxy method is called with an old # blockhash an unecessary transaction will be sent, and the # error will be treated as a race-condition. other_result = self.open_secret_transactions.get(secret) if other_result is not None: wait_for.add(other_result) secrethashes_not_sent.append(secrethash_hex) elif not self.is_secret_registered(secrethash, verification_block_hash): secrets_to_register.append(secret) secrethashes_to_register.append(secrethash_hex) self.open_secret_transactions[secret] = transaction_result # From here on the lock is not required. Context-switches will happen # for the gas estimation and the transaction, however the # synchronization data is limited to the open_secret_transactions log_details = { "node": pex(self.node_address), "contract": pex(self.address), "secrethashes": secrethashes_to_register, "secrethashes_not_sent": secrethashes_not_sent, } if not secrets_to_register: log.debug("registerSecretBatch skipped, waiting for transactions", **log_details) gevent.joinall(wait_for, raise_error=True) log.info("registerSecretBatch successful", **log_details) return checking_block = self.client.get_checking_block() gas_limit = self.proxy.estimate_gas(checking_block, "registerSecretBatch", secrets_to_register) receipt = None transaction_hash = None msg = None if gas_limit: gas_limit = safe_gas_limit( gas_limit, len(secrets_to_register) * GAS_REQUIRED_PER_SECRET_IN_BATCH) log.debug("registerSecretBatch called", **log_details) try: transaction_hash = self.proxy.transact("registerSecretBatch", gas_limit, secrets_to_register) self.client.poll(transaction_hash) receipt = self.client.get_transaction_receipt(transaction_hash) 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 = (gas_limit is None or receipt is None or receipt["status"] == RECEIPT_FAILURE_CODE) 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 receipt is not None: if receipt["gasUsed"] == gas_limit: # 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.") log.critical(error, **log_details) 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 gas_limit: assert msg, "Unexpected control flow, an exception should have been raised." error = ( f"Sending the the transaction for registerSecretBatch failed with: `{msg}`. " f"This happens if the same ethereum account is being used by more than one " f"program which is not supported.") log.critical(error, **log_details) 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.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name="registerSecretBatch", transaction_executed=True, required_gas=gas_limit, block_identifier=checking_block, ) error = "Call to registerSecretBatch couldn't be done" log.critical(error, **log_details) 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 transaction_result.set(transaction_hash) if wait_for: log.info("registerSecretBatch waiting for pending", **log_details) gevent.joinall(wait_for, raise_error=True) log.info("registerSecretBatch successful", **log_details)
def _register_secret_batch( self, secrets_to_register: List[Secret], transaction_result: AsyncResult, log_details: Dict[Any, Any], ) -> None: checking_block = self.client.get_checking_block() gas_limit = self.proxy.estimate_gas(checking_block, "registerSecretBatch", secrets_to_register) receipt = None transaction_hash = None msg = None if gas_limit: gas_limit = safe_gas_limit( gas_limit, len(secrets_to_register) * GAS_REQUIRED_PER_SECRET_IN_BATCH) log_details["gas_limit"] = gas_limit try: transaction_hash = self.proxy.transact("registerSecretBatch", gas_limit, secrets_to_register) receipt = self.client.poll(transaction_hash) 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 = (gas_limit is None or receipt is None or receipt["status"] == RECEIPT_FAILURE_CODE) 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 receipt is not None: if receipt["gasUsed"] == gas_limit: # 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 gas_limit: 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.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name="registerSecretBatch", transaction_executed=True, required_gas=gas_limit, block_identifier=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 transaction_result.set(transaction_hash)
def _add_token( self, token_address: TokenAddress, additional_arguments: Dict, ) -> Address: if not is_binary_address(token_address): raise InvalidAddress('Expected binary address format for token') token_proxy = Token( jsonrpc_client=self.client, token_address=token_address, contract_manager=self.contract_manager, ) if token_proxy.total_supply() == '': raise InvalidToken( 'Given token address does not follow the ERC20 standard (missing totalSupply()', ) log_details = { 'node': pex(self.node_address), 'token_address': pex(token_address), 'registry_address': pex(self.address), } log.debug('createERC20TokenNetwork called', **log_details) checking_block = self.client.get_checking_block() error_prefix = 'Call to createERC20TokenNetwork will fail' kwarguments = {'_token_address': token_address} kwarguments.update(additional_arguments) gas_limit = self.proxy.estimate_gas( checking_block, 'createERC20TokenNetwork', **kwarguments, ) if gas_limit: error_prefix = 'Call to createERC20TokenNetwork failed' transaction_hash = self.proxy.transact( 'createERC20TokenNetwork', safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK), **kwarguments, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: error_type = RaidenUnrecoverableError if transaction_executed: block = receipt_or_none['blockNumber'] else: block = checking_block required_gas = gas_limit if gas_limit else GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name='createERC20TokenNetwork', transaction_executed=transaction_executed, required_gas=required_gas, block_identifier=block, ) msg = '' if self.get_token_network(token_address, block): msg = 'Token already registered' error_type = RaidenRecoverableError error_msg = f'{error_prefix}. {msg}' if error_type == RaidenRecoverableError: log.warning(error_msg, **log_details) else: log.critical(error_msg, **log_details) raise error_type(error_msg) token_network_address = self.get_token_network(token_address, 'latest') if token_network_address is None: msg = 'createERC20TokenNetwork succeeded but token network address is Null' log.critical(msg, **log_details) raise RuntimeError(msg) log.info( 'createERC20TokenNetwork successful', token_network_address=pex(token_network_address), **log_details, ) return token_network_address
def add_token(self, token_address: TokenAddress): if not is_binary_address(token_address): raise InvalidAddress('Expected binary address format for token') log_details = { 'node': pex(self.node_address), 'token_address': pex(token_address), 'registry_address': pex(self.address), } log.debug('createERC20TokenNetwork called', **log_details) error_prefix = 'Call to createERC20TokenNetwork will fail' gas_limit = self.proxy.estimate_gas( 'pending', 'createERC20TokenNetwork', token_address, ) if gas_limit: error_prefix = 'Call to createERC20TokenNetwork failed' transaction_hash = self.proxy.transact( 'createERC20TokenNetwork', safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK), token_address, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: error_type = RaidenUnrecoverableError if transaction_executed: block = receipt_or_none['blockNumber'] else: block = 'pending' required_gas = gas_limit if gas_limit else GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name='createERC20TokenNetwork', transaction_executed=transaction_executed, required_gas=required_gas, block_identifier=block, ) msg = '' if self.get_token_network(token_address, block): msg = 'Token already registered' error_type = RaidenRecoverableError error_msg = f'{error_prefix}. {msg}' if error_type == RaidenRecoverableError: log.warning(error_msg, **log_details) else: log.critical(error_msg, **log_details) raise error_type(error_msg) token_network_address = self.get_token_network(token_address, 'latest') if token_network_address is None: msg = 'createERC20TokenNetwork succeeded but token network address is Null' log.critical(msg, **log_details) raise RuntimeError(msg) log.info( 'createERC20TokenNetwork successful', token_network_address=pex(token_network_address), **log_details, ) return token_network_address
def settle( self, channel_identifier: typing.ChannelID, transferred_amount: int, locked_amount: int, locksroot: typing.Locksroot, partner: typing.Address, partner_transferred_amount: int, partner_locked_amount: int, partner_locksroot: typing.Locksroot, ): """ Settle the channel. Raises: ChannelBusyError: If the channel is busy with another operation """ log_details = { 'channel_identifier': channel_identifier, 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'transferred_amount': transferred_amount, 'locked_amount': locked_amount, 'locksroot': encode_hex(locksroot), 'partner_transferred_amount': partner_transferred_amount, 'partner_locked_amount': partner_locked_amount, 'partner_locksroot': encode_hex(partner_locksroot), } log.debug('settle called', **log_details) self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) with self.channel_operations_lock[partner]: our_maximum = transferred_amount + locked_amount partner_maximum = partner_transferred_amount + partner_locked_amount # The second participant transferred + locked amount must be higher our_bp_is_larger = our_maximum > partner_maximum if our_bp_is_larger: gas_limit = self.proxy.estimate_gas( 'settleChannel', channel_identifier, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_SETTLE_CHANNEL) transaction_hash = self.proxy.transact( 'settleChannel', gas_limit, channel_identifier, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) else: gas_limit = self.proxy.estimate_gas( 'settleChannel', channel_identifier, self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_SETTLE_CHANNEL) transaction_hash = self.proxy.transact( 'settleChannel', gas_limit, channel_identifier, self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('settle failed', **log_details) self._check_channel_state_for_settle( self.node_address, partner, channel_identifier, ) raise TransactionThrew('Settle', receipt_or_none) log.info('settle successful', **log_details)
def _add_token(self, token_address: TokenAddress, additional_arguments: Dict) -> Address: if not is_binary_address(token_address): raise InvalidAddress("Expected binary address format for token") token_proxy = Token( jsonrpc_client=self.client, token_address=token_address, contract_manager=self.contract_manager, ) if token_proxy.total_supply() == "": raise InvalidToken( "Given token address does not follow the ERC20 standard (missing totalSupply()" ) log_details = { "node": pex(self.node_address), "token_address": pex(token_address), "registry_address": pex(self.address), } log.debug("createERC20TokenNetwork called", **log_details) checking_block = self.client.get_checking_block() error_prefix = "Call to createERC20TokenNetwork will fail" kwarguments = {"_token_address": token_address} kwarguments.update(additional_arguments) gas_limit = self.proxy.estimate_gas(checking_block, "createERC20TokenNetwork", **kwarguments) if gas_limit: error_prefix = "Call to createERC20TokenNetwork failed" transaction_hash = self.proxy.transact( "createERC20TokenNetwork", safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK), **kwarguments, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: if transaction_executed: block = receipt_or_none["blockNumber"] else: block = checking_block required_gas = gas_limit if gas_limit else GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name="createERC20TokenNetwork", transaction_executed=transaction_executed, required_gas=required_gas, block_identifier=block, ) if self.get_token_network(token_address, block): error_msg = f"{error_prefix}. Token already registered" log.warning(error_msg, **log_details) raise RaidenRecoverableError(error_msg) error_msg = f"{error_prefix}" log.critical(error_msg, **log_details) raise RaidenUnrecoverableError(error_msg) token_network_address = self.get_token_network(token_address, "latest") if token_network_address is None: msg = "createERC20TokenNetwork succeeded but token network address is Null" log.critical(msg, **log_details) raise RuntimeError(msg) log.info( "createERC20TokenNetwork successful", token_network_address=pex(token_network_address), **log_details, ) return token_network_address
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 register_secret_batch(self, secrets: List[Secret]): secrets_to_register = list() secrethashes_to_register = list() secrethashes_not_sent = list() secret_registry_transaction = AsyncResult() for secret in secrets: secrethash = sha3(secret) secrethash_hex = encode_hex(secrethash) is_register_needed = ( not self.check_registered(secrethash) and secret not in self.open_secret_transactions ) if is_register_needed: secrets_to_register.append(secret) secrethashes_to_register.append(secrethash_hex) self.open_secret_transactions[secret] = secret_registry_transaction else: secrethashes_not_sent.append(secrethash_hex) log_details = { 'node': pex(self.node_address), 'contract': pex(self.address), 'secrethashes': secrethashes_to_register, 'secrethashes_not_sent': secrethashes_not_sent, } if not secrets_to_register: log.debug('registerSecretBatch skipped', **log_details) return error_prefix = 'Call to registerSecretBatch will fail' gas_limit = self.proxy.estimate_gas('pending', 'registerSecretBatch', secrets) if gas_limit: error_prefix = 'Call to registerSecretBatch failed' try: gas_limit = safe_gas_limit( gas_limit, len(secrets) * GAS_REQUIRED_PER_SECRET_IN_BATCH, ) transaction_hash = self.proxy.transact('registerSecretBatch', gas_limit, secrets) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) except Exception as e: secret_registry_transaction.set_exception(e) msg = 'Unexpected exception at sending registerSecretBatch transaction' else: secret_registry_transaction.set(transaction_hash) finally: for secret in secrets_to_register: self.open_secret_transactions.pop(secret, None) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: if transaction_executed: block = receipt_or_none['blockNumber'] else: block = 'pending' self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name='registerSecretBatch', transaction_executed=transaction_executed, required_gas=len(secrets) * GAS_REQUIRED_PER_SECRET_IN_BATCH, block_identifier=block, ) error_msg = f'{error_prefix}. {msg}' log.critical(error_msg, **log_details) raise RaidenUnrecoverableError(error_msg) log.info('registerSecretBatch successful', **log_details)
def register_secret_batch( self, secrets: List[Secret], given_block_identifier: BlockSpecification, ): secrets_to_register = list() secrethashes_to_register = list() secrethashes_not_sent = list() secret_registry_transaction = AsyncResult() for secret in secrets: secrethash = sha3(secret) secrethash_hex = encode_hex(secrethash) is_register_needed = ( not self.check_registered(secrethash, given_block_identifier) and secret not in self.open_secret_transactions) if is_register_needed: secrets_to_register.append(secret) secrethashes_to_register.append(secrethash_hex) self.open_secret_transactions[ secret] = secret_registry_transaction else: secrethashes_not_sent.append(secrethash_hex) log_details = { 'node': pex(self.node_address), 'contract': pex(self.address), 'secrethashes': secrethashes_to_register, 'secrethashes_not_sent': secrethashes_not_sent, } if not secrets_to_register: log.debug('registerSecretBatch skipped', **log_details) return checking_block = self.client.get_checking_block() error_prefix = 'Call to registerSecretBatch will fail' gas_limit = self.proxy.estimate_gas(checking_block, 'registerSecretBatch', secrets) if gas_limit: error_prefix = 'Call to registerSecretBatch failed' try: gas_limit = safe_gas_limit( gas_limit, len(secrets) * GAS_REQUIRED_PER_SECRET_IN_BATCH, ) transaction_hash = self.proxy.transact('registerSecretBatch', gas_limit, secrets) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw( self.client, transaction_hash) except Exception as e: secret_registry_transaction.set_exception(e) msg = 'Unexpected exception at sending registerSecretBatch transaction' else: secret_registry_transaction.set(transaction_hash) finally: for secret in secrets_to_register: self.open_secret_transactions.pop(secret, None) transaction_executed = gas_limit is not None if not transaction_executed or receipt_or_none: if transaction_executed: block = receipt_or_none['blockNumber'] else: block = checking_block self.proxy.jsonrpc_client.check_for_insufficient_eth( transaction_name='registerSecretBatch', transaction_executed=transaction_executed, required_gas=len(secrets) * GAS_REQUIRED_PER_SECRET_IN_BATCH, block_identifier=block, ) error_msg = f'{error_prefix}. {msg}' log.critical(error_msg, **log_details) raise RaidenUnrecoverableError(error_msg) log.info('registerSecretBatch successful', **log_details)