예제 #1
0
def test_state_operators():
    a_route = RouteState('opened', ADDRESS, ADDRESS2, 5, 5, 5, 5)
    b_route = RouteState('opened', ADDRESS, ADDRESS2, 5, 5, 5, 5)
    c_route = RouteState('closed', ADDRESS3, ADDRESS2, 1, 2, 3, 4)

    assert a_route == b_route
    assert not a_route != b_route
    assert a_route != c_route
    assert not a_route == c_route

    d_route = RouteState('opened', ADDRESS4, ADDRESS, 1, 2, 3, 4)
    a = RoutesState([a_route, d_route])
    b = RoutesState([a_route, d_route])
    c = RoutesState([a_route, c_route])

    assert a == b
    assert not a != b
    assert a != c
    assert not a == c

    a = LockedTransferState(1, 2, ADDRESS, ADDRESS2, ADDRESS3, 4, HASH, 'secret')
    b = LockedTransferState(1, 2, ADDRESS, ADDRESS2, ADDRESS3, 4, HASH, 'secret')
    c = LockedTransferState(2, 4, ADDRESS3, ADDRESS4, ADDRESS, 4, HASH, 'secret')

    assert a == b
    assert not a != b
    assert a != c
    assert not a == c
예제 #2
0
def next_transfer_pair(payer_route, payer_transfer, routes_state,
                       timeout_blocks, block_number):
    """ Given a payer transfer tries a new route to proceed with the mediation.

    Args:
        payer_route (RouteState): The previous route in the path that provides
            the token for the mediation.
        payer_transfer (LockedTransferState): The transfer received from the
            payer_route.
        routes_state (RoutesState): Current available routes that may be used,
            it's assumed that the available_routes list is ordered from best to
            worst.
        timeout_blocks (int): Base number of available blocks used to compute
            the lock timeout.
        block_number (int): The current block number.
    """
    assert timeout_blocks > 0
    assert timeout_blocks <= payer_transfer.expiration - block_number

    transfer_pair = None
    mediated_events = list()

    payee_route = next_route(
        routes_state,
        timeout_blocks,
        payer_transfer.amount,
    )

    if payee_route:
        assert payee_route.reveal_timeout < timeout_blocks

        lock_timeout = timeout_blocks - payee_route.reveal_timeout
        lock_expiration = lock_timeout + block_number

        payee_transfer = LockedTransferState(
            payer_transfer.identifier,
            payer_transfer.amount,
            payer_transfer.token,
            payer_transfer.initiator,
            payer_transfer.target,
            lock_expiration,
            payer_transfer.hashlock,
            payer_transfer.secret,
        )

        transfer_pair = MediationPairState(
            payer_route,
            payer_transfer,
            payee_route,
            payee_transfer,
        )

        mediated_events = [
            mediatedtransfer(payee_transfer, payee_route.node_address),
        ]

    return (
        transfer_pair,
        mediated_events,
    )
예제 #3
0
def make_hashlock_transfer(amount,
                           target,
                           identifier=0,
                           token=factories.UNIT_TOKEN_ADDRESS):
    """ Helper for creating a hashlocked transfer.

    Args:
        amount (int): Amount of token being transferred.
        target (address): Transfer target.
        expiration (int): Block number
    """
    # the initiator machine populates these values
    secret = None
    hashlock = None
    expiration = None

    transfer = LockedTransferState(
        identifier,
        amount,
        token,
        target,
        expiration,
        hashlock,
        secret,
    )
    return transfer
예제 #4
0
    def message_refundtransfer(self, message):
        self.balance_proof(message)

        self.raiden.greenlet_task_dispatcher.dispatch_message(
            message,
            message.lock.hashlock,
        )

        transfer_state = LockedTransferState(
            identifier=message.identifier,
            amount=message.lock.amount,
            token=message.token,
            initiator=message.initiator,
            target=message.target,
            expiration=message.lock.expiration,
            hashlock=message.lock.hashlock,
            secret=None,
        )
        state_change = ReceiveTransferRefund(
            message.sender,
            transfer_state,
        )
        self.raiden.state_machine_event_handler.log_and_dispatch_by_identifier(
            message.identifier,
            state_change,
        )
예제 #5
0
    def message_refundtransfer(self, message):
        self.balance_proof(message)

        graph = self.raiden.token_to_channelgraph[message.token]

        if not graph.has_channel(self.raiden.address, message.sender):
            raise UnknownAddress(
                'Direct transfer from node without an existing channel: {}'.
                format(pex(message.sender), ))

        channel = graph.partneraddress_to_channel[message.sender]
        channel.register_transfer(
            self.raiden.get_block_number(),
            message,
        )

        transfer_state = LockedTransferState(
            identifier=message.identifier,
            amount=message.lock.amount,
            token=message.token,
            initiator=message.initiator,
            target=message.target,
            expiration=message.lock.expiration,
            hashlock=message.lock.hashlock,
            secret=None,
        )
        state_change = ReceiveTransferRefund(
            message.sender,
            transfer_state,
        )
        self.raiden.state_machine_event_handler.log_and_dispatch_by_identifier(
            message.identifier,
            state_change,
        )
예제 #6
0
def make_transfer(
        amount,
        initiator,
        target,
        expiration,
        secret=None,
        hashlock=UNIT_HASHLOCK,
        identifier=1,
        token=UNIT_TOKEN_ADDRESS
):

    if secret is not None:
        assert sha3(secret) == hashlock

    transfer = LockedTransferState(
        identifier,
        amount,
        token,
        initiator,
        target,
        expiration,
        hashlock=hashlock,
        secret=secret,
    )
    return transfer
예제 #7
0
def test_is_valid_refund():
    initiator = factories.ADDR
    target = factories.HOP1
    valid_sender = factories.HOP2

    transfer = LockedTransferState(
        identifier=20,
        amount=30,
        token=factories.UNIT_TOKEN_ADDRESS,
        initiator=initiator,
        target=target,
        expiration=50,
        hashlock=factories.UNIT_HASHLOCK,
        secret=None,
    )

    refund_lower_expiration = LockedTransferState(
        identifier=20,
        amount=30,
        token=factories.UNIT_TOKEN_ADDRESS,
        initiator=initiator,
        target=target,
        expiration=35,
        hashlock=factories.UNIT_HASHLOCK,
        secret=None,
    )

    assert mediator.is_valid_refund(transfer, valid_sender,
                                    refund_lower_expiration) is True

    # target cannot refund
    assert mediator.is_valid_refund(transfer, target,
                                    refund_lower_expiration) is False

    refund_same_expiration = LockedTransferState(
        identifier=20,
        amount=30,
        token=factories.UNIT_TOKEN_ADDRESS,
        initiator=initiator,
        target=factories.HOP1,
        expiration=50,
        hashlock=factories.UNIT_HASHLOCK,
        secret=None,
    )
    assert mediator.is_valid_refund(transfer, valid_sender,
                                    refund_same_expiration) is False
예제 #8
0
    def message_refundtransfer(self, message):
        self.raiden.greenlet_task_dispatcher.dispatch_message(
            message,
            message.lock.hashlock,
        )

        if message.identifier in self.raiden.identifier_statemanager:
            identifier = message.identifier
            token_address = message.token
            target = message.target
            amount = message.lock.amount
            expiration = message.lock.expiration
            hashlock = message.lock.hashlock

            manager = self.raiden.identifier_statemanager[identifier]

            if isinstance(manager.current_state, InitiatorState):
                initiator_address = self.raiden.address

            elif isinstance(manager.current_state, MediatorState):
                last_pair = manager.current_state.transfers_pair[-1]
                initiator_address = last_pair.payee_transfer.initiator

            else:
                # TODO: emit a proper event for the reject message
                return

            transfer_state = LockedTransferState(
                identifier=identifier,
                amount=amount,
                token=token_address,
                initiator=initiator_address,
                target=target,
                expiration=expiration,
                hashlock=hashlock,
                secret=None,
            )
            state_change = ReceiveTransferRefund(
                message.sender,
                transfer_state,
            )
            self.raiden.state_machine_event_handler.dispatch_by_identifier(
                message.identifier,
                state_change,
            )
예제 #9
0
    def start_mediated_transfer(self, token_address, amount, identifier,
                                target):
        # pylint: disable=too-many-locals

        async_result = AsyncResult()
        graph = self.token_to_channelgraph[token_address]

        available_routes = get_best_routes(
            graph,
            self.protocol.nodeaddresses_networkstatuses,
            self.address,
            target,
            amount,
            None,
        )

        if not available_routes:
            async_result.set(False)
            return async_result

        self.protocol.start_health_check(target)

        if identifier is None:
            identifier = create_default_identifier()

        route_state = RoutesState(available_routes)
        our_address = self.address
        block_number = self.get_block_number()

        transfer_state = LockedTransferState(
            identifier=identifier,
            amount=amount,
            token=token_address,
            initiator=self.address,
            target=target,
            expiration=None,
            hashlock=None,
            secret=None,
        )

        # Issue #489
        #
        # Raiden may fail after a state change using the random generator is
        # handled but right before the snapshot is taken. If that happens on
        # the next initialization when raiden is recovering and applying the
        # pending state changes a new secret will be generated and the
        # resulting events won't match, this breaks the architecture model,
        # since it's assumed the re-execution of a state change will always
        # produce the same events.
        #
        # TODO: Removed the secret generator from the InitiatorState and add
        # the secret into all state changes that require one, this way the
        # secret will be serialized with the state change and the recovery will
        # use the same /random/ secret.
        random_generator = RandomSecretGenerator()

        init_initiator = ActionInitInitiator(
            our_address=our_address,
            transfer=transfer_state,
            routes=route_state,
            random_generator=random_generator,
            block_number=block_number,
        )

        state_manager = StateManager(initiator.state_transition, None)
        self.state_machine_event_handler.log_and_dispatch(
            state_manager, init_initiator)

        # TODO: implement the network timeout raiden.config['msg_timeout'] and
        # cancel the current transfer if it hapens (issue #374)
        self.identifier_to_statemanagers[identifier].append(state_manager)
        self.identifier_to_results[identifier].append(async_result)

        return async_result
예제 #10
0
def try_new_route(state):
    assert state.route is None, 'cannot try a new route while one is being used'

    # TODO:
    # - Route ranking. An upper layer should rate each route to optimize
    #   the fee price/quality of each route and add a rate from in the range
    #   [0.0,1.0].
    # - Add in a policy per route:
    #   - filtering, e.g. so the user may have a per route maximum transfer
    #     value based on fixed value or reputation.
    #   - reveal time computation
    #   - These policy details are better hidden from this implementation and
    #     changes should be applied through the use of Route state changes.

    # Find a single route that may fulfill the request, this uses a single
    # route intentionally
    try_route = None
    while state.routes.available_routes:
        route = state.routes.available_routes.pop(0)

        if route.available_balance < state.transfer.amount:
            state.routes.ignored_routes.append(route)
        else:
            try_route = route
            break

    if try_route is None:
        # No available route has sufficient balance for the current transfer,
        # cancel it.
        #
        # At this point we can just discard all the state data, this is only
        # valid because we are the initiator and we know that the secret was
        # not released.
        cancel = EventTransferFailed(
            identifier=state.transfer.identifier,
            reason='no route available',
        )
        iteration = TransitionResult(None, [cancel])

    else:
        state.route = try_route

        secret = state.random_generator.next()
        hashlock = sha3(secret)

        # The initiator doesn't need to learn the secret, so there is no need
        # to decrement reveal_timeout from the lock timeout.
        #
        # A value larger than settle_timeout could be used but wouldn't
        # improve, since the next hop will take settle_timeout as an upper
        # limit for expiration.
        lock_expiration = state.block_number + try_route.settle_timeout
        identifier = state.transfer.identifier

        transfer = LockedTransferState(
            identifier,
            state.transfer.amount,
            state.transfer.token,
            state.transfer.target,
            lock_expiration,
            hashlock,
            secret,
        )

        message = SendMediatedTransfer(
            transfer.identifier,
            transfer.token,
            transfer.amount,
            transfer.hashlock,
            transfer.target,
            lock_expiration,
            try_route.node_address,
        )

        state.transfer = transfer
        state.message = message

        iteration = TransitionResult(state, [message])

    return iteration
예제 #11
0
    def start_mediated_transfer(self, token_address, amount, identifier,
                                target):
        # pylint: disable=too-many-locals
        graph = self.channelgraphs[token_address]
        routes = graph.get_best_routes(
            self.address,
            target,
            amount,
            lock_timeout=None,
        )

        available_routes = [
            route for route in map(route_to_routestate, routes)
            if route.state == CHANNEL_STATE_OPENED
        ]

        # send ping to target to make sure we can receive something back from target
        async_result = self.protocol.send_ping(target)
        async_result.wait(timeout=0.5)  # allow the ping to succeed
        if async_result.ready():
            log.debug("transfer target received invitation ping")
        else:
            log.debug(
                "transfer target did not receive invitation ping, probably behing NAT"
            )

        identifier = create_default_identifier(self.address, token_address,
                                               target)
        route_state = RoutesState(available_routes)
        our_address = self.address
        block_number = self.get_block_number()

        transfer_state = LockedTransferState(
            identifier=identifier,
            amount=amount,
            token=token_address,
            initiator=self.address,
            target=target,
            expiration=None,
            hashlock=None,
            secret=None,
        )

        # Issue #489
        #
        # Raiden may fail after a state change using the random generator is
        # handled but right before the snapshot is taken. If that happens on
        # the next initialization when raiden is recovering and applying the
        # pending state changes a new secret will be generated and the
        # resulting events won't match, this breaks the architecture model,
        # since it's assumed the re-execution of a state change will always
        # produce the same events.
        #
        # TODO: Removed the secret generator from the InitiatorState and add
        # the secret into all state changes that require one, this way the
        # secret will be serialized with the state change and the recovery will
        # use the same /random/ secret.
        random_generator = RandomSecretGenerator()

        init_initiator = ActionInitInitiator(
            our_address=our_address,
            transfer=transfer_state,
            routes=route_state,
            random_generator=random_generator,
            block_number=block_number,
        )

        state_manager = StateManager(initiator.state_transition, None)
        self.state_machine_event_handler.log_and_dispatch(
            state_manager, init_initiator)
        async_result = AsyncResult()

        # TODO: implement the network timeout raiden.config['msg_timeout'] and
        # cancel the current transfer if it hapens (issue #374)
        self.identifier_to_statemanagers[identifier].append(state_manager)
        self.identifier_to_results[identifier].append(async_result)

        return async_result