def channel_open( self, registry_address, token_address, partner_address, settle_timeout=None, reveal_timeout=None, poll_timeout=DEFAULT_POLL_TIMEOUT, retry_timeout=DEFAULT_RETRY_TIMEOUT, ): """ Open a channel with the peer at `partner_address` with the given `token_address`. """ if reveal_timeout is None: reveal_timeout = self.raiden.config['reveal_timeout'] if settle_timeout is None: settle_timeout = self.raiden.config['settle_timeout'] if settle_timeout <= reveal_timeout: raise InvalidSettleTimeout( 'reveal_timeout can not be larger-or-equal to settle_timeout', ) if not is_binary_address(registry_address): raise InvalidAddress('Expected binary address format for registry in channel open') if not is_binary_address(token_address): raise InvalidAddress('Expected binary address format for token in channel open') if not is_binary_address(partner_address): raise InvalidAddress('Expected binary address format for partner in channel open') registry = self.raiden.chain.token_network_registry(registry_address) token_network = registry.token_network_by_token(token_address) channel_identifier = token_network.new_netting_channel( partner_address, settle_timeout, ) msg = 'After {} seconds the channel was not properly created.'.format( poll_timeout, ) with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_newchannel( self.raiden, registry_address, token_address, partner_address, retry_timeout, ) return channel_identifier
def call(self, method, *args): """ Do the request and return the result. Args: method (str): The RPC method. args: The encoded arguments expected by the method. - Object arguments must be supplied as a dictionary. - Quantity arguments must be hex encoded starting with '0x' and without left zeros. - Data arguments must be hex encoded starting with '0x' """ request = self.protocol.create_request(method, args) reply = self.transport.send_message(request.serialize()) jsonrpc_reply = self.protocol.parse_reply(reply) if isinstance(jsonrpc_reply, JSONRPCSuccessResponse): return jsonrpc_reply.result elif isinstance(jsonrpc_reply, JSONRPCErrorResponse): raise EthNodeCommunicationError(jsonrpc_reply.error) else: raise EthNodeCommunicationError('Unknown type of JSONRPC reply')
def deposit(self, token_address, partner_address, amount): """ Deposit `amount` in the channel with the peer at `partner_address` and the given `token_address` in order to be able to do transfers. """ if not isaddress(token_address): raise InvalidAddress( 'Expected binary address format for token in channel deposit') if not isaddress(partner_address): raise InvalidAddress( 'Expected binary address format for partner in channel deposit' ) graph = self.raiden.token_to_channelgraph[token_address] channel = graph.partneraddress_to_channel[partner_address] netcontract_address = channel.external_state.netting_channel.address assert len(netcontract_address) # Obtain a reference to the token and approve the amount for funding token = self.raiden.chain.token(token_address) balance = token.balance_of(self.raiden.address.encode('hex')) if not balance >= amount: msg = "Not enough balance for token'{}' [{}]: have={}, need={}".format( token.proxy.name(), pex(token_address), balance, amount) raise InsufficientFunds(msg) token.approve(netcontract_address, amount) # Obtain the netting channel and fund it by depositing the amount old_balance = channel.contract_balance netting_channel = self.raiden.chain.netting_channel( netcontract_address) # actually make the deposit, and wait for it to be mined netting_channel.deposit(amount) # Wait until the balance has been updated via a state transition triggered # by processing the `ChannelNewBalance` event. # Usually, it'll take a single gevent.sleep, as we already have waited # for it to be mined, and only need to give the event handling greenlet # the chance to process the event, but let's wait 10s to be safe wait_timeout_secs = 10 # FIXME: hardcoded timeout if not wait_until( lambda: channel.contract_balance != old_balance, wait_timeout_secs, self.raiden.alarm.wait_time, ): raise EthNodeCommunicationError( 'After {} seconds the deposit was not properly processed.'. format(wait_timeout_secs)) return channel
def deposit(self, token_address, partner_address, amount): """ Deposit `amount` in the channel with the peer at `partner_address` and the given `token_address` in order to be able to do transfers. """ if not isaddress(token_address): raise InvalidAddress('Expected binary address format for token in channel deposit') if not isaddress(partner_address): raise InvalidAddress('Expected binary address format for partner in channel deposit') graph = self.raiden.token_to_channelgraph[token_address] channel = graph.partneraddress_to_channel[partner_address] netcontract_address = channel.external_state.netting_channel.address assert len(netcontract_address) # Obtain a reference to the token and approve the amount for funding token = self.raiden.chain.token(token_address) balance = token.balance_of(self.raiden.address.encode('hex')) if not balance >= amount: msg = "Not enough balance for token'{}' [{}]: have={}, need={}".format( token.proxy.name(), pex(token_address), balance, amount ) raise InsufficientFunds(msg) token.approve(netcontract_address, amount) # Obtain the netting channel and fund it by depositing the amount old_balance = channel.contract_balance netting_channel = self.raiden.chain.netting_channel(netcontract_address) netting_channel.deposit(amount) # Wait until the balance has been updated via a state transition triggered # by processing the `ChannelNewBalance` event wait_for = gevent.spawn( channel.wait_for_balance_update, old_balance, self.raiden.alarm.wait_time ) wait_timeout_secs = 60 gevent.wait([wait_for], timeout=wait_timeout_secs) # If balance is still the same then that means we did not get the event # after `timeout` seconds. if old_balance == channel.contract_balance: raise EthNodeCommunicationError( 'After {} seconds the deposit event was not seen by the ethereum node.'.format( wait_timeout_secs ) ) return channel
def token_network_register( self, registry_address, token_address, poll_timeout=DEFAULT_POLL_TIMEOUT, retry_timeout=DEFAULT_RETRY_TIMEOUT, ) -> typing.TokenNetworkAddress: """Register the `token_address` in the blockchain. If the address is already registered but the event has not been processed this function will block until the next block to make sure the event is processed. Raises: InvalidAddress: If the registry_address or token_address is not a valid address. AlreadyRegisteredTokenAddress: If the token is already registered. TransactionThrew: If the register transaction failed, this may happen because the account has not enough balance to pay for the gas or this register call raced with another transaction and lost. """ if not is_binary_address(registry_address): raise InvalidAddress( 'registry_address must be a valid address in binary') if not is_binary_address(token_address): raise InvalidAddress( 'token_address must be a valid address in binary') if token_address in self.get_tokens_list(registry_address): raise AlreadyRegisteredTokenAddress('Token already registered') try: registry = self.raiden.chain.token_network_registry( registry_address) msg = 'After {} seconds the channel was not properly created.'.format( poll_timeout, ) with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): return registry.add_token(token_address) finally: # Assume the transaction failed because the token is already # registered with the smart contract and this node has not yet # polled for the event (otherwise the check above would have # failed). # # To provide a consistent view to the user, wait one block, this # will guarantee that the events have been processed. next_block = self.raiden.get_block_number() + 1 waiting.wait_for_block(self.raiden, next_block, retry_timeout)
def channel_open(self, token_address, partner_address, settle_timeout=None, reveal_timeout=None, poll_timeout=DEFAULT_POLL_TIMEOUT): """ Open a channel with the peer at `partner_address` with the given `token_address`. """ if reveal_timeout is None: reveal_timeout = self.raiden.config['reveal_timeout'] if settle_timeout is None: settle_timeout = self.raiden.config['settle_timeout'] if settle_timeout <= reveal_timeout: raise InvalidSettleTimeout( 'reveal_timeout can not be larger-or-equal to settle_timeout') if not isaddress(token_address): raise InvalidAddress( 'Expected binary address format for token in channel open') if not isaddress(partner_address): raise InvalidAddress( 'Expected binary address format for partner in channel open') channel_manager = self.raiden.default_registry.manager_by_token( token_address) netcontract_address = channel_manager.new_netting_channel( partner_address, settle_timeout, ) msg = 'After {} seconds the channel was not properly created.'.format( poll_timeout) registry_address = self.raiden.default_registry.address with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_newchannel( self.raiden, registry_address, token_address, partner_address, self.raiden.alarm.wait_time, ) return netcontract_address
def _setup_web3(eth_rpc_endpoint): web3 = Web3(HTTPProvider(eth_rpc_endpoint)) try: node_version = web3.version.node # pylint: disable=no-member except ConnectTimeout: raise EthNodeCommunicationError("Couldn't connect to the ethereum node") supported, _ = is_supported_client(node_version) if not supported: click.secho( 'You need a Byzantium enabled ethereum node. Parity >= 1.7.6 or Geth >= 1.7.2', fg='red', ) sys.exit(1) return web3
def loop_until_stop(self): # The AlarmTask must have completed its first_run() before starting # the background greenlet. # # This is required because the first run will synchronize the node with # the blockchain since the last run. msg = "Only start the AlarmTask after it has been primed with the first_run" assert self.is_primed(), msg sleep_time = self.sleep_time while self._stop_event.wait(sleep_time) is not True: try: latest_block = self.chain.get_block(block_identifier="latest") except JSONDecodeError as e: raise EthNodeCommunicationError(str(e)) self._maybe_run_callbacks(latest_block)
def loop_until_stop(self): # The AlarmTask must have completed its first_run() before starting # the background greenlet. # # This is required because the first run will synchronize the node with # the blockchain since the last run. assert self.chain_id, 'chain_id not set' assert self.known_block_number is not None, 'known_block_number not set' sleep_time = self.sleep_time while self._stop_event.wait(sleep_time) is not True: try: latest_block = self.chain.get_block(block_identifier='latest') except JSONDecodeError as e: raise EthNodeCommunicationError(str(e)) self._maybe_run_callbacks(latest_block)
def __init__( self, web3: Web3, privkey: bytes, gasprice: int = None, nonce_offset: int = 0, ): if privkey is None or len(privkey) != 32: raise ValueError('Invalid private key') monkey_patch_web3(web3, self) try: version = web3.version.node except ConnectTimeout: raise EthNodeCommunicationError('couldnt reach the ethereum node') _, eth_node = is_supported_client(version) sender = privatekey_to_address(privkey) transaction_count = web3.eth.getTransactionCount( to_checksum_address(sender), 'pending') _available_nonce = transaction_count + nonce_offset self.eth_node = eth_node self.given_gas_price = gasprice self.privkey = privkey self.sender = sender # Needs to be initialized to None in the beginning since JSONRPCClient # gets constructed before the RaidenService Object. self.stop_event = None self.web3 = web3 self._gaslimit_cache = TTLCache(maxsize=16, ttl=RPC_CACHE_TTL) self._gasprice_cache = TTLCache(maxsize=16, ttl=RPC_CACHE_TTL) self._available_nonce = _available_nonce self._nonce_lock = Semaphore() self._nonce_offset = nonce_offset log.debug( 'JSONRPCClient created', sender=pex(self.sender), available_nonce=_available_nonce, )
def check_ethereum_client_is_supported(web3: Web3) -> None: try: node_version = web3.version.node # pylint: disable=no-member except ConnectTimeout: raise EthNodeCommunicationError( "Couldn't connect to the ethereum node") except ValueError: raise EthNodeInterfaceError( "The underlying ethereum node does not have the web3 rpc interface " "enabled. Please run it with --rpcapi eth,net,web3,txpool for geth " "and --jsonrpc-apis=eth,net,web3,parity for parity.") supported, _ = is_supported_client(node_version) if not supported: click.secho( "You need a Byzantium enabled ethereum node. Parity >= 1.7.6 or Geth >= 1.7.2", fg="red", ) sys.exit(1)
def _setup_web3(eth_rpc_endpoint): web3 = Web3(HTTPProvider(eth_rpc_endpoint)) try: node_version = web3.version.node # pylint: disable=no-member except ConnectTimeout: raise EthNodeCommunicationError( "Couldn't connect to the ethereum node") except ValueError: raise EthNodeInterfaceError( 'The underlying ethereum node does not have the web3 rpc interface ' 'enabled. Please run it with --rpcapi eth,net,web3,txpool for geth ' 'and --jsonrpc-apis=eth,net,web3,parity for parity.', ) supported, _ = is_supported_client(node_version) if not supported: click.secho( 'You need a Byzantium enabled ethereum node. Parity >= 1.7.6 or Geth >= 1.7.2', fg='red', ) sys.exit(1) return web3
def __init__( self, web3: Web3, privkey: bytes, gas_price_strategy: Callable = rpc_gas_price_strategy, nonce_offset: int = 0, ): if privkey is None or len(privkey) != 32: raise ValueError('Invalid private key') monkey_patch_web3(web3, self, gas_price_strategy) try: version = web3.version.node except ConnectTimeout: raise EthNodeCommunicationError('couldnt reach the ethereum node') _, eth_node = is_supported_client(version) sender = privatekey_to_address(privkey) transaction_count = web3.eth.getTransactionCount( to_checksum_address(sender), 'pending') _available_nonce = transaction_count + nonce_offset self.eth_node = eth_node self.privkey = privkey self.sender = sender self.web3 = web3 self._gasprice_cache = TTLCache(maxsize=16, ttl=RPC_CACHE_TTL) self._available_nonce = _available_nonce self._nonce_lock = Semaphore() self._nonce_offset = nonce_offset log.debug( 'JSONRPCClient created', sender=pex(self.sender), available_nonce=_available_nonce, )
def deposit(self, token_address, partner_address, amount, poll_timeout=DEFAULT_POLL_TIMEOUT): """ Deposit `amount` in the channel with the peer at `partner_address` and the given `token_address` in order to be able to do transfers. Raises: InvalidAddress: If either token_address or partner_address is not 20 bytes long. TransactionThrew: May happen for multiple reasons: - If the token approval fails, e.g. the token may validate if account has enough balance for the allowance. - The deposit failed, e.g. the allowance did not set the token aside for use and the user spent it before deposit was called. - The channel was closed/settled between the allowance call and the deposit call. AddressWithoutCode: The channel was settled during the deposit execution. """ if not isaddress(token_address): raise InvalidAddress( 'Expected binary address format for token in channel deposit') if not isaddress(partner_address): raise InvalidAddress( 'Expected binary address format for partner in channel deposit' ) graph = self.raiden.token_to_channelgraph.get(token_address) if graph is None: raise InvalidAddress('Unknown token address') channel = graph.partneraddress_to_channel.get(partner_address) if channel is None: raise InvalidAddress( 'No channel with partner_address for the given token') if channel.token_address != token_address: raise InvalidAddress( 'token_address does not match the netting channel attribute') token = self.raiden.chain.token(token_address) netcontract_address = channel.external_state.netting_channel.address old_balance = channel.contract_balance # Checking the balance is not helpful since this requires multiple # transactions that can race, e.g. the deposit check succeed but the # user spent his balance before deposit. balance = token.balance_of(hexlify(self.raiden.address)) if not balance >= amount: msg = 'Not enough balance to deposit. {} Available={} Tried={}'.format( pex(token_address), balance, amount, ) raise InsufficientFunds(msg) token.approve(netcontract_address, amount) channel_proxy = self.raiden.chain.netting_channel(netcontract_address) channel_proxy.deposit(amount) # Wait until the `ChannelNewBalance` event is processed. # # Usually a single sleep is sufficient, since the `deposit` waits for # the transaction to be polled. sucess = wait_until( lambda: channel.contract_balance != old_balance, poll_timeout, self.raiden.alarm.wait_time, ) if not sucess: raise EthNodeCommunicationError( 'After {} seconds the deposit was not properly processed.'. format(poll_timeout)) return channel
def run_app( address, keystore_path, gas_price, eth_rpc_endpoint, registry_contract_address, secret_registry_contract_address, discovery_contract_address, listen_address, mapped_socket, max_unresponsive_time, api_address, rpc, sync_check, console, password_file, web_ui, datadir, transport, matrix_server, network_id, extra_config=None, **kwargs, ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,unused-argument from raiden.app import App if transport == 'udp' and not mapped_socket: raise RuntimeError('Missing socket') address_hex = to_normalized_address(address) if address else None address_hex, privatekey_bin = prompt_account(address_hex, keystore_path, password_file) address = to_canonical_address(address_hex) (listen_host, listen_port) = split_endpoint(listen_address) (api_host, api_port) = split_endpoint(api_address) if datadir is None: datadir = os.path.join(os.path.expanduser('~'), '.raiden') config = deepcopy(App.DEFAULT_CONFIG) if extra_config: merge_dict(config, extra_config) config['transport']['udp']['host'] = listen_host config['transport']['udp']['port'] = listen_port config['console'] = console config['rpc'] = rpc config['web_ui'] = rpc and web_ui config['api_host'] = api_host config['api_port'] = api_port if mapped_socket: config['socket'] = mapped_socket.socket config['transport']['udp']['external_ip'] = mapped_socket.external_ip config['transport']['udp'][ 'external_port'] = mapped_socket.external_port config['transport_type'] = transport config['transport']['matrix']['server'] = matrix_server config['transport']['udp'][ 'nat_keepalive_retries'] = DEFAULT_NAT_KEEPALIVE_RETRIES timeout = max_unresponsive_time / DEFAULT_NAT_KEEPALIVE_RETRIES config['transport']['udp']['nat_keepalive_timeout'] = timeout privatekey_hex = hexlify(privatekey_bin) config['privatekey_hex'] = privatekey_hex parsed_eth_rpc_endpoint = urlparse(eth_rpc_endpoint) if not parsed_eth_rpc_endpoint.scheme: eth_rpc_endpoint = f'http://{eth_rpc_endpoint}' web3 = Web3(HTTPProvider(eth_rpc_endpoint)) try: node_version = web3.version.node # pylint: disable=no-member except ConnectTimeout: raise EthNodeCommunicationError( "Couldn't connect to the ethereum node") supported, _ = is_supported_client(node_version) if not supported: print( 'You need a Byzantium enabled ethereum node. Parity >= 1.7.6 or Geth >= 1.7.2' ) sys.exit(1) rpc_client = JSONRPCClient( web3, privatekey_bin, gasprice=gas_price, ) blockchain_service = BlockChainService(privatekey_bin, rpc_client) net_id = blockchain_service.network_id if net_id != network_id: if network_id in constants.ID_TO_NETWORKNAME and net_id in constants.ID_TO_NETWORKNAME: print(( "The chosen ethereum network '{}' differs from the ethereum client '{}'. " 'Please update your settings.').format( constants.ID_TO_NETWORKNAME[network_id], constants.ID_TO_NETWORKNAME[net_id])) else: print(( "The chosen ethereum network id '{}' differs from the ethereum client '{}'. " 'Please update your settings.').format(network_id, net_id)) sys.exit(1) config['chain_id'] = network_id if sync_check: check_synced(blockchain_service) database_path = os.path.join(datadir, 'netid_%s' % net_id, address_hex[:8], 'log.db') config['database_path'] = database_path print( '\nYou are connected to the \'{}\' network and the DB path is: {}'. format( constants.ID_TO_NETWORKNAME.get(net_id) or net_id, database_path, ), ) contract_addresses_given = (registry_contract_address is not None and secret_registry_contract_address is not None and discovery_contract_address is not None) contract_addresses_known = net_id in constants.ID_TO_NETWORK_CONFIG if not contract_addresses_given and not contract_addresses_known: print(( "There are known contract addresses for network id '{}'. Please provide " 'them in the command line or the configuration file.' ).format(net_id)) sys.exit(1) contract_addresses = constants.ID_TO_NETWORK_CONFIG.get(net_id, dict()) try: token_network_registry = blockchain_service.token_network_registry( registry_contract_address or contract_addresses[CONTRACT_TOKEN_NETWORK_REGISTRY], ) except ContractVersionMismatch: handle_contract_version_mismatch('token network registry', registry_contract_address) except AddressWithoutCode: handle_contract_no_code('token network registry', registry_contract_address) except AddressWrongContract: handle_contract_wrong_address('token network registry', registry_contract_address) try: secret_registry = blockchain_service.secret_registry( secret_registry_contract_address or contract_addresses[CONTRACT_SECRET_REGISTRY], ) except ContractVersionMismatch: handle_contract_version_mismatch('secret registry', secret_registry_contract_address) except AddressWithoutCode: handle_contract_no_code('secret registry', secret_registry_contract_address) except AddressWrongContract: handle_contract_wrong_address('secret registry', secret_registry_contract_address) discovery = None if transport == 'udp': check_discovery_registration_gas(blockchain_service, address) try: dicovery_proxy = blockchain_service.discovery( discovery_contract_address or contract_addresses[CONTRACT_ENDPOINT_REGISTRY], ) discovery = ContractDiscovery( blockchain_service.node_address, dicovery_proxy, ) except ContractVersionMismatch: handle_contract_version_mismatch('discovery', discovery_contract_address) except AddressWithoutCode: handle_contract_no_code('discovery', discovery_contract_address) except AddressWrongContract: handle_contract_wrong_address('discovery', discovery_contract_address) throttle_policy = TokenBucket( config['transport']['udp']['throttle_capacity'], config['transport']['udp']['throttle_fill_rate'], ) transport = UDPTransport( discovery, mapped_socket.socket, throttle_policy, config['transport']['udp'], ) elif transport == 'matrix': try: transport = MatrixTransport(config['transport']['matrix']) except RaidenError as ex: click.secho(f'FATAL: {ex}', fg='red') sys.exit(1) else: raise RuntimeError(f'Unknown transport type "{transport}" given') try: chain_config = constants.ID_TO_NETWORK_CONFIG.get(net_id, {}) start_block = chain_config.get(constants.START_QUERY_BLOCK_KEY, 0) raiden_app = App( config=config, chain=blockchain_service, query_start_block=start_block, default_registry=token_network_registry, default_secret_registry=secret_registry, transport=transport, discovery=discovery, ) except RaidenError as e: click.secho(f'FATAL: {e}', fg='red') sys.exit(1) try: raiden_app.start() except filelock.Timeout: name_or_id = constants.ID_TO_NETWORKNAME.get(network_id, network_id) print( f'FATAL: Another Raiden instance already running for account {address_hex} on ' f'network id {name_or_id}', ) sys.exit(1) return raiden_app
def __init__( self, web3: Web3, privkey: bytes, gas_price_strategy: Callable = rpc_gas_price_strategy, gas_estimate_correction: Callable = lambda gas: gas, block_num_confirmations: int = 0, uses_infura=False, ): if privkey is None or len(privkey) != 32: raise ValueError("Invalid private key") if block_num_confirmations < 0: raise ValueError("Number of confirmations has to be positive") monkey_patch_web3(web3, gas_price_strategy) try: version = web3.version.node except ConnectTimeout: raise EthNodeCommunicationError("couldnt reach the ethereum node") _, eth_node = is_supported_client(version) address = privatekey_to_address(privkey) address_checksumed = to_checksum_address(address) if uses_infura: warnings.warn( "Infura does not provide an API to " "recover the latest used nonce. This may cause the Raiden node " "to error on restarts.\n" "The error will manifest while there is a pending transaction " "from a previous execution in the Ethereum's client pool. When " "Raiden restarts the same transaction with the same nonce will " "be retried and *rejected*, because the nonce is already used." ) # The first valid nonce is 0, therefore the count is already the next # available nonce available_nonce = web3.eth.getTransactionCount( address_checksumed, "pending") elif eth_node is constants.EthClient.PARITY: parity_assert_rpc_interfaces(web3) available_nonce = parity_discover_next_available_nonce( web3, address_checksumed) elif eth_node is constants.EthClient.GETH: geth_assert_rpc_interfaces(web3) available_nonce = geth_discover_next_available_nonce( web3, address_checksumed) else: raise EthNodeInterfaceError( f"Unsupported Ethereum client {version}") self.eth_node = eth_node self.privkey = privkey self.address = address self.web3 = web3 self.default_block_num_confirmations = block_num_confirmations self._available_nonce = available_nonce self._nonce_lock = Semaphore() self._gas_estimate_correction = gas_estimate_correction log.debug( "JSONRPCClient created", node=pex(self.address), available_nonce=available_nonce, client=version, )
def channel_batch_close(self, token_address, partner_addresses, poll_timeout=DEFAULT_POLL_TIMEOUT): """Close a channel opened with `partner_address` for the given `token_address`. Race condition, this can fail if channel was closed externally. """ if not isaddress(token_address): raise InvalidAddress( 'Expected binary address format for token in channel close') if not all(map(isaddress, partner_addresses)): raise InvalidAddress( 'Expected binary address format for partner in channel close') valid_tokens = views.get_token_network_addresses_for( views.state_from_raiden(self.raiden), self.raiden.default_registry.address, ) if token_address not in valid_tokens: raise UnknownTokenAddress('Token address is not known.') registry_address = self.raiden.default_registry.address node_state = views.state_from_raiden(self.raiden) channels_to_close = views.filter_channels_by_partneraddress( node_state, registry_address, token_address, partner_addresses, ) # If concurrent operations are happening on one of the channels, fail entire # request. with ExitStack() as stack: # Put all the locks in this outer context so that the netting channel functions # don't release the locks when their context goes out of scope for channel_state in channels_to_close: channel = self.raiden.chain.netting_channel( channel_state.identifier) # Check if we can acquire the lock. If we can't raise an exception, which # will cause the ExitStack to exit, releasing all locks acquired so far if not channel.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with id {channel_state.identifier} is ' f'busy with another ongoing operation.') stack.push(channel.channel_operations_lock) for channel_state in channels_to_close: channel_close = ActionChannelClose( registry_address, token_address, channel_state.identifier, ) self.raiden.handle_state_change(channel_close) msg = 'After {} seconds the deposit was not properly processed.'.format( poll_timeout) channel_ids = [ channel_state.identifier for channel_state in channels_to_close ] with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_close( self.raiden, registry_address, token_address, channel_ids, self.raiden.alarm.wait_time, )
def channel_deposit(self, token_address, partner_address, amount, poll_timeout=DEFAULT_POLL_TIMEOUT): """ Deposit `amount` in the channel with the peer at `partner_address` and the given `token_address` in order to be able to do transfers. Raises: InvalidAddress: If either token_address or partner_address is not 20 bytes long. TransactionThrew: May happen for multiple reasons: - If the token approval fails, e.g. the token may validate if account has enough balance for the allowance. - The deposit failed, e.g. the allowance did not set the token aside for use and the user spent it before deposit was called. - The channel was closed/settled between the allowance call and the deposit call. AddressWithoutCode: The channel was settled during the deposit execution. """ node_state = views.state_from_raiden(self.raiden) registry_address = self.raiden.default_registry.address token_networks = views.get_token_network_addresses_for( node_state, registry_address, ) channel_state = views.get_channelstate_for( node_state, registry_address, token_address, partner_address, ) if not isaddress(token_address): raise InvalidAddress( 'Expected binary address format for token in channel deposit') if not isaddress(partner_address): raise InvalidAddress( 'Expected binary address format for partner in channel deposit' ) if token_address not in token_networks: raise UnknownTokenAddress('Unknown token address') if channel_state is None: raise InvalidAddress( 'No channel with partner_address for the given token') token = self.raiden.chain.token(token_address) balance = token.balance_of(hexlify(self.raiden.address)) # If this check succeeds it does not imply the the `deposit` will # succeed, since the `deposit` transaction may race with another # transaction. if not balance >= amount: msg = 'Not enough balance to deposit. {} Available={} Tried={}'.format( pex(token_address), balance, amount, ) raise InsufficientFunds(msg) netcontract_address = channel_state.identifier channel_proxy = self.raiden.chain.netting_channel(netcontract_address) # If concurrent operations are happening on the channel, fail the request if not channel_proxy.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with id {channel_state.identifier} is ' f'busy with another ongoing operation') with releasing(channel_proxy.channel_operations_lock): token.approve(netcontract_address, amount) channel_proxy.deposit(amount) old_balance = channel_state.our_state.contract_balance target_balance = old_balance + amount msg = 'After {} seconds the deposit was not properly processed.'.format( poll_timeout) # Wait until the `ChannelNewBalance` event is processed. with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_newbalance( self.raiden, registry_address, token_address, partner_address, target_balance, self.raiden.alarm.wait_time, )
def channel_open( self, registry_address, token_address, partner_address, settle_timeout=None, reveal_timeout=None, poll_timeout=DEFAULT_POLL_TIMEOUT, retry_timeout=DEFAULT_RETRY_TIMEOUT, ): """ Open a channel with the peer at `partner_address` with the given `token_address`. """ if reveal_timeout is None: reveal_timeout = self.raiden.config['reveal_timeout'] if settle_timeout is None: settle_timeout = self.raiden.config['settle_timeout'] if settle_timeout <= reveal_timeout: raise InvalidSettleTimeout( 'reveal_timeout can not be larger-or-equal to settle_timeout', ) if not is_binary_address(registry_address): raise InvalidAddress( 'Expected binary address format for registry in channel open') if not is_binary_address(token_address): raise InvalidAddress( 'Expected binary address format for token in channel open') if not is_binary_address(partner_address): raise InvalidAddress( 'Expected binary address format for partner in channel open') chain_state = views.state_from_raiden(self.raiden) channel_state = views.get_channelstate_for( chain_state, registry_address, token_address, partner_address, ) if channel_state: raise DuplicatedChannelError( 'Channel with given partner address already exists') registry = self.raiden.chain.token_network_registry(registry_address) token_network = self.raiden.chain.token_network( registry.get_token_network(token_address), ) try: token_network.new_netting_channel( partner_address, settle_timeout, ) except DuplicatedChannelError: log.info('partner opened channel first') msg = 'After {} seconds the channel was not properly created.'.format( poll_timeout, ) with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_newchannel( self.raiden, registry_address, token_address, partner_address, retry_timeout, ) chain_state = views.state_from_raiden(self.raiden) channel_state = views.get_channelstate_for( chain_state, registry_address, token_address, partner_address, ) return channel_state.identifier
def set_total_channel_deposit( self, registry_address, token_address, partner_address, total_deposit, poll_timeout=DEFAULT_POLL_TIMEOUT, retry_timeout=DEFAULT_RETRY_TIMEOUT, ): """ Set the `total_deposit` in the channel with the peer at `partner_address` and the given `token_address` in order to be able to do transfers. Raises: InvalidAddress: If either token_address or partner_address is not 20 bytes long. TransactionThrew: May happen for multiple reasons: - If the token approval fails, e.g. the token may validate if account has enough balance for the allowance. - The deposit failed, e.g. the allowance did not set the token aside for use and the user spent it before deposit was called. - The channel was closed/settled between the allowance call and the deposit call. AddressWithoutCode: The channel was settled during the deposit execution. DepositOverLimit: The total deposit amount is higher than the limit. """ chain_state = views.state_from_raiden(self.raiden) token_networks = views.get_token_network_addresses_for( chain_state, registry_address, ) channel_state = views.get_channelstate_for( chain_state, registry_address, token_address, partner_address, ) if not is_binary_address(token_address): raise InvalidAddress( 'Expected binary address format for token in channel deposit') if not is_binary_address(partner_address): raise InvalidAddress( 'Expected binary address format for partner in channel deposit' ) if token_address not in token_networks: raise UnknownTokenAddress('Unknown token address') if channel_state is None: raise InvalidAddress( 'No channel with partner_address for the given token') token = self.raiden.chain.token(token_address) netcontract_address = channel_state.identifier token_network_registry = self.raiden.chain.token_network_registry( registry_address) token_network_address = token_network_registry.get_token_network( token_address) token_network_proxy = self.raiden.chain.token_network( token_network_address) channel_proxy = self.raiden.chain.payment_channel( token_network_proxy.address, netcontract_address, ) balance = token.balance_of(self.raiden.address) deposit_limit = token_network_proxy.proxy.contract.functions.deposit_limit( ).call() if total_deposit > deposit_limit: raise DepositOverLimit( 'The deposit of {} is bigger than the current limit of {}'. format( total_deposit, deposit_limit, ), ) if total_deposit <= channel_state.our_state.contract_balance: # no action required return addendum = total_deposit - channel_state.our_state.contract_balance # If this check succeeds it does not imply the the `deposit` will # succeed, since the `deposit` transaction may race with another # transaction. if not balance >= addendum: msg = 'Not enough balance to deposit. {} Available={} Needed={}'.format( pex(token_address), balance, addendum, ) raise InsufficientFunds(msg) # If concurrent operations are happening on the channel, fail the request with channel_proxy.lock_or_raise(): # set_total_deposit calls approve # token.approve(netcontract_address, addendum) channel_proxy.set_total_deposit(total_deposit) msg = 'After {} seconds the deposit was not properly processed.'.format( poll_timeout, ) # Wait until the `ChannelNewBalance` event is processed. with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): target_address = self.raiden.address waiting.wait_for_participant_newbalance( self.raiden, registry_address, token_address, partner_address, target_address, total_deposit, retry_timeout, )
def channel_batch_close( self, registry_address, token_address, partner_addresses, poll_timeout=DEFAULT_POLL_TIMEOUT, retry_timeout=DEFAULT_RETRY_TIMEOUT, ): """Close a channel opened with `partner_address` for the given `token_address`. Race condition, this can fail if channel was closed externally. """ if not is_binary_address(token_address): raise InvalidAddress( 'Expected binary address format for token in channel close') if not all(map(is_binary_address, partner_addresses)): raise InvalidAddress( 'Expected binary address format for partner in channel close') valid_tokens = views.get_token_network_addresses_for( views.state_from_raiden(self.raiden), registry_address, ) if token_address not in valid_tokens: raise UnknownTokenAddress('Token address is not known.') chain_state = views.state_from_raiden(self.raiden) channels_to_close = views.filter_channels_by_partneraddress( chain_state, registry_address, token_address, partner_addresses, ) token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_raiden(self.raiden), registry_address, token_address, ) # If concurrent operations are happening on one of the channels, fail entire # request. with ExitStack() as stack: # Put all the locks in this outer context so that the netting channel functions # don't release the locks when their context goes out of scope for channel_state in channels_to_close: channel = self.raiden.chain.payment_channel( token_network_identifier, channel_state.identifier, ) stack.enter_context(channel.lock_or_raise()) for channel_state in channels_to_close: channel_close = ActionChannelClose( token_network_identifier, channel_state.identifier, ) self.raiden.handle_state_change(channel_close) msg = 'After {} seconds the closing transactions were not properly processed.'.format( poll_timeout, ) channel_ids = [ channel_state.identifier for channel_state in channels_to_close ] with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_close( self.raiden, registry_address, token_address, channel_ids, retry_timeout, )