示例#1
0
 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()
示例#2
0
class PaymentResource(BaseResource):
    post_schema = PaymentSchema(
        only=("amount", "identifier", "secret", "secret_hash", "lock_timeout")
    )
    get_schema = RaidenEventsRequestSchema()

    @if_api_available
    def get(self, token_address: TokenAddress, target_address: Address) -> Response:
        kwargs = validate_query_params(self.get_schema)
        return self.rest_api.get_raiden_events_payment_history_with_timestamps(
            registry_address=self.rest_api.raiden_api.raiden.default_registry.address,
            token_address=token_address,
            target_address=target_address,
            **kwargs,
        )

    @if_api_available
    def post(self, token_address: TokenAddress, target_address: TargetAddress) -> Response:
        kwargs = validate_json(self.post_schema)

        return self.rest_api.initiate_payment(
            registry_address=self.rest_api.raiden_api.raiden.default_registry.address,
            token_address=token_address,
            target_address=target_address,
            **kwargs,
        )
示例#3
0
class PaymentResource(BaseResource):

    post_schema = PaymentSchema(only=('amount', 'identifier'), )

    def get(
        self,
        token_address: typing.TokenAddress = None,
        target_address: typing.Address = None,
    ):
        return self.rest_api.get_payment_history(
            token_address=token_address,
            target_address=target_address,
        )

    @use_kwargs(post_schema, locations=('json', ))
    def post(
        self,
        token_address: typing.TokenAddress,
        target_address: typing.TargetAddress,
        amount: typing.PaymentAmount,
        identifier: typing.PaymentID,
    ):
        return self.rest_api.initiate_payment(
            registry_address=self.rest_api.raiden_api.raiden.default_registry.
            address,
            token_address=token_address,
            target_address=target_address,
            amount=amount,
            identifier=identifier,
        )
示例#4
0
class PaymentResource(BaseResource):

    post_schema = PaymentSchema(only=("amount", "identifier", "secret",
                                      "secret_hash", "lock_timeout"))
    get_schema = RaidenEventsRequestSchema()

    @use_kwargs(get_schema, locations=("query", ))
    @if_api_available
    def get(
        self,
        token_address: TokenAddress = None,
        target_address: Address = None,
        limit: int = None,
        offset: int = None,
    ) -> Response:
        return self.rest_api.get_raiden_events_payment_history_with_timestamps(
            token_address=token_address,
            target_address=target_address,
            limit=limit,
            offset=offset)

    @use_kwargs(post_schema, locations=("json", ))
    @if_api_available
    def post(
        self,
        token_address: TokenAddress,
        target_address: TargetAddress,
        amount: PaymentAmount,
        identifier: PaymentID,
        secret: Secret,
        secret_hash: SecretHash,
        lock_timeout: BlockTimeout,
    ) -> Response:
        return self.rest_api.initiate_payment(
            registry_address=self.rest_api.raiden_api.raiden.default_registry.
            address,
            token_address=token_address,
            target_address=target_address,
            amount=amount,
            identifier=identifier,
            secret=secret,
            secret_hash=secret_hash,
            lock_timeout=lock_timeout,
        )
示例#5
0
class PaymentResource(BaseResource):

    post_schema = PaymentSchema(
        only=('amount', 'identifier', 'secret', 'secret_hash'),
    )
    get_schema = RaidenEventsRequestSchema()

    @use_kwargs(get_schema, locations=('query',))
    def get(
            self,
            token_address: typing.TokenAddress = None,
            target_address: typing.Address = None,
            limit: int = None,
            offset: int = None,
    ):
        return self.rest_api.get_raiden_events_payment_history_with_timestamps(
            token_address=token_address,
            target_address=target_address,
            limit=limit,
            offset=offset,
        )

    @use_kwargs(post_schema, locations=('json',))
    def post(
            self,
            token_address: typing.TokenAddress,
            target_address: typing.TargetAddress,
            amount: typing.PaymentAmount,
            identifier: typing.PaymentID,
            secret: typing.Secret,
            secret_hash: typing.SecretHash,
    ):
        return self.rest_api.initiate_payment(
            registry_address=self.rest_api.raiden_api.raiden.default_registry.address,
            token_address=token_address,
            target_address=target_address,
            amount=amount,
            identifier=identifier,
            secret=secret,
            secret_hash=secret_hash,
        )
示例#6
0
class PaymentResource(BaseResource):
    post_schema = PaymentSchema(only=("amount", "identifier", "secret",
                                      "secret_hash"))
    get_schema = RaidenEventsRequestSchema()

    @use_kwargs(get_schema, locations=("query", ))
    def get(
        self,
        token_address: typing.TokenAddress = None,
        target_address: typing.Address = None,
        limit: int = None,
        offset: int = None,
    ):
        return self.rest_api.get_raiden_events_payment_history_with_timestamps(
            token_address=token_address,
            target_address=target_address,
            limit=limit,
            offset=offset)

    @use_kwargs(post_schema, locations=("json", ))
    def post(
        self,
        token_address: typing.TokenAddress,
        target_address: typing.TargetAddress,
        amount: typing.PaymentAmount,
        identifier: typing.PaymentID,
        secret: typing.Secret,
        secret_hash: typing.SecretHash,
    ):
        return self.rest_api.initiate_payment(
            registry_address=self.rest_api.raiden_api.raiden.default_registry.
            address,
            token_address=token_address,
            target_address=target_address,
            amount=amount,
            identifier=identifier,
            secret=secret,
            secret_hash=secret_hash,
            payment_hash_invoice=EMPTY_PAYMENT_HASH_INVOICE)
示例#7
0
文件: rest.py 项目: zhengyunly/raiden
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
示例#8
0
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)