Beispiel #1
0
def test_matrix_invitee_receives_invite_on_restart(
        matrix_transports: List[MatrixTransport]) -> None:
    """The invitee should receive the invite, even if the inviter is offline."""
    raiden_service0: RaidenService = cast(RaidenService,
                                          MockRaidenService(None))
    raiden_service1: RaidenService = cast(RaidenService,
                                          MockRaidenService(None))

    transport0, transport1 = matrix_transports

    room_creator_address = my_place_or_yours(raiden_service0.address,
                                             raiden_service1.address)
    if room_creator_address == raiden_service0.address:
        inviter_service = raiden_service0
        invitee_service = raiden_service1

        inviter_transport = transport0
        invitee_transport = transport1
    else:
        inviter_service = raiden_service1
        invitee_service = raiden_service0

        inviter_transport = transport1
        invitee_transport = transport0

    # Initialize the invitee and stop it before the invite happens
    invitee_transport.start(invitee_service, [], None)
    invitee_transport.stop()

    inviter_transport.start(inviter_service, [], None)
    inviter_transport.immediate_health_check_for(invitee_service.address)

    room = inviter_transport._get_room_for_address(invitee_service.address)
    assert room, "The inviter should have created the room, even if the invitee is offline."

    # Now stop the inviter and check the invitee received the invite
    inviter_transport.stop()

    invitee_transport.start(invitee_service, [], None)
    invitee_transport.immediate_health_check_for(inviter_service.address)

    with Timeout(TIMEOUT_MESSAGE_RECEIVE):
        while True:
            try:
                room_state1 = invitee_transport._client.api.get_room_state(
                    room.room_id)
                break
            except MatrixRequestError:
                gevent.sleep(0.1)

    assert room_state1 is not None
Beispiel #2
0
def send_lockedtransfer(
    transfer_description: TransferDescriptionWithSecretState,
    channel_state: NettingChannelState,
    message_identifier: MessageID,
    block_number: BlockNumber,
) -> SendLockedTransfer:
    """ Create a mediated transfer using channel. """
    assert channel_state.token_network_identifier == transfer_description.token_network_identifier

    lock_expiration = get_initial_lock_expiration(
        block_number,
        channel_state.reveal_timeout,
    )

    lockedtransfer_event = channel.send_lockedtransfer(
        channel_state,
        transfer_description.initiator,
        transfer_description.target,
        cast(
            PaymentAmount,
            transfer_description.amount,
        ),
        message_identifier,
        transfer_description.payment_identifier,
        lock_expiration,
        transfer_description.secrethash,
    )
    return lockedtransfer_event
Beispiel #3
0
    def new(self) -> ID:
        timestamp: int

        with self._lock:
            # Using RAW to circumvent a bug in Pine64/ARM64 and the 3.x family
            # of Linux Kernels which allowed `CLOCK_MONOTONIC` to go backwards
            # (PR: #4156).
            #
            # A monotonic clock with microsecond (us), or better, precision must
            # not return the same value twice, looking up the time itself should
            # take more then 1us:
            # https://www.python.org/dev/peps/pep-0564/#annex-clocks-resolution-in-python
            new_monotonic = clock_gettime_ns(CLOCK_MONOTONIC_RAW)

            assert (new_monotonic > self._previous_monotonic
                    ), "The monotonic clock must not go backwards"

            delta = new_monotonic - self._previous_monotonic
            timestamp = self._previous_timestamp + delta

            self._previous_monotonic = new_monotonic
            self._previous_timestamp = timestamp

        rnd = random.getrandbits(64)
        identifier = ULID(
            timestamp.to_bytes(8, "big") + rnd.to_bytes(8, "big"))

        return cast(ID, identifier)
Beispiel #4
0
def send_lockedtransfer(
    transfer_description: TransferDescriptionWithSecretState,
    channel_state: NettingChannelState,
    message_identifier: typing.MessageID,
    block_number: typing.BlockNumber,
) -> SendLockedTransfer:
    """ Create a mediated transfer using channel.

    Raises:
        AssertionError: If the channel does not have enough capacity.
    """
    assert channel_state.token_network_identifier == transfer_description.token_network_identifier

    lock_expiration = get_initial_lock_expiration(
        block_number,
        channel_state.reveal_timeout,
    )

    lockedtransfer_event = channel.send_lockedtransfer(
        channel_state,
        transfer_description.initiator,
        transfer_description.target,
        typing.cast(
            typing.PaymentAmount,
            transfer_description.amount,
        ),
        message_identifier,
        transfer_description.payment_identifier,
        lock_expiration,
        transfer_description.secrethash,
    )
    return lockedtransfer_event
Beispiel #5
0
def clear_if_finalized(iteration: TransitionResult, ) -> TransitionResult[InitiatorPaymentState]:
    """ Clear the initiator payment task if all transfers have been finalized
    or expired. """
    state = cast(InitiatorPaymentState, iteration.new_state)

    if state is None:
        return iteration

    if len(state.initiator_transfers) == 0:
        return TransitionResult(None, iteration.events)

    return iteration
Beispiel #6
0
def handle_block(
    initiator_state: InitiatorTransferState,
    state_change: Block,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
) -> TransitionResult:
    secrethash = initiator_state.transfer.lock.secrethash
    locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get(
        secrethash)

    if not locked_lock:
        return TransitionResult(initiator_state, list())

    lock_expiration_threshold = typing.BlockNumber(
        locked_lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2, )
    lock_has_expired, _ = channel.is_lock_expired(
        end_state=channel_state.our_state,
        lock=locked_lock,
        block_number=state_change.block_number,
        lock_expiration_threshold=lock_expiration_threshold,
    )

    if lock_has_expired:
        expired_lock_events = channel.events_for_expired_lock(
            channel_state=channel_state,
            locked_lock=locked_lock,
            pseudo_random_generator=pseudo_random_generator,
        )
        transfer_description = initiator_state.transfer_description
        # TODO: When we introduce multiple transfers per payment this needs to be
        #       reconsidered. As we would want to try other routes once a route
        #       has failed, and a transfer failing does not mean the entire payment
        #       would have to fail.
        #       Related issue: https://github.com/raiden-network/raiden/issues/2329
        transfer_failed = EventPaymentSentFailed(
            payment_network_identifier=transfer_description.
            payment_network_identifier,
            token_network_identifier=transfer_description.
            token_network_identifier,
            identifier=transfer_description.payment_identifier,
            target=transfer_description.target,
            reason="transfer's lock has expired",
        )
        expired_lock_events.append(transfer_failed)
        return TransitionResult(
            None,
            typing.cast(typing.List[Event], expired_lock_events),
        )
    else:
        return TransitionResult(initiator_state, list())
def test_matrix_invite_retry_with_offline_invitee(
    matrix_transports: List[MatrixTransport],
) -> None:
    """The inviter should create the room and send the invite even if the
    target node is offline.
    """
    raiden_service0: RaidenService = cast(RaidenService, MockRaidenService(None))
    raiden_service1: RaidenService = cast(RaidenService, MockRaidenService(None))

    transport0, transport1 = matrix_transports

    room_creator_address = my_place_or_yours(raiden_service0.address, raiden_service1.address)
    if room_creator_address == raiden_service0.address:
        inviter_service = raiden_service0
        invitee_service = raiden_service1

        inviter_transport = transport0
        invitee_transport = transport1
    else:
        inviter_service = raiden_service1
        invitee_service = raiden_service0

        inviter_transport = transport1
        invitee_transport = transport0

    # Initialize the invitee and stop it before the invite happens
    invitee_transport.start(invitee_service, [], None)
    invitee_transport.stop()

    inviter_transport.start(inviter_service, [], None)
    inviter_transport.immediate_health_check_for(invitee_service.address)

    wait_for_peer_unreachable(inviter_transport, invitee_service.address)
    assert not is_reachable(inviter_transport, invitee_service.address)

    room = inviter_transport._get_room_for_address(invitee_service.address)
    assert room, "The inviter should have created the room, even if the invitee is offline."

    invitee_transport.start(invitee_service, [], None)
    invitee_transport.immediate_health_check_for(inviter_service.address)

    with Timeout(TIMEOUT_MESSAGE_RECEIVE):
        while True:
            try:
                room_state0 = inviter_transport._client.api.get_room_state(room.room_id)
                break
            except MatrixRequestError:
                gevent.sleep(0.1)

    assert room_state0 is not None

    with Timeout(TIMEOUT_MESSAGE_RECEIVE):
        while True:
            try:
                room_state1 = invitee_transport._client.api.get_room_state(room.room_id)
                break
            except MatrixRequestError as ex:
                print(ex, transport0._client.user_id, transport1._client.user_id)
                gevent.sleep(0.5)

    assert room_state1 is not None

    assert is_reachable(inviter_transport, invitee_service.address)
    assert is_reachable(invitee_transport, inviter_service.address)
Beispiel #8
0
def services_bundle_from_contracts_deployment(
    config: RaidenConfig,
    proxy_manager: ProxyManager,
    routing_mode: RoutingMode,
    deployed_addresses: DeploymentAddresses,
    pathfinding_service_address: str,
    enable_monitoring: bool,
) -> ServicesBundle:
    """
    Initialize and setup the contract proxies.

    Depending on the provided contract addresses via the CLI, the routing mode,
    the environment type and the network id try to initialize the proxies.
    Returns the initialized proxies or exits the application with an error if
    there is a problem.

    Also depending on the given arguments populate config with PFS related settings
    """
    node_network_id = config.chain_id
    environment_type = config.environment_type

    user_deposit_address = deployed_addresses.user_deposit_address
    service_registry_address = deployed_addresses.service_registry_address
    token_network_registry_address = deployed_addresses.token_network_registry_address

    contractname_address: List[Tuple[str, Address, Callable]] = [
        ("user_deposit", Address(user_deposit_address), proxy_manager.user_deposit)
    ]
    if routing_mode == RoutingMode.PFS:
        contractname_address.append(
            ("service_registry", Address(service_registry_address), proxy_manager.service_registry)
        )
    if enable_monitoring or routing_mode == RoutingMode.PFS:
        contractname_address.append(
            (
                "monitoring_service",
                Address(deployed_addresses.monitoring_service_address),
                proxy_manager.monitoring_service,
            )
        )
        contractname_address.append(
            ("one_to_n", Address(deployed_addresses.one_to_n_address), proxy_manager.one_to_n)
        )

    proxies = dict()

    for contractname, address, constructor in contractname_address:
        try:
            proxy = constructor(address)
        except ContractCodeMismatch as e:
            handle_contract_code_mismatch(e)
        except AddressWithoutCode:
            handle_contract_no_code(contractname, address)
        except AddressWrongContract:
            handle_contract_wrong_address(contractname, address)

        proxies[contractname] = proxy

    if routing_mode == RoutingMode.PFS:
        check_pfs_configuration(pathfinding_service_address=pathfinding_service_address)

        pfs_info = configure_pfs_or_exit(
            pfs_url=pathfinding_service_address,
            routing_mode=routing_mode,
            service_registry=proxies["service_registry"],
            node_network_id=node_network_id,
            token_network_registry_address=TokenNetworkRegistryAddress(
                token_network_registry_address
            ),
            pathfinding_max_fee=config.services.pathfinding_max_fee,
        )
        msg = "Eth address of selected pathfinding service is unknown."
        assert pfs_info.payment_address is not None, msg

        # Only check that PFS is registered in production mode
        if environment_type == Environment.PRODUCTION:
            check_pfs_for_production(
                service_registry=proxies["service_registry"], pfs_info=pfs_info
            )

        config.pfs_config = PFSConfig(
            info=pfs_info,
            maximum_fee=config.services.pathfinding_max_fee,
            iou_timeout=config.services.pathfinding_iou_timeout,
            max_paths=config.services.pathfinding_max_paths,
        )
    else:
        config.pfs_config = None

    return ServicesBundle(
        user_deposit=cast(UserDeposit, proxies.get("user_deposit")),
        service_registry=cast(ServiceRegistry, proxies.get("service_registry")),
        monitoring_service=cast(MonitoringService, proxies.get("monitoring_service")),
        one_to_n=cast(OneToN, proxies.get("one_to_n")),
    )
Beispiel #9
0
def assert_balance_proof(
    token_network_address: TokenNetworkAddress,
    app0: App,
    app1: App,
    saved_state0: SavedState,
    saved_state1: SavedState,
) -> None:
    """Assert app0 and app1 agree on the latest balance proof from app0.

    Notes:
        - The other direction of the channel does not have to be synchronized,
          it can be checked with another call.
        - It is important to do the validation on a fixed  state, that is why
          saved_state0 is used.
    """
    assert app0.raiden.wal
    assert app1.raiden.wal

    assert app0.raiden.address == saved_state0.state.our_address
    assert app1.raiden.address == saved_state1.state.our_address

    channel0 = views.get_channelstate_by_token_network_and_partner(
        saved_state0.state, token_network_address, app1.raiden.address)
    channel1 = views.get_channelstate_by_token_network_and_partner(
        saved_state1.state, token_network_address, app0.raiden.address)

    assert channel0
    assert channel1

    balanceproof0 = cast(BalanceProofUnsignedState,
                         channel0.our_state.balance_proof)
    balanceproof1 = cast(BalanceProofSignedState,
                         channel1.partner_state.balance_proof)

    if balanceproof0 is None:
        msg = "Bug detected. The sender does not have a balance proof, but the recipient does."
        assert balanceproof1 is None, msg

        # nothing to compare
        return

    # Handle the case when the recipient didn't receive the message yet.
    if balanceproof1 is not None:
        nonce1 = balanceproof1.nonce
    else:
        nonce1 = 0

    if balanceproof0.nonce < nonce1:
        msg = (
            "This is a bug, it should never happen. The nonce updates **always**  "
            "start with the owner of the channel's end. This means for a channel "
            "A-B, only A can increase its nonce, same thing with B. At this "
            "point, the assertion is failling because this rule was broken, and "
            "the partner node has a larger nonce than the sending partner.")
        raise AssertionError(msg)

    if balanceproof0.nonce > nonce1:
        # TODO: Only consider the records up to saved state's state_change_id.
        # ATM this has a race condition where this utility could be called
        # before the alarm task fetches the corresponding event but while it
        # runs it does fetch it.
        sent_balance_proof = get_event_with_balance_proof_by_balance_hash(
            storage=app0.raiden.wal.storage,
            canonical_identifier=balanceproof0.canonical_identifier,
            balance_hash=balanceproof0.balance_hash,
            recipient=app1.raiden.address,
        )
        received_balance_proof = get_state_change_with_balance_proof_by_locksroot(
            storage=app1.raiden.wal.storage,
            canonical_identifier=balanceproof0.canonical_identifier,
            locksroot=balanceproof0.locksroot,
            sender=app0.raiden.address,
        )

        if received_balance_proof is not None:
            if type(received_balance_proof) == ReceiveTransferRefund:
                msg = (
                    f"Node1 received a refund from node0 and rejected it. This "
                    f"is likely a Raiden bug. state_change={received_balance_proof}"
                )
            elif type(received_balance_proof) in (
                    ActionInitMediator,
                    ActionInitTarget,
                    ReceiveUnlock,
                    ReceiveLockExpired,
            ):
                if type(received_balance_proof) == ReceiveUnlock:
                    assert isinstance(received_balance_proof,
                                      ReceiveUnlock), MYPY_ANNOTATION
                    is_valid, _, innermsg = channel.handle_unlock(
                        channel_state=channel1, unlock=received_balance_proof)
                elif type(received_balance_proof) == ReceiveLockExpired:
                    assert isinstance(received_balance_proof,
                                      ReceiveLockExpired), MYPY_ANNOTATION
                    is_valid, innermsg, _ = channel.is_valid_lock_expired(
                        state_change=received_balance_proof,
                        channel_state=channel1,
                        sender_state=channel1.partner_state,
                        receiver_state=channel1.our_state,
                        block_number=saved_state1.state.block_number,
                    )
                else:
                    assert isinstance(received_balance_proof,
                                      (ActionInitMediator,
                                       ActionInitTarget)), MYPY_ANNOTATION
                    is_valid, _, innermsg = channel.handle_receive_lockedtransfer(
                        channel_state=channel1,
                        mediated_transfer=received_balance_proof.from_transfer,
                    )

                if not is_valid:
                    msg = (
                        f"Node1 received the node0's message but rejected it. This "
                        f"is likely a Raiden bug. reason={innermsg} "
                        f"state_change={received_balance_proof}")
                else:
                    msg = (
                        f"Node1 received the node0's message at that time it "
                        f"was rejected, this is likely a race condition, node1 "
                        f"has to process the message again. reason={innermsg} "
                        f"state_change={received_balance_proof}")
            else:
                msg = (
                    f"Node1 received the node0's message but rejected it. This "
                    f"is likely a Raiden bug. state_change={received_balance_proof}"
                )

        elif sent_balance_proof is None:
            msg = (
                "Node0 did not send a message with the latest balanceproof, "
                "this is likely a Raiden bug.")
        else:
            msg = (
                "Node0 sent the latest balanceproof but Node1 didn't receive, "
                "likely the test is missing proper synchronization amongst the "
                "nodes.")

        msg = (f"{msg}. "
               f"node1={to_checksum_address(app1.raiden.address)} "
               f"node0={to_checksum_address(app0.raiden.address)} "
               f"state_change_id0={saved_state0.state_change_id} "
               f"state_change_id1={saved_state1.state_change_id}.")

        raise AssertionError(msg)

    is_equal = (balanceproof0.nonce == balanceproof1.nonce
                and balanceproof0.transferred_amount
                == balanceproof1.transferred_amount
                and balanceproof0.locked_amount == balanceproof1.locked_amount
                and balanceproof0.locksroot == balanceproof1.locksroot
                and balanceproof0.canonical_identifier
                == balanceproof1.canonical_identifier
                and balanceproof0.balance_hash == balanceproof1.balance_hash)

    if not is_equal:
        msg = (
            f"The balance proof seems corrupted, the recipient has different "
            f"values than the sender. "
            f"node1={to_checksum_address(app1.raiden.address)} "
            f"node0={to_checksum_address(app0.raiden.address)} "
            f"state_change_id0={saved_state0.state_change_id} "
            f"state_change_id1={saved_state1.state_change_id}.")

        raise AssertionError(msg)
Beispiel #10
0
def handle_block(
    initiator_state: InitiatorTransferState,
    state_change: Block,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
) -> TransitionResult:
    secrethash = initiator_state.transfer.lock.secrethash
    locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get(
        secrethash)

    if not locked_lock:
        if channel_state.partner_state.secrethashes_to_lockedlocks.get(
                secrethash):
            return TransitionResult(initiator_state, list())
        else:
            # if lock is not in our or our partner's locked locks then the
            # task can go
            return TransitionResult(None, list())

    lock_expiration_threshold = BlockNumber(
        locked_lock.expiration + DEFAULT_WAIT_BEFORE_LOCK_REMOVAL, )
    lock_has_expired, _ = channel.is_lock_expired(
        end_state=channel_state.our_state,
        lock=locked_lock,
        block_number=state_change.block_number,
        lock_expiration_threshold=lock_expiration_threshold,
    )

    if lock_has_expired:
        expired_lock_events = channel.events_for_expired_lock(
            channel_state=channel_state,
            locked_lock=locked_lock,
            pseudo_random_generator=pseudo_random_generator,
        )
        transfer_description = initiator_state.transfer_description
        # TODO: When we introduce multiple transfers per payment this needs to be
        #       reconsidered. As we would want to try other routes once a route
        #       has failed, and a transfer failing does not mean the entire payment
        #       would have to fail.
        #       Related issue: https://github.com/raiden-network/raiden/issues/2329
        transfer_failed = EventPaymentSentFailed(
            payment_network_identifier=transfer_description.
            payment_network_identifier,
            token_network_identifier=transfer_description.
            token_network_identifier,
            identifier=transfer_description.payment_identifier,
            target=transfer_description.target,
            reason="transfer's lock has expired",
        )
        expired_lock_events.append(transfer_failed)
        lock_exists = channel.lock_exists_in_either_channel_side(
            channel_state=channel_state,
            secrethash=secrethash,
        )

        return TransitionResult(
            # If the lock is either in our state or partner state we keep the
            # task around to wait for the LockExpired messages to sync.
            # Check https://github.com/raiden-network/raiden/issues/3183
            initiator_state if lock_exists else None,
            cast(List[Event], expired_lock_events),
        )
    else:
        return TransitionResult(initiator_state, list())