def test_channel_with_self(raiden_network, settle_timeout): app0, = raiden_network # pylint: disable=unbalanced-tuple-unpacking token_address = app0.raiden.default_registry.token_addresses()[0] assert not app0.raiden.token_to_channelgraph[token_address].address_to_channel graph0 = app0.raiden.default_registry.manager_by_token(token_address) with pytest.raises(SamePeerAddress) as excinfo: graph0.new_netting_channel( app0.raiden.address, settle_timeout, ) assert 'The other peer must not have the same address as the client.' in str(excinfo.value) transaction_hash = graph0.proxy.transact( 'newChannel', app0.raiden.address, settle_timeout, ) # wait to make sure we get the receipt wait_until_block(app0.raiden.chain, app0.raiden.chain.block_number() + 5) assert check_transaction_threw(app0.raiden.chain.client, transaction_hash)
def _register_secret_batch(self, secrets): transaction_hash = self.proxy.transact( 'registerSecretBatch', secrets, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'registerSecretBatch failed', node=pex(self.node_address), contract=pex(self.address), secrets=secrets, ) raise TransactionThrew('registerSecretBatch', receipt_or_none) log.info( 'registerSecretBatch successful', node=pex(self.node_address), contract=pex(self.address), secrets=secrets, ) return transaction_hash
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 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 add_token(self, token_address): if not isaddress(token_address): raise ValueError('token_address must be a valid address') transaction_hash = estimate_and_transact( self.proxy, 'addToken', self.startgas, self.gasprice, token_address, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('AddToken', receipt_or_none) manager_address = self.manager_address_by_token(token_address) if manager_address is None: log.error('Transaction failed and check_transaction_threw didnt detect it') raise RuntimeError('channelManagerByToken failed') if log.isEnabledFor(logging.INFO): log.info( 'add_token called', token_address=pex(token_address), registry_address=pex(self.address), manager_address=pex(manager_address), ) return manager_address
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 approve(self, contract_address, allowance): """ Aprove `contract_address` to transfer up to `deposit` amount of token. """ # TODO: check that `contract_address` is a netting channel and that # `self.address` is one of the participants (maybe add this logic into # `NettingChannel` and keep this straight forward) transaction_hash = estimate_and_transact( self.proxy, 'approve', self.startgas, self.gasprice, contract_address, allowance, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: user_balance = self.balance_of(self.client.sender) # If the balance is zero, either the smart contract doesnt have a # balanceOf function or the actual balance is zero if user_balance == 0: msg = ( "Approve failed. \n" "Your account balance is 0 (zero), either the smart " "contract is not a valid ERC20 token or you don't have funds " "to use for openning a channel. " ) raise TransactionThrew(msg, receipt_or_none) # The approve call failed, check the user has enough balance # (assuming the token smart contract may check for the maximum # allowance, which is not necessarily the case) elif user_balance < allowance: msg = ( 'Approve failed. \n' 'Your account balance is {}, nevertheless the call to ' 'approve failed. Please make sure the corresponding smart ' 'contract is a valid ERC20 token.' ).format(user_balance) raise TransactionThrew(msg, receipt_or_none) # If the user has enough balance, warn the user the smart contract # may not have the approve function. else: msg = ( 'Approve failed. \n' 'Your account balance is {}, the request allowance is {}. ' 'The smart contract may be rejecting your request for the ' 'lack of balance.' ).format(user_balance, allowance) raise TransactionThrew(msg, receipt_or_none)
def transfer(self, to_address, amount): transaction_hash = self.proxy.transact( 'transfer', to_checksum_address(to_address), amount, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('Transfer', receipt_or_none)
def close( self, partner: typing.Address, nonce: typing.Nonce, balance_hash: typing.BalanceHash, additional_hash: typing.AdditionalHash, signature: typing.Signature, ): """ Close the channel using the provided balance proof. Raises: ChannelBusyError: If the channel is busy with another operation. ChannelIncorrectStateError: If the channel is not in the open state. """ log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'nonce': nonce, 'balance_hash': encode_hex(balance_hash), 'additional_hash': encode_hex(additional_hash), 'signature': encode_hex(signature), } log.info('close called', **log_details) if not self.channel_is_opened(self.node_address, partner): raise ChannelIncorrectStateError( 'Channel is not in an opened state. It cannot be closed.', ) with self.channel_operations_lock[partner]: transaction_hash = self.proxy.transact( 'closeChannel', partner, balance_hash, nonce, additional_hash, signature, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('close failed', **log_details) if not self.channel_is_opened(self.node_address, partner): raise ChannelIncorrectStateError( 'Channel is not in an opened state. It cannot be closed.', ) raise TransactionThrew('Close', receipt_or_none) log.info('close successful', **log_details)
def test_transact_opcode(deploy_client): """ The receipt status field of a transaction that did not throw is 0x1 """ 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 startgas = contract_proxy.estimate_gas('pending', 'ret') * 2 transaction = contract_proxy.transact('ret', startgas) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction) is None, 'must be empty'
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 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 gas = min(contract_proxy.estimate_gas('loop', 1000) // 2, deploy_client.gaslimit()) transaction_hex = contract_proxy.transact('loop', 1000, startgas=gas) transaction = unhexlify(transaction_hex) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction_hex), 'must not be empty'
def register_endpoint(self, node_address, endpoint): if node_address != self.client.sender: raise ValueError("node_address doesnt match this node's address") transaction_hash = self.proxy.transact( 'registerEndpoint', endpoint, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('Register Endpoint', receipt_or_none)
def transfer(self, to_address, amount): transaction_hash = estimate_and_transact( self.proxy, 'transfer', self.startgas, self.gasprice, to_address, amount, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('Transfer', receipt_or_none)
def test_transact_opcode(deploy_client, blockchain_backend): """ The receipt status field of a transaction that did not throw is 0x1 """ contract_proxy = deploy_rpc_test_contract(deploy_client) address = contract_proxy.contract_address assert len(deploy_client.eth_getCode(address)) > 0 gas = contract_proxy.estimate_gas('ret') * 2 transaction_hex = contract_proxy.transact('ret', startgas=gas) transaction = unhexlify(transaction_hex) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction_hex) is None, 'must 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 gas = deploy_client.gaslimit() transaction_hex = contract_proxy.transact('fail', startgas=gas) transaction = unhexlify(transaction_hex) deploy_client.poll(transaction) assert check_transaction_threw(deploy_client, transaction_hex), '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.info( 'add_token called', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), ) transaction_hash = self.proxy.transact( 'createERC20TokenNetwork', token_address, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'add_token failed', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), ) raise TransactionThrew('createERC20TokenNetwork', receipt_or_none) token_network_address = self.get_token_network(token_address) if token_network_address is None: log.info( 'add_token failed and check_transaction_threw didnt detect it', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), ) raise RuntimeError('token_to_token_networks failed') log.info( 'add_token sucessful', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), token_network_address=pex(token_network_address), ) return token_network_address
def register_endpoint(self, node_address, endpoint): if node_address != self.client.sender: raise ValueError("node_address doesnt match this node's address") transaction_hash = self.proxy.transact( 'registerEndpoint', endpoint, gasprice=self.gasprice, startgas=DISCOVERY_REGISTRATION_GAS, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('Register Endpoint', receipt_or_none)
def update_transfer( self, partner: typing.Address, nonce: typing.Nonce, balance_hash: typing.BalanceHash, additional_hash: typing.AdditionalHash, closing_signature: typing.Signature, non_closing_signature: typing.Signature, ): log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'nonce': nonce, 'balance_hash': encode_hex(balance_hash), 'additional_hash': encode_hex(additional_hash), 'closing_signature': encode_hex(closing_signature), 'non_closing_signature': encode_hex(non_closing_signature), } log.info('updateNonClosingBalanceProof called', **log_details) transaction_hash = self.proxy.transact( 'updateNonClosingBalanceProof', partner, self.node_address, balance_hash, nonce, additional_hash, closing_signature, non_closing_signature, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('updateNonClosingBalanceProof failed', **log_details) channel_closed = self.channel_is_closed(self.node_address, partner) if channel_closed is False: raise ChannelIncorrectStateError('Channel is not in a closed state') raise TransactionThrew('Update NonClosing balance proof', receipt_or_none) log.info('updateNonClosingBalanceProof successful', **log_details)
def _new_netting_channel(self, partner: typing.Address, settle_timeout: int): if self.channel_exists(self.node_address, partner): raise DuplicatedChannelError('Channel with given partner address already exists') transaction_hash = self.proxy.transact( 'openChannel', self.node_address, partner, settle_timeout, ) if not transaction_hash: raise RuntimeError('open channel transaction failed') self.client.poll(unhexlify(transaction_hash)) if check_transaction_threw(self.client, transaction_hash): raise DuplicatedChannelError('Duplicated channel') return transaction_hash
def withdraw( self, partner: typing.Address, total_withdraw: int, partner_signature: typing.Address, signature: typing.Signature, ): log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'total_withdraw': total_withdraw, 'partner_signature': encode_hex(partner_signature), 'signature': encode_hex(signature), } log.info('withdraw called', **log_details) with self.channel_operations_lock[partner]: transaction_hash = self.proxy.transact( 'setTotalWithdraw', self.node_address, partner, total_withdraw, partner_signature, signature, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('withdraw failed', **log_details) channel_opened = self.channel_is_opened(self.node_address, partner) if channel_opened is False: raise ChannelIncorrectStateError( 'Channel is not in an opened state. A withdraw cannot be made', ) raise TransactionThrew('Withdraw', receipt_or_none) log.info('withdraw successful', **log_details)
def unlock(self, 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) transaction_hash = self.proxy.transact( 'unlock', self.node_address, partner, leaves_packed, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: channel_settled = self.channel_is_settled(self.node_address, partner) if channel_settled is False: log.critical('unlock failed. Channel is not in a settled state', **log_details) raise ChannelIncorrectStateError( '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 settle(self): """ Settle the channel. Raises: ChannelBusyError: If the channel is busy with another operation """ log.info( 'settle called', node=pex(self.node_address), ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact('settle', ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Settle', receipt_or_none) log.info( 'settle successful', node=pex(self.node_address), contract=pex(self.address), )
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 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) transaction_hash = self.proxy.transact( 'transfer', 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 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.sender: 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', 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 update_transfer( self, channel_identifier: typing.ChannelID, partner: typing.Address, balance_hash: typing.BalanceHash, nonce: typing.Nonce, additional_hash: typing.AdditionalHash, closing_signature: typing.Signature, non_closing_signature: typing.Signature, ): log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'nonce': nonce, 'balance_hash': encode_hex(balance_hash), 'additional_hash': encode_hex(additional_hash), 'closing_signature': encode_hex(closing_signature), 'non_closing_signature': encode_hex(non_closing_signature), } log.debug('updateNonClosingBalanceProof called', **log_details) self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) transaction_hash = self.proxy.transact( 'updateNonClosingBalanceProof', channel_identifier, partner, self.node_address, balance_hash, nonce, additional_hash, closing_signature, non_closing_signature, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: channel_closed = self.channel_is_closed( participant1=self.node_address, participant2=partner, channel_identifier=channel_identifier, ) if channel_closed is False: msg = 'Channel is not in a closed state' log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenUnrecoverableError(msg) msg = 'Update NonClosing balance proof' log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise TransactionThrew(msg, receipt_or_none) log.info('updateNonClosingBalanceProof successful', **log_details)
def close( self, channel_identifier: typing.ChannelID, partner: typing.Address, balance_hash: typing.BalanceHash, nonce: typing.Nonce, additional_hash: typing.AdditionalHash, signature: typing.Signature, ): """ Close the channel using the provided balance proof. Raises: ChannelBusyError: If the channel is busy with another operation. RaidenRecoverableError: If the channel is already closed. RaidenUnrecoverableError: If the channel does not exist or is settled. """ log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'nonce': nonce, 'balance_hash': encode_hex(balance_hash), 'additional_hash': encode_hex(additional_hash), 'signature': encode_hex(signature), } log.debug('closeChannel called', **log_details) self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) self._check_channel_state_for_close( self.node_address, partner, channel_identifier, ) with self.channel_operations_lock[partner]: transaction_hash = self.proxy.transact( 'closeChannel', channel_identifier, partner, balance_hash, nonce, additional_hash, signature, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('closeChannel failed', **log_details) self._check_channel_state_for_close( self.node_address, partner, channel_identifier, ) raise TransactionThrew('Close', receipt_or_none) log.info('closeChannel successful', **log_details)
def set_total_deposit( self, channel_identifier: typing.ChannelID, total_deposit: typing.TokenAmount, partner: typing.Address, ): """ Set total token deposit in the channel to total_deposit. Raises: ChannelBusyError: If the channel is busy with another operation RuntimeError: If the token address is empty. """ if not isinstance(total_deposit, int): raise ValueError('total_deposit needs to be an integral number.') self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) token_address = self.token_address() token = Token(self.client, token_address) with self.channel_operations_lock[partner], self.deposit_lock: # setTotalDeposit requires a monotonically increasing value. This # is used to handle concurrent actions: # # - The deposits will be done in order, i.e. the monotonic # property is preserved by the caller # - The race of two deposits will be resolved with the larger # deposit winning # - Retries wont have effect # # This check is serialized with the channel_operations_lock to avoid # sending invalid transactions on-chain (decreasing total deposit). # current_deposit = self.detail_participant( channel_identifier, self.node_address, partner, ).deposit amount_to_deposit = total_deposit - current_deposit log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'total_deposit': total_deposit, 'amount_to_deposit': amount_to_deposit, 'id': id(self), } log.debug('setTotalDeposit called', **log_details) # These two scenarions can happen if two calls to deposit happen # and then we get here on the second call if total_deposit < current_deposit: msg = ( f'Current deposit ({current_deposit}) is already larger ' f'than the requested total deposit amount ({total_deposit})' ) log.info(f'setTotalDeposit failed, {msg}', **log_details) raise DepositMismatch(msg) if amount_to_deposit <= 0: msg = (f'total_deposit - current_deposit = ' f'{amount_to_deposit} must be greater than 0.', ) log.info(f'setTotalDeposit failed, {msg}', **log_details) raise DepositMismatch(msg) # A node may be setting up multiple channels for the same token # concurrently. Because each deposit changes the user balance this # check must be serialized with the operation locks. # # This check is merely informational, used to avoid sending # transactions which are known to fail. # # It is serialized with the deposit_lock to avoid sending invalid # transactions on-chain (account without balance). The lock # channel_operations_lock is not sufficient, as it allows two # concurrent deposits for different channels. # current_balance = token.balance_of(self.node_address) if current_balance < amount_to_deposit: msg = ( f'total_deposit - current_deposit = {amount_to_deposit} can not ' f'be larger than the available balance {current_balance}, ' f'for token at address {pex(token_address)}') log.info(f'setTotalDeposit failed, {msg}', **log_details) raise DepositMismatch(msg) # If there are channels being set up concurrenlty either the # allowance must be accumulated *or* the calls to `approve` and # `setTotalDeposit` must be serialized. This is necessary otherwise # the deposit will fail. # # Calls to approve and setTotalDeposit are serialized with the # deposit_lock to avoid transaction failure, because with two # concurrent deposits, we may have the transactions executed in the # following order # # - approve # - approve # - setTotalDeposit # - setTotalDeposit # # in which case the second `approve` will overwrite the first, # and the first `setTotalDeposit` will consume the allowance, # making the second deposit fail. token.approve(self.address, amount_to_deposit) transaction_hash = self.proxy.transact( 'setTotalDeposit', channel_identifier, self.node_address, total_deposit, partner, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: if token.allowance(self.node_address, self.address) < amount_to_deposit: log_msg = ( 'setTotalDeposit failed. The allowance is insufficient, ' 'check concurrent deposits for the same token network ' 'but different proxies.') elif token.balance_of(self.node_address) < amount_to_deposit: log_msg = 'setTotalDeposit failed. The address doesnt have funds' else: log_msg = 'setTotalDeposit failed' log.critical(log_msg, **log_details) self._check_channel_state_for_deposit( self.node_address, partner, channel_identifier, total_deposit, ) raise TransactionThrew('Deposit', receipt_or_none) log.info('setTotalDeposit successful', **log_details)
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 = { '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.info('settle called', **log_details) self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) with self.channel_operations_lock[partner]: if self._verify_settle_state( transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) is False: raise ChannelIncorrectStateError( 'local state can not be used to call settle') 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: transaction_hash = self.proxy.transact( 'settleChannel', partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) else: transaction_hash = self.proxy.transact( 'settleChannel', 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: channel_exists = self.channel_exists(self.node_address, partner) if not channel_exists: log.info('settle failed, channel already settled', **log_details) raise ChannelIncorrectStateError( 'Channel already settled or non-existent', ) channel_closed = self.channel_is_closed( self.node_address, partner) if channel_closed is False: log.info('settle failed, channel is not closed', **log_details) raise ChannelIncorrectStateError( 'Channel is not in a closed state. It cannot be settled', ) log.info('settle failed', **log_details) raise TransactionThrew('Settle', receipt_or_none) log.info('settle successful', **log_details)
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 update_transfer( self, partner: typing.Address, nonce: typing.Nonce, balance_hash: typing.BalanceHash, additional_hash: typing.AdditionalHash, partner_signature: typing.Signature, signature: typing.Signature, ): log.info( 'updateNonClosingBalanceProof called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), partner_signature=encode_hex(partner_signature), signature=encode_hex(signature), ) transaction_hash = self.proxy.transact( 'updateNonClosingBalanceProof', partner, self.node_address, balance_hash, nonce, additional_hash, partner_signature, signature, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'updateNonClosingBalanceProof failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), partner_signature=encode_hex(partner_signature), signature=encode_hex(signature), ) channel_closed = self.channel_is_closed(partner) if channel_closed is False: raise ChannelIncorrectStateError('Channel is not in a closed state') raise TransactionThrew('Update NonClosing balance proof', receipt_or_none) log.info( 'updateNonClosingBalanceProof successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), partner_signature=encode_hex(partner_signature), signature=encode_hex(signature), )
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 settle( self, 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 = { '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.info('settle called', **log_details) 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: transaction_hash = self.proxy.transact( 'settleChannel', partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) else: transaction_hash = self.proxy.transact( 'settleChannel', self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: channel_exists = self.channel_exists(self.node_address, partner) if not channel_exists: log.info('settle failed, channel already settled', **log_details) raise ChannelIncorrectStateError( 'Channel already settled or non-existent', ) channel_closed = self.channel_is_closed(self.node_address, partner) if channel_closed is False: log.info('settle failed, channel is not closed', **log_details) raise ChannelIncorrectStateError( 'Channel is not in a closed state. It cannot be settled', ) log.info('settle failed', **log_details) raise TransactionThrew('Settle', receipt_or_none) log.info('settle successful', **log_details)
def set_url(self, url: str): """Sets the url needed to access the service via HTTP for the caller""" gas_limit = self.proxy.estimate_gas("latest", "setURL", url) transaction_hash = self.proxy.transact("setURL", gas_limit, url) self.client.poll(transaction_hash) assert not check_transaction_threw(self.client, transaction_hash)
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
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 withdraw( self, channel_identifier: typing.ChannelID, partner: typing.Address, total_withdraw: int, partner_signature: typing.Address, signature: typing.Signature, ): log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'total_withdraw': total_withdraw, 'partner_signature': encode_hex(partner_signature), 'signature': encode_hex(signature), } log.debug('setTotalWithdraw called', **log_details) self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) current_withdraw = self.detail_participant( channel_identifier, self.node_address, partner, ).withdrawn amount_to_withdraw = total_withdraw - current_withdraw if total_withdraw < current_withdraw: msg = ( f'Current withdraw ({current_withdraw}) is already larger ' f'than the requested total withdraw amount ({total_withdraw})', ) log.critical(f'setTotalWithdraw failed, {msg}', **log_details) raise WithdrawMismatch(msg) if amount_to_withdraw <= 0: msg = f'withdraw {amount_to_withdraw} must be greater than 0.' log.critical(f'setTotalWithdraw failed, {msg}', **log_details) raise ValueError(msg) with self.channel_operations_lock[partner]: transaction_hash = self.proxy.transact( 'setTotalWithdraw', channel_identifier, self.node_address, total_withdraw, partner_signature, signature, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical('setTotalWithdraw failed', **log_details) self._check_channel_state_for_withdraw( self.node_address, partner, channel_identifier, total_withdraw, ) raise TransactionThrew('Withdraw', receipt_or_none) log.info('setTotalWithdraw successful', **log_details)
def settle( self, 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.info( 'settle called', 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), ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'settleChannel', self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', 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), ) channel_closed = self.channel_is_closed(partner) if channel_closed is False: raise ChannelIncorrectStateError( 'Channel is not in a closed state. It cannot be settled', ) raise TransactionThrew('Settle', receipt_or_none) log.info( 'settle successful', 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), )
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 = { '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: transaction_hash = self.proxy.transact( 'settleChannel', channel_identifier, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) else: transaction_hash = self.proxy.transact( 'settleChannel', 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 deposit(self, amount): """ Deposit amount token in the channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. RuntimeError: If the netting channel token address is empty. """ if not isinstance(amount, int): raise ValueError('amount needs to be an integral number.') token_address = self.token_address() token = Token( self.client, token_address, self.poll_timeout, ) current_balance = token.balance_of(self.node_address) if current_balance < amount: raise ValueError( 'deposit [{}] cant be larger than the available balance [{}].'. format( amount, current_balance, )) if log.isEnabledFor(logging.INFO): log.info( 'deposit called', node=pex(self.node_address), contract=pex(self.address), amount=amount, ) transaction_hash = estimate_and_transact( self.proxy, 'deposit', amount, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'deposit failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Deposit', receipt_or_none) if log.isEnabledFor(logging.INFO): log.info( 'deposit sucessfull', node=pex(self.node_address), contract=pex(self.address), amount=amount, )
def set_total_deposit(self, total_deposit: typing.TokenAmount, partner: typing.Address): """ Set total token deposit in the channel to total_deposit. Raises: ChannelBusyError: If the channel is busy with another operation RuntimeError: If the token address is empty. """ if not isinstance(total_deposit, int): raise ValueError('total_deposit needs to be an integral number.') token_address = self.token_address() token = Token(self.client, token_address) with self.channel_operations_lock[partner], self.deposit_lock: # setTotalDeposit requires a monotonically increasing value. This # is used to handle concurrent actions: # # - The deposits will be done in order, i.e. the monotonic # property is preserved by the caller # - The race of two deposits will be resolved with the larger # deposit winning # - Retries wont have effect # # This check is serialized with the channel_operations_lock to avoid # sending invalid transactions on-chain (decreasing total deposit). # current_deposit = self.detail_participant(self.node_address, partner)['deposit'] amount_to_deposit = total_deposit - current_deposit if amount_to_deposit <= 0: raise ValueError(f'deposit {amount_to_deposit} must be greater than 0.') # A node may be setting up multiple channels for the same token # concurrently. Because each deposit changes the user balance this # check must be serialized with the operation locks. # # This check is merely informational, used to avoid sending # transactions which are known to fail. # # It is serialized with the deposit_lock to avoid sending invalid # transactions on-chain (account without balance). The lock # channel_operations_lock is not sufficient, as it allows two # concurrent deposits for different channels. # current_balance = token.balance_of(self.node_address) if current_balance < amount_to_deposit: raise ValueError( f'deposit {amount_to_deposit} can not be larger than the ' f'available balance {current_balance}, ' f'for token at address {pex(token_address)}', ) # If there are channels being set up concurrenlty either the # allowance must be accumulated *or* the calls to `approve` and # `setTotalDeposit` must be serialized. This is necessary otherwise # the deposit will fail. # # Calls to approve and setTotalDeposit are serialized with the # deposit_lock to avoid transaction failure, because with two # concurrent deposits, we may have the transactions executed in the # following order # # - approve # - approve # - setTotalDeposit # - setTotalDeposit # # in which case the second `approve` will overwrite the first, # and the first `setTotalDeposit` will consume the allowance, # making the second deposit fail. token.approve(self.address, amount_to_deposit) log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'total_deposit': total_deposit, 'amount_to_deposit': amount_to_deposit, 'id': id(self), } log.info('deposit called', **log_details) transaction_hash = self.proxy.transact( 'setTotalDeposit', self.node_address, total_deposit, partner, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: if token.allowance(self.node_address, self.address) < amount_to_deposit: log_msg = ( 'deposit failed. The allowance is insufficient, check concurrent deposits ' 'for the same token network but different proxies.' ) elif token.balance_of(self.node_address) < amount_to_deposit: log_msg = 'deposit failed. The address doesnt have funds' else: log_msg = 'deposit failed' log.critical(log_msg, **log_details) channel_opened = self.channel_is_opened(self.node_address, partner) if channel_opened is False: raise ChannelIncorrectStateError( 'Channel is not in an opened state. A deposit cannot be made', ) raise TransactionThrew('Deposit', receipt_or_none) log.info('deposit 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: 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 settle( self, 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.info( 'settle called', 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), ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): # the second participants transferred + locked amount have to be higher than the first if (transferred_amount + locked_amount > partner_transferred_amount + partner_locked_amount): transaction_hash = self.proxy.transact( 'settleChannel', partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) else: transaction_hash = self.proxy.transact( 'settleChannel', self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', 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), ) channel_exists = self.channel_exists(self.node_address, partner) if not channel_exists: raise ChannelIncorrectStateError( 'Channel already settled or non-existent', ) channel_closed = self.channel_is_closed(self.node_address, partner) if channel_closed is False: raise ChannelIncorrectStateError( 'Channel is not in a closed state. It cannot be settled', ) raise TransactionThrew('Settle', receipt_or_none) log.info( 'settle successful', 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), )
def new_netting_channel(self, peer1, peer2, settle_timeout): if not isaddress(peer1): raise ValueError('The peer1 must be a valid address') if not isaddress(peer2): raise ValueError('The peer2 must be a valid address') invalid_timeout = (settle_timeout < NETTINGCHANNEL_SETTLE_TIMEOUT_MIN or settle_timeout > NETTINGCHANNEL_SETTLE_TIMEOUT_MAX) if invalid_timeout: raise ValueError('settle_timeout must be in range [{}, {}]'.format( NETTINGCHANNEL_SETTLE_TIMEOUT_MIN, NETTINGCHANNEL_SETTLE_TIMEOUT_MAX)) if peer1 == peer2: raise SamePeerAddress('Peer1 and peer2 must not be equal') if privatekey_to_address(self.client.privkey) == peer1: other = peer2 else: other = peer1 transaction_hash = estimate_and_transact( self.proxy.newChannel, self.startgas, self.gasprice, other, settle_timeout, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) if check_transaction_threw(self.client, transaction_hash): raise DuplicatedChannelError('Duplicated channel') netting_channel_results_encoded = self.proxy.getChannelWith.call( other, startgas=self.startgas, ) # address is at index 0 netting_channel_address_encoded = netting_channel_results_encoded if not netting_channel_address_encoded: log.error('netting_channel_address failed', peer1=pex(peer1), peer2=pex(peer2)) raise RuntimeError('netting_channel_address failed') netting_channel_address_bin = address_decoder( netting_channel_address_encoded) if log.isEnabledFor(logging.INFO): log.info( 'new_netting_channel called', peer1=pex(peer1), peer2=pex(peer2), netting_channel=pex(netting_channel_address_bin), ) return netting_channel_address_bin
def deposit(self, total_deposit: typing.TokenAmount, partner: typing.Address): """ Set total token deposit in the channel to total_deposit. Raises: ChannelBusyError: If the channel is busy with another operation RuntimeError: If the token address is empty. """ if not isinstance(total_deposit, int): raise ValueError('total_deposit needs to be an integral number.') token_address = self.token_address() token = Token( self.client, token_address, self.poll_timeout, ) current_balance = token.balance_of(self.node_address) current_deposit = self.detail_participant(self.node_address, partner)['deposit'] amount_to_deposit = total_deposit - current_deposit if amount_to_deposit <= 0: raise ValueError('deposit [{}] must be greater than 0.'.format( amount_to_deposit, )) if current_balance < amount_to_deposit: raise ValueError( f'deposit {amount_to_deposit} cant be larger than the ' f'available balance {current_balance}, ' f'for token at address {pex(token_address)}', ) log.info( 'deposit called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_deposit=total_deposit, amount_to_deposit=amount_to_deposit, ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'setTotalDeposit', self.node_address, total_deposit, partner, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'deposit failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_deposit=total_deposit, ) channel_opened = self.channel_is_opened(partner) if channel_opened is False: raise ChannelIncorrectStateError( 'Channel is not in an opened state. A deposit cannot be made', ) raise TransactionThrew('Deposit', receipt_or_none) log.info( 'deposit successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_deposit=total_deposit, )
def close(self, nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature): """ Close the channel using the provided balance proof. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation. """ log.info( 'close called', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact( 'close', nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'close failed', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) self._check_exists() raise TransactionThrew('Close', receipt_or_none) log.info( 'close successful', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), )
def close( self, partner: typing.Address, nonce: typing.Nonce, balance_hash: typing.BalanceHash, additional_hash: typing.AdditionalHash, signature: typing.Signature, ): """ Close the channel using the provided balance proof. Raises: ChannelBusyError: If the channel is busy with another operation. """ log.info( 'close called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), signature=encode_hex(signature), ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'closeChannel', partner, balance_hash, nonce, additional_hash, signature, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'close failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), signature=encode_hex(signature), ) channel_opened = self.channel_is_opened(partner) if channel_opened is False: raise ChannelIncorrectStateError( 'Channel is not in an opened state. It cannot be closed.', ) raise TransactionThrew('Close', receipt_or_none) log.info( 'close successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), signature=encode_hex(signature), )
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 update_transfer( self, nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ): if signature: log.info( 'updateTransfer called', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) transaction_hash = self.proxy.transact( 'updateTransfer', nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'updateTransfer failed', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) self._check_exists() raise TransactionThrew('Update Transfer', receipt_or_none) log.info( 'updateTransfer successful', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), )
def deploy_service_registry_and_set_urls( private_keys, web3, contract_manager, service_registry_address) -> Tuple[ServiceRegistry, List[str]]: urls = ["http://foo", "http://boo", "http://coo"] c1_client = JSONRPCClient(web3, private_keys[0]) c1_service_proxy = ServiceRegistry( jsonrpc_client=c1_client, service_registry_address=service_registry_address, contract_manager=contract_manager, ) token_address = c1_service_proxy.token_address(block_identifier="latest") c1_token_proxy = Token(jsonrpc_client=c1_client, token_address=token_address, contract_manager=contract_manager) c2_client = JSONRPCClient(web3, private_keys[1]) c2_service_proxy = ServiceRegistry( jsonrpc_client=c2_client, service_registry_address=service_registry_address, contract_manager=contract_manager, ) c2_token_proxy = Token(jsonrpc_client=c2_client, token_address=token_address, contract_manager=contract_manager) c3_client = JSONRPCClient(web3, private_keys[2]) c3_service_proxy = ServiceRegistry( jsonrpc_client=c3_client, service_registry_address=service_registry_address, contract_manager=contract_manager, ) c3_token_proxy = Token(jsonrpc_client=c3_client, token_address=token_address, contract_manager=contract_manager) # Test that getting a random service for an empty registry returns None pfs_address = get_random_pfs(c1_service_proxy, "latest", pathfinding_max_fee=TokenAmount(1)) assert pfs_address is None # Test that setting the urls works c1_price = c1_service_proxy.current_price(block_identifier="latest") tx = c1_token_proxy.proxy.transact("mint", 1000000, c1_price) receipt = c1_client.poll(tx) assert not check_transaction_threw(receipt=receipt) assert c1_token_proxy.balance_of(c1_client.address) > 0 c1_token_proxy.approve(allowed_address=service_registry_address, allowance=c1_price) c1_service_proxy.deposit(block_identifier="latest", limit_amount=c1_price) c1_service_proxy.set_url(urls[0]) c2_price = c2_service_proxy.current_price(block_identifier="latest") tx = c2_token_proxy.proxy.transact("mint", 1000000, c2_price) receipt = c2_client.poll(tx) assert not check_transaction_threw(receipt=receipt) assert c2_token_proxy.balance_of(c2_client.address) > 0 c2_token_proxy.approve(allowed_address=service_registry_address, allowance=c2_price) c2_service_proxy.deposit(block_identifier="latest", limit_amount=c2_price) c2_service_proxy.set_url(urls[1]) c3_price = c3_service_proxy.current_price(block_identifier="latest") tx = c3_token_proxy.proxy.transact("mint", 1000000, c3_price) receipt = c3_client.poll(tx) assert not check_transaction_threw(receipt=receipt) assert c3_token_proxy.balance_of(c3_client.address) > 0 c3_token_proxy.approve(allowed_address=service_registry_address, allowance=c3_price) c3_service_proxy.deposit(block_identifier="latest", limit_amount=c3_price) c3_token_proxy.proxy.transact("mint", 1000000, c3_price) c3_token_proxy.approve(allowed_address=service_registry_address, allowance=c3_price) c3_service_proxy.set_url(urls[2]) return c1_service_proxy, urls
def new_netting_channel(self, other_peer: Address, settle_timeout: int) -> Address: """ Creates and deploys a new netting channel contract. Args: other_peer: The peer to open the channel with. settle_timeout: The settle timout to use for this channel. Returns: The address of the new netting channel. """ if not isaddress(other_peer): raise ValueError('The other_peer must be a valid address') invalid_timeout = (settle_timeout < NETTINGCHANNEL_SETTLE_TIMEOUT_MIN or settle_timeout > NETTINGCHANNEL_SETTLE_TIMEOUT_MAX) if invalid_timeout: raise InvalidSettleTimeout( 'settle_timeout must be in range [{}, {}]'.format( NETTINGCHANNEL_SETTLE_TIMEOUT_MIN, NETTINGCHANNEL_SETTLE_TIMEOUT_MAX)) local_address = privatekey_to_address(self.client.privkey) if local_address == other_peer: raise SamePeerAddress( 'The other peer must not have the same address as the client.') transaction_hash = estimate_and_transact( self.proxy, 'newChannel', other_peer, settle_timeout, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) if check_transaction_threw(self.client, transaction_hash): raise DuplicatedChannelError('Duplicated channel') netting_channel_results_encoded = self.proxy.call( 'getChannelWith', other_peer, ) # address is at index 0 netting_channel_address_encoded = netting_channel_results_encoded if not netting_channel_address_encoded: log.error('netting_channel_address failed', peer1=pex(local_address), peer2=pex(other_peer)) raise RuntimeError('netting_channel_address failed') netting_channel_address_bin = address_decoder( netting_channel_address_encoded) if log.isEnabledFor(logging.INFO): log.info( 'new_netting_channel called', peer1=pex(local_address), peer2=pex(other_peer), netting_channel=pex(netting_channel_address_bin), ) return netting_channel_address_bin
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)
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 deposit(self, amount): """ Deposit amount token in the channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation RuntimeError: If the netting channel token address is empty. """ if not isinstance(amount, int): raise ValueError('amount needs to be an integral number.') token_address = self.token_address() token = Token( self.client, token_address, self.poll_timeout, ) current_balance = token.balance_of(self.node_address) if current_balance < amount: raise ValueError('deposit [{}] cant be larger than the available balance [{}].'.format( amount, current_balance, )) log.info( 'deposit called', node=pex(self.node_address), contract=pex(self.address), amount=amount, ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.' ) with releasing(self.channel_operations_lock): transaction_hash = estimate_and_transact( self.proxy, 'deposit', amount, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'deposit failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Deposit', receipt_or_none) log.info( 'deposit successful', node=pex(self.node_address), contract=pex(self.address), amount=amount, )