class RestAPI: """ This wraps around the actual RaidenAPI in api/python. It will provide the additional, neccessary RESTful logic and the proper JSON-encoding of the Objects provided by the RaidenAPI """ version = 1 def __init__(self, raiden_api): self.raiden_api = raiden_api self.channel_schema = ChannelStateSchema() self.address_list_schema = AddressListSchema() self.partner_per_token_list_schema = PartnersPerTokenListSchema() self.payment_schema = PaymentSchema() self.sent_success_payment_schema = EventPaymentSentSuccessSchema() self.received_success_payment_schema = EventPaymentReceivedSuccessSchema( ) self.failed_payment_schema = EventPaymentSentFailedSchema() def get_our_address(self): return api_response(result=dict( our_address=to_checksum_address(self.raiden_api.address)), ) def register_token( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, ): log.debug( 'Registering token', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), ) try: token_network_address = self.raiden_api.token_network_register( registry_address, token_address, ) except EthNodeCommunicationError: return api_response( result='', status_code=HTTPStatus.ACCEPTED, ) except (InvalidAddress, AlreadyRegisteredTokenAddress, TransactionThrew) as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) except InsufficientFunds as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) return api_response( result=dict(token_network_address=to_checksum_address( token_network_address)), status_code=HTTPStatus.CREATED, ) def open( self, registry_address: typing.PaymentNetworkID, partner_address: typing.Address, token_address: typing.TokenAddress, settle_timeout: typing.BlockTimeout = None, reveal_timeout: typing.BlockTimeout = None, total_deposit: typing.TokenAmount = None, ): log.debug( 'Opening channel', registry_address=to_checksum_address(registry_address), partner_address=to_checksum_address(partner_address), token_address=to_checksum_address(token_address), settle_timeout=settle_timeout, reveal_timeout=reveal_timeout, ) try: self.raiden_api.channel_open( registry_address, token_address, partner_address, settle_timeout, reveal_timeout, ) except EthNodeCommunicationError: return api_response( result='', status_code=HTTPStatus.ACCEPTED, ) except (InvalidAddress, InvalidSettleTimeout, SamePeerAddress, AddressWithoutCode, DuplicatedChannelError, TokenNotRegistered) as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) except (InsufficientFunds, InsufficientGasReserve) as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) if total_deposit: # make initial deposit log.debug( 'Depositing to new channel', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), partner_address=to_checksum_address(partner_address), total_deposit=total_deposit, ) try: self.raiden_api.set_total_channel_deposit( registry_address=registry_address, token_address=token_address, partner_address=partner_address, total_deposit=total_deposit, ) except EthNodeCommunicationError: return api_response( result='', status_code=HTTPStatus.ACCEPTED, ) except InsufficientFunds as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) except (DepositOverLimit, DepositMismatch) as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) channel_state = views.get_channelstate_for( views.state_from_raiden(self.raiden_api.raiden), registry_address, token_address, partner_address, ) result = self.channel_schema.dump(channel_state) return api_response( result=result.data, status_code=HTTPStatus.CREATED, ) def connect( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, funds: typing.TokenAmount, initial_channel_target: int = None, joinable_funds_target: float = None, ): log.debug( 'Connecting to token network', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), funds=funds, initial_channel_target=initial_channel_target, joinable_funds_target=joinable_funds_target, ) try: self.raiden_api.token_network_connect( registry_address, token_address, funds, initial_channel_target, joinable_funds_target, ) except EthNodeCommunicationError: return api_response( result='', status_code=HTTPStatus.ACCEPTED, ) except (InsufficientFunds, InsufficientGasReserve) as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) except (InvalidAmount, InvalidAddress) as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) return api_response( result=dict(), status_code=HTTPStatus.NO_CONTENT, ) def leave( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, ): log.debug( 'Leaving token network', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), ) closed_channels = self.raiden_api.token_network_leave( registry_address, token_address, ) closed_channels = [ self.channel_schema.dump(channel_state).data for channel_state in closed_channels ] return api_response(result=closed_channels) def get_connection_managers_info( self, registry_address: typing.PaymentNetworkID): """Get a dict whose keys are token addresses and whose values are open channels, funds of last request, sum of deposits and number of channels""" log.debug( 'Getting connection managers info', registry_address=to_checksum_address(registry_address), ) connection_managers = dict() for token in self.raiden_api.get_tokens_list(registry_address): token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_raiden(self.raiden_api.raiden), payment_network_id=registry_address, token_address=token, ) try: connection_manager = self.raiden_api.raiden.connection_manager_for_token_network( token_network_identifier, ) except InvalidAddress: connection_manager = None open_channels = views.get_channelstate_open( chain_state=views.state_from_raiden(self.raiden_api.raiden), payment_network_id=registry_address, token_address=token, ) if connection_manager is not None and open_channels: connection_managers[to_checksum_address( connection_manager.token_address)] = { 'funds': connection_manager.funds, 'sum_deposits': views.get_our_capacity_for_token_network( views.state_from_raiden(self.raiden_api.raiden), registry_address, token, ), 'channels': len(open_channels), } return connection_managers def get_channel_list( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress = None, partner_address: typing.Address = None, ): log.debug( 'Getting channel list', registry_address=to_checksum_address(registry_address), token_address=optional_address_to_string(token_address), partner_address=optional_address_to_string(partner_address), ) raiden_service_result = self.raiden_api.get_channel_list( registry_address, token_address, partner_address, ) assert isinstance(raiden_service_result, list) result = [ self.channel_schema.dump(channel_schema).data for channel_schema in raiden_service_result ] return api_response(result=result) def get_tokens_list(self, registry_address: typing.PaymentNetworkID): log.debug( 'Getting token list', registry_address=registry_address, ) raiden_service_result = self.raiden_api.get_tokens_list( registry_address) assert isinstance(raiden_service_result, list) tokens_list = AddressList(raiden_service_result) result = self.address_list_schema.dump(tokens_list) return api_response(result=result.data) def get_blockchain_events_network( self, registry_address: typing.PaymentNetworkID, from_block: typing.BlockSpecification = 0, to_block: typing.BlockSpecification = 'latest', ): log.debug( 'Getting network events', registry_address=to_checksum_address(registry_address), from_block=from_block, to_block=to_block, ) try: raiden_service_result = self.raiden_api.get_blockchain_events_network( registry_address=registry_address, from_block=from_block, to_block=to_block, ) except InvalidBlockNumberInput as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) return api_response( result=normalize_events_list(raiden_service_result)) def get_blockchain_events_token_network( self, token_address: typing.TokenAddress, from_block: typing.BlockSpecification = 0, to_block: typing.BlockSpecification = 'latest', ): log.debug( 'Getting token network blockchain events', token_address=token_address, ) try: raiden_service_result = self.raiden_api.get_blockchain_events_token_network( token_address=token_address, from_block=from_block, to_block=to_block, ) return api_response( result=normalize_events_list(raiden_service_result)) except UnknownTokenAddress as e: return api_error(str(e), status_code=HTTPStatus.NOT_FOUND) except (InvalidBlockNumberInput, InvalidAddress) as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) def get_raiden_events_payment_history( self, token_address: typing.TokenAddress = None, target_address: typing.Address = None, limit: int = None, offset: int = None, ): log.debug( 'Getting payment history', token_address=optional_address_to_string(token_address), target_address=optional_address_to_string(target_address), ) try: raiden_service_result = self.raiden_api.get_raiden_events_payment_history( token_address=token_address, target_address=target_address, limit=limit, offset=offset, ) except (InvalidBlockNumberInput, InvalidAddress) as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) result = [] for event in raiden_service_result: if isinstance(event, EventPaymentSentSuccess): serialized_event = self.sent_success_payment_schema.dump(event) elif isinstance(event, EventPaymentSentFailed): serialized_event = self.failed_payment_schema.dump(event) elif isinstance(event, EventPaymentReceivedSuccess): serialized_event = self.received_success_payment_schema.dump( event) else: log.warning('Unexpected event', unexpected_event=event) result.append(serialized_event.data) return api_response(result=result) def get_raiden_internal_events(self, limit, offset): return [ str(e) for e in self.raiden_api.raiden.wal.storage.get_events( limit=limit, offset=offset) ] def get_blockchain_events_channel( self, token_address: typing.TokenAddress, partner_address: typing.Address = None, from_block: typing.BlockSpecification = 0, to_block: typing.BlockSpecification = 'latest', ): log.debug( 'Getting channel blockchain events', token_address=token_address, partner_address=partner_address, ) try: raiden_service_result = self.raiden_api.get_blockchain_events_channel( token_address=token_address, partner_address=partner_address, from_block=from_block, to_block=to_block, ) return api_response( result=normalize_events_list(raiden_service_result)) except (InvalidBlockNumberInput, InvalidAddress) as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) except UnknownTokenAddress as e: return api_error(str(e), status_code=HTTPStatus.NOT_FOUND) def get_channel( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, partner_address: typing.Address, ): log.debug( 'Getting channel', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), partner_address=to_checksum_address(partner_address), ) try: channel_state = self.raiden_api.get_channel( registry_address=registry_address, token_address=token_address, partner_address=partner_address, ) result = self.channel_schema.dump(channel_state) return api_response(result=result.data) except ChannelNotFound as e: return api_error( errors=str(e), status_code=HTTPStatus.NOT_FOUND, ) def get_partners_by_token( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, ): log.debug( 'Getting partners by token', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), ) return_list = [] try: raiden_service_result = self.raiden_api.get_channel_list( registry_address, token_address, ) except InvalidAddress as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) for result in raiden_service_result: return_list.append({ 'partner_address': result.partner_state.address, 'channel': url_for( # TODO: Somehow nicely parameterize this for future versions 'v1_resources.channelsresourcebytokenandpartneraddress', token_address=token_address, partner_address=result.partner_state.address, ), }) schema_list = PartnersPerTokenList(return_list) result = self.partner_per_token_list_schema.dump(schema_list) return api_response(result=result.data) def initiate_payment( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, target_address: typing.Address, amount: typing.TokenAmount, identifier: typing.PaymentID, ): log.debug( 'Initiating payment', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), target_address=to_checksum_address(target_address), amount=amount, payment_identifier=identifier, ) if identifier is None: identifier = create_default_identifier() try: transfer_result = self.raiden_api.transfer( registry_address=registry_address, token_address=token_address, target=target_address, amount=amount, identifier=identifier, ) except (InvalidAmount, InvalidAddress) as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) except InsufficientFunds as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) if transfer_result is False: return api_error( errors="Payment couldn't be completed " "(insufficient funds, no route to target or target offline).", status_code=HTTPStatus.CONFLICT, ) payment = { 'initiator_address': self.raiden_api.address, 'registry_address': registry_address, 'token_address': token_address, 'target_address': target_address, 'amount': amount, 'identifier': identifier, } result = self.payment_schema.dump(payment) return api_response(result=result.data) def _deposit( self, registry_address: typing.PaymentNetworkID, channel_state: NettingChannelState, total_deposit: typing.TokenAmount, ): log.debug( 'Depositing to channel', registry_address=to_checksum_address(registry_address), channel_identifier=channel_state.identifier, total_deposit=total_deposit, ) if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: return api_error( errors="Can't set total deposit on a closed channel", status_code=HTTPStatus.CONFLICT, ) try: self.raiden_api.set_total_channel_deposit( registry_address, channel_state.token_address, channel_state.partner_state.address, total_deposit, ) except EthNodeCommunicationError: return api_response( result='', status_code=HTTPStatus.ACCEPTED, ) except InsufficientFunds as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) except DepositOverLimit as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) except DepositMismatch as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) updated_channel_state = self.raiden_api.get_channel( registry_address, channel_state.token_address, channel_state.partner_state.address, ) result = self.channel_schema.dump(updated_channel_state) return api_response(result=result.data) def _close( self, registry_address: typing.PaymentNetworkID, channel_state: NettingChannelState, ): log.debug( 'Closing channel', registry_address=to_checksum_address(registry_address), channel_identifier=channel_state.identifier, ) if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: return api_error( errors='Attempted to close an already closed channel', status_code=HTTPStatus.CONFLICT, ) try: self.raiden_api.channel_close( registry_address, channel_state.token_address, channel_state.partner_state.address, ) except EthNodeCommunicationError: return api_response( result='', status_code=HTTPStatus.ACCEPTED, ) except InsufficientFunds as e: return api_error( errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED, ) updated_channel_state = self.raiden_api.get_channel( registry_address, channel_state.token_address, channel_state.partner_state.address, ) result = self.channel_schema.dump(updated_channel_state) return api_response(result=result.data) def patch_channel( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, partner_address: typing.Address, total_deposit: typing.TokenAmount = None, state: str = None, ): log.debug( 'Patching channel', registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), partner_address=to_checksum_address(partner_address), total_deposit=total_deposit, state=state, ) if total_deposit is not None and state is not None: return api_error( errors= "Can not update a channel's total deposit and state at the same time", status_code=HTTPStatus.CONFLICT, ) if total_deposit is None and state is None: return api_error( errors= "Nothing to do. Should either provide 'total_deposit' or 'state' argument", status_code=HTTPStatus.BAD_REQUEST, ) if total_deposit and total_deposit < 0: return api_error( errors="Amount to deposit must not be negative.", status_code=HTTPStatus.CONFLICT, ) try: channel_state = self.raiden_api.get_channel( registry_address=registry_address, token_address=token_address, partner_address=partner_address, ) except ChannelNotFound: return api_error( errors='Requested channel for token {} and partner {} not found' .format( to_checksum_address(token_address), to_checksum_address(partner_address), ), status_code=HTTPStatus.CONFLICT, ) except InvalidAddress as e: return api_error( errors=str(e), status_code=HTTPStatus.CONFLICT, ) if total_deposit is not None: result = self._deposit(registry_address, channel_state, total_deposit) elif state == CHANNEL_STATE_CLOSED: result = self._close(registry_address, channel_state) else: # should never happen, channel_state is validated in the schema result = api_error( errors='Provided invalid channel state {}'.format(state), status_code=HTTPStatus.BAD_REQUEST, ) return result
class RestAPI: """ This wraps around the actual RaidenAPI in api/python. It will provide the additional, neccessary RESTful logic and the proper JSON-encoding of the Objects provided by the RaidenAPI """ version = 1 def __init__(self, raiden_api): self.raiden_api = raiden_api self.channel_schema = ChannelStateSchema() self.address_list_schema = AddressListSchema() self.partner_per_token_list_schema = PartnersPerTokenListSchema() self.payment_schema = PaymentSchema() self.sent_success_payment_schema = EventPaymentSentSuccessSchema() self.received_success_payment_schema = EventPaymentReceivedSuccessSchema( ) self.failed_payment_schema = EventPaymentSentFailedSchema() def get_our_address(self): return api_response(result=dict( our_address=to_checksum_address(self.raiden_api.address))) def register_token(self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress): if self.raiden_api.raiden.config[ "environment_type"] == Environment.PRODUCTION: return api_error( errors= "Registering a new token is currently disabled in the Ethereum mainnet", status_code=HTTPStatus.NOT_IMPLEMENTED, ) conflict_exceptions = ( InvalidAddress, AlreadyRegisteredTokenAddress, TransactionThrew, InvalidToken, AddressWithoutCode, ) log.debug( "Registering token", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), ) try: token_network_address = self.raiden_api.token_network_register( registry_address=registry_address, token_address=token_address, channel_participant_deposit_limit=UINT256_MAX, token_network_deposit_limit=UINT256_MAX, ) except conflict_exceptions as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) except InsufficientFunds as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) return api_response( result=dict(token_network_address=to_checksum_address( token_network_address)), status_code=HTTPStatus.CREATED, ) def open( self, registry_address: typing.PaymentNetworkID, partner_address: typing.Address, token_address: typing.TokenAddress, settle_timeout: typing.BlockTimeout = None, total_deposit: typing.TokenAmount = None, ): log.debug( "Opening channel", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), partner_address=to_checksum_address(partner_address), token_address=to_checksum_address(token_address), settle_timeout=settle_timeout, ) try: token = self.raiden_api.raiden.chain.token(token_address) except AddressWithoutCode as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) balance = token.balance_of(self.raiden_api.raiden.address) if total_deposit is not None and total_deposit > balance: error_msg = "Not enough balance to deposit. {} Available={} Needed={}".format( pex(token_address), balance, total_deposit) return api_error(errors=error_msg, status_code=HTTPStatus.PAYMENT_REQUIRED) try: self.raiden_api.channel_open(registry_address, token_address, partner_address, settle_timeout) except ( InvalidAddress, InvalidSettleTimeout, SamePeerAddress, AddressWithoutCode, DuplicatedChannelError, TokenNotRegistered, ) as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) except (InsufficientFunds, InsufficientGasReserve) as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) if total_deposit: # make initial deposit log.debug( "Depositing to new channel", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), partner_address=to_checksum_address(partner_address), total_deposit=total_deposit, ) try: self.raiden_api.set_total_channel_deposit( registry_address=registry_address, token_address=token_address, partner_address=partner_address, total_deposit=total_deposit, ) except InsufficientFunds as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) except (DepositOverLimit, DepositMismatch) as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) channel_state = views.get_channelstate_for( views.state_from_raiden(self.raiden_api.raiden), registry_address, token_address, partner_address, ) result = self.channel_schema.dump(channel_state) return api_response(result=result.data, status_code=HTTPStatus.CREATED) def connect( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, funds: typing.TokenAmount, initial_channel_target: int = None, joinable_funds_target: float = None, ): log.debug( "Connecting to token network", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), funds=funds, initial_channel_target=initial_channel_target, joinable_funds_target=joinable_funds_target, ) try: self.raiden_api.token_network_connect( registry_address, token_address, funds, initial_channel_target, joinable_funds_target, ) except (InsufficientFunds, InsufficientGasReserve) as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) except (InvalidAmount, InvalidAddress) as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) return api_response(result=dict(), status_code=HTTPStatus.NO_CONTENT) def leave(self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress): log.debug( "Leaving token network", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), ) closed_channels = self.raiden_api.token_network_leave( registry_address, token_address) closed_channels = [ self.channel_schema.dump(channel_state).data for channel_state in closed_channels ] return api_response(result=closed_channels) def get_connection_managers_info( self, registry_address: typing.PaymentNetworkID): """Get a dict whose keys are token addresses and whose values are open channels, funds of last request, sum of deposits and number of channels""" log.debug( "Getting connection managers info", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), ) connection_managers = dict() for token in self.raiden_api.get_tokens_list(registry_address): token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_raiden(self.raiden_api.raiden), payment_network_id=registry_address, token_address=token, ) try: connection_manager = self.raiden_api.raiden.connection_manager_for_token_network( token_network_identifier) except InvalidAddress: connection_manager = None open_channels = views.get_channelstate_open( chain_state=views.state_from_raiden(self.raiden_api.raiden), payment_network_id=registry_address, token_address=token, ) if connection_manager is not None and open_channels: connection_managers[to_checksum_address( connection_manager.token_address)] = { "funds": connection_manager.funds, "sum_deposits": views.get_our_capacity_for_token_network( views.state_from_raiden(self.raiden_api.raiden), registry_address, token), "channels": len(open_channels), } return connection_managers def get_channel_list( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress = None, partner_address: typing.Address = None, ): log.debug( "Getting channel list", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=optional_address_to_string(token_address), partner_address=optional_address_to_string(partner_address), ) raiden_service_result = self.raiden_api.get_channel_list( registry_address, token_address, partner_address) assert isinstance(raiden_service_result, list) result = [ self.channel_schema.dump(channel_schema).data for channel_schema in raiden_service_result ] return api_response(result=result) def get_tokens_list(self, registry_address: typing.PaymentNetworkID): log.debug( "Getting token list", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), ) raiden_service_result = self.raiden_api.get_tokens_list( registry_address) assert isinstance(raiden_service_result, list) tokens_list = AddressList(raiden_service_result) result = self.address_list_schema.dump(tokens_list) return api_response(result=result.data) def get_token_network_for_token(self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress): log.debug( "Getting token network for token", node=pex(self.raiden_api.address), token_address=to_checksum_address(token_address), ) token_network_address = self.raiden_api.get_token_network_address_for_token_address( registry_address=registry_address, token_address=token_address) if token_network_address is not None: return api_response( result=to_checksum_address(token_network_address)) else: pretty_address = to_checksum_address(token_address) message = f'No token network registered for token "{pretty_address}"' return api_error(message, status_code=HTTPStatus.NOT_FOUND) def get_blockchain_events_network( self, registry_address: typing.PaymentNetworkID, from_block: typing.BlockSpecification = GENESIS_BLOCK_NUMBER, to_block: typing.BlockSpecification = "latest", ): log.debug( "Getting network events", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), from_block=from_block, to_block=to_block, ) try: raiden_service_result = self.raiden_api.get_blockchain_events_network( registry_address=registry_address, from_block=from_block, to_block=to_block) except InvalidBlockNumberInput as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) return api_response( result=normalize_events_list(raiden_service_result)) def get_blockchain_events_token_network( self, token_address: typing.TokenAddress, from_block: typing.BlockSpecification = GENESIS_BLOCK_NUMBER, to_block: typing.BlockSpecification = "latest", ): log.debug( "Getting token network blockchain events", node=pex(self.raiden_api.address), token_address=to_checksum_address(token_address), from_block=from_block, to_block=to_block, ) try: raiden_service_result = self.raiden_api.get_blockchain_events_token_network( token_address=token_address, from_block=from_block, to_block=to_block) return api_response( result=normalize_events_list(raiden_service_result)) except UnknownTokenAddress as e: return api_error(str(e), status_code=HTTPStatus.NOT_FOUND) except (InvalidBlockNumberInput, InvalidAddress) as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) def get_raiden_events_payment_history_with_timestamps( self, token_address: typing.TokenAddress = None, target_address: typing.Address = None, limit: int = None, offset: int = None, ): log.debug( "Getting payment history", node=pex(self.raiden_api.address), token_address=optional_address_to_string(token_address), target_address=optional_address_to_string(target_address), limit=limit, offset=offset, ) try: service_result = self.raiden_api.get_raiden_events_payment_history_with_timestamps( token_address=token_address, target_address=target_address, limit=limit, offset=offset, ) except (InvalidNumberInput, InvalidAddress) as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) result = [] for event in service_result: if isinstance(event.wrapped_event, EventPaymentSentSuccess): serialized_event = self.sent_success_payment_schema.dump(event) elif isinstance(event.wrapped_event, EventPaymentSentFailed): serialized_event = self.failed_payment_schema.dump(event) elif isinstance(event.wrapped_event, EventPaymentReceivedSuccess): serialized_event = self.received_success_payment_schema.dump( event) else: log.warning( "Unexpected event", node=pex(self.raiden_api.address), unexpected_event=event.wrapped_event, ) result.append(serialized_event.data) return api_response(result=result) def get_raiden_internal_events_with_timestamps(self, limit, offset): return [ str(e) for e in self.raiden_api.raiden.wal.storage. get_events_with_timestamps(limit=limit, offset=offset) ] def get_blockchain_events_channel( self, token_address: typing.TokenAddress, partner_address: typing.Address = None, from_block: typing.BlockSpecification = GENESIS_BLOCK_NUMBER, to_block: typing.BlockSpecification = "latest", ): log.debug( "Getting channel blockchain events", node=pex(self.raiden_api.address), token_address=to_checksum_address(token_address), partner_address=optional_address_to_string(partner_address), from_block=from_block, to_block=to_block, ) try: raiden_service_result = self.raiden_api.get_blockchain_events_channel( token_address=token_address, partner_address=partner_address, from_block=from_block, to_block=to_block, ) return api_response( result=normalize_events_list(raiden_service_result)) except (InvalidBlockNumberInput, InvalidAddress) as e: return api_error(str(e), status_code=HTTPStatus.CONFLICT) except UnknownTokenAddress as e: return api_error(str(e), status_code=HTTPStatus.NOT_FOUND) def get_channel( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, partner_address: typing.Address, ): log.debug( "Getting channel", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), partner_address=to_checksum_address(partner_address), ) try: channel_state = self.raiden_api.get_channel( registry_address=registry_address, token_address=token_address, partner_address=partner_address, ) result = self.channel_schema.dump(channel_state) return api_response(result=result.data) except ChannelNotFound as e: return api_error(errors=str(e), status_code=HTTPStatus.NOT_FOUND) def get_partners_by_token(self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress): log.debug( "Getting partners by token", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), ) return_list = [] try: raiden_service_result = self.raiden_api.get_channel_list( registry_address, token_address) except InvalidAddress as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) for result in raiden_service_result: return_list.append({ "partner_address": result.partner_state.address, "channel": url_for( # TODO: Somehow nicely parameterize this for future versions "v1_resources.channelsresourcebytokenandpartneraddress", token_address=token_address, partner_address=result.partner_state.address, ), }) schema_list = PartnersPerTokenList(return_list) result = self.partner_per_token_list_schema.dump(schema_list) return api_response(result=result.data) def initiate_payment( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, target_address: typing.Address, amount: typing.TokenAmount, identifier: typing.PaymentID, secret: typing.Secret, secret_hash: typing.SecretHash, ): log.debug( "Initiating payment", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), target_address=to_checksum_address(target_address), amount=amount, payment_identifier=identifier, secret=secret, secret_hash=secret_hash, ) if identifier is None: identifier = create_default_identifier() try: payment_status = self.raiden_api.transfer( registry_address=registry_address, token_address=token_address, target=target_address, amount=amount, identifier=identifier, secret=secret, secrethash=secret_hash, ) except ( InvalidAmount, InvalidAddress, InvalidSecret, InvalidSecretHash, PaymentConflict, UnknownTokenAddress, ) as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) except InsufficientFunds as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) if payment_status.payment_done.get() is False: return api_error( errors="Payment couldn't be completed " "(insufficient funds, no route to target or target offline).", status_code=HTTPStatus.CONFLICT, ) secret = payment_status.payment_done.get() payment = { "initiator_address": self.raiden_api.address, "registry_address": registry_address, "token_address": token_address, "target_address": target_address, "amount": amount, "identifier": identifier, "secret": secret, "secret_hash": sha3(secret), } result = self.payment_schema.dump(payment) return api_response(result=result.data) def _deposit( self, registry_address: typing.PaymentNetworkID, channel_state: NettingChannelState, total_deposit: typing.TokenAmount, ): log.debug( "Depositing to channel", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), channel_identifier=channel_state.identifier, total_deposit=total_deposit, ) if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: return api_error( errors="Can't set total deposit on a closed channel", status_code=HTTPStatus.CONFLICT, ) try: self.raiden_api.set_total_channel_deposit( registry_address, channel_state.token_address, channel_state.partner_state.address, total_deposit, ) except InsufficientFunds as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) except DepositOverLimit as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) except DepositMismatch as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) updated_channel_state = self.raiden_api.get_channel( registry_address, channel_state.token_address, channel_state.partner_state.address) result = self.channel_schema.dump(updated_channel_state) return api_response(result=result.data) def _close(self, registry_address: typing.PaymentNetworkID, channel_state: NettingChannelState): log.debug( "Closing channel", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), channel_identifier=channel_state.identifier, ) if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: return api_error( errors="Attempted to close an already closed channel", status_code=HTTPStatus.CONFLICT, ) try: self.raiden_api.channel_close(registry_address, channel_state.token_address, channel_state.partner_state.address) except InsufficientFunds as e: return api_error(errors=str(e), status_code=HTTPStatus.PAYMENT_REQUIRED) updated_channel_state = self.raiden_api.get_channel( registry_address, channel_state.token_address, channel_state.partner_state.address) result = self.channel_schema.dump(updated_channel_state) return api_response(result=result.data) def patch_channel( self, registry_address: typing.PaymentNetworkID, token_address: typing.TokenAddress, partner_address: typing.Address, total_deposit: typing.TokenAmount = None, state: str = None, ): log.debug( "Patching channel", node=pex(self.raiden_api.address), registry_address=to_checksum_address(registry_address), token_address=to_checksum_address(token_address), partner_address=to_checksum_address(partner_address), total_deposit=total_deposit, state=state, ) if total_deposit is not None and state is not None: return api_error( errors= "Can not update a channel's total deposit and state at the same time", status_code=HTTPStatus.CONFLICT, ) if total_deposit is None and state is None: return api_error( errors= "Nothing to do. Should either provide 'total_deposit' or 'state' argument", status_code=HTTPStatus.BAD_REQUEST, ) if total_deposit and total_deposit < 0: return api_error(errors="Amount to deposit must not be negative.", status_code=HTTPStatus.CONFLICT) try: channel_state = self.raiden_api.get_channel( registry_address=registry_address, token_address=token_address, partner_address=partner_address, ) except ChannelNotFound: return api_error( errors="Requested channel for token {} and partner {} not found" .format(to_checksum_address(token_address), to_checksum_address(partner_address)), status_code=HTTPStatus.CONFLICT, ) except InvalidAddress as e: return api_error(errors=str(e), status_code=HTTPStatus.CONFLICT) if total_deposit is not None: result = self._deposit(registry_address, channel_state, total_deposit) elif state == CHANNEL_STATE_CLOSED: result = self._close(registry_address, channel_state) else: # should never happen, channel_state is validated in the schema result = api_error( errors="Provided invalid channel state {}".format(state), status_code=HTTPStatus.BAD_REQUEST, ) return result def get_pending_transfers(self, token_address=None, partner_address=None): try: return api_response( self.raiden_api.get_pending_transfers( token_address=token_address, partner_address=partner_address)) except (ChannelNotFound, UnknownTokenAddress) as e: return api_error(errors=str(e), status_code=HTTPStatus.NOT_FOUND)