Ejemplo n.º 1
0
def handle_cancelpayment(
    payment_state: InitiatorPaymentState,
    channelidentifiers_to_channels: ChannelMap
) -> TransitionResult[InitiatorPaymentState]:
    """ Cancel the payment and all related transfers. """
    # Cannot cancel a transfer after the secret is revealed
    events = list()
    for initiator_state in payment_state.initiator_transfers.values():
        channel_identifier = initiator_state.channel_identifier
        channel_state = channelidentifiers_to_channels.get(channel_identifier)

        if not channel_state:
            continue

        if can_cancel(initiator_state):
            transfer_description = initiator_state.transfer_description
            cancel_events = cancel_current_route(payment_state,
                                                 initiator_state)

            initiator_state.transfer_state = "transfer_cancelled"

            cancel = EventPaymentSentFailed(
                payment_network_identifier=channel_state.
                payment_network_identifier,
                token_network_identifier=TokenNetworkID(
                    channel_state.token_network_identifier),
                identifier=transfer_description.payment_identifier,
                target=transfer_description.target,
                reason="user canceled payment",
            )
            cancel_events.append(cancel)

            events.extend(cancel_events)

    return TransitionResult(payment_state, events)
Ejemplo n.º 2
0
def next_channel_from_routes(
    available_routes: typing.List[RouteState],
    channelidentifiers_to_channels: typing.ChannelMap,
    transfer_amount: typing.TokenAmount,
) -> typing.Optional[NettingChannelState]:
    """ Returns the first channel that can be used to start the transfer.
    The routing service can race with local changes, so the recommended routes
    must be validated.
    """
    for route in available_routes:
        channel_identifier = route.channel_identifier
        channel_state = channelidentifiers_to_channels.get(channel_identifier)

        if not channel_state:
            continue

        if channel.get_status(channel_state) != CHANNEL_STATE_OPENED:
            continue

        pending_transfers = channel.get_number_of_pending_transfers(
            channel_state.our_state)
        if pending_transfers >= MAXIMUM_PENDING_TRANSFERS:
            continue

        distributable = channel.get_distributable(
            channel_state.our_state,
            channel_state.partner_state,
        )
        if transfer_amount > distributable:
            continue

        if channel.is_valid_amount(channel_state.our_state, transfer_amount):
            return channel_state

    return None
Ejemplo n.º 3
0
def subdispatch_to_initiatortransfer(
    payment_state: InitiatorPaymentState,
    initiator_state: InitiatorTransferState,
    state_change: StateChange,
    channelidentifiers_to_channels: ChannelMap,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[InitiatorPaymentState]:
    channel_identifier = initiator_state.channel_identifier
    channel_state = channelidentifiers_to_channels.get(channel_identifier)
    if not channel_state:
        return TransitionResult(initiator_state, list())

    sub_iteration = initiator.state_transition(
        initiator_state=initiator_state,
        state_change=state_change,
        channel_state=channel_state,
        pseudo_random_generator=pseudo_random_generator,
        block_number=block_number,
    )

    if sub_iteration.new_state is None:
        del payment_state.initiator_transfers[
            initiator_state.transfer.lock.secrethash]

    return sub_iteration
Ejemplo n.º 4
0
def events_for_expired_locks(
        mediator_state: MediatorTransferState,
        channelidentifiers_to_channels: typing.ChannelMap,
        block_number: typing.BlockNumber,
        pseudo_random_generator: random.Random,
):
    events = list()

    transfer_pair: MediationPairState
    for transfer_pair in mediator_state.transfers_pair:
        balance_proof = transfer_pair.payee_transfer.balance_proof
        channel_identifier = balance_proof.channel_identifier
        channel_state = channelidentifiers_to_channels.get(channel_identifier)

        assert channel_state, "Couldn't find channel for channel_id: {}".format(channel_identifier)

        secrethash = mediator_state.secrethash
        locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get(secrethash)
        lock_expired = channel.is_lock_expired(
            end_state=channel_state.our_state,
            locked_lock=locked_lock,
            secrethash=secrethash,
            block_number=block_number,
        )
        if locked_lock and lock_expired:
            # Lock has expired, cleanup...
            transfer_pair.payee_state = 'payee_expired'
            expired_lock_events = channel.events_for_expired_lock(
                channel_state,
                secrethash,
                locked_lock,
                pseudo_random_generator,
            )
            events.extend(expired_lock_events)
    return events
Ejemplo n.º 5
0
def handle_lock_expired(
    mediator_state: MediatorTransferState,
    state_change: ReceiveLockExpired,
    channelidentifiers_to_channels: typing.ChannelMap,
    block_number: typing.BlockNumber,
):
    events = list()

    for transfer_pair in mediator_state.transfers_pair:
        balance_proof = transfer_pair.payer_transfer.balance_proof
        channel_state = channelidentifiers_to_channels.get(
            balance_proof.channel_identifier)

        if not channel_state:
            return TransitionResult(mediator_state, list())

        result = channel.handle_receive_lock_expired(
            channel_state=channel_state,
            state_change=state_change,
            block_number=block_number,
        )
        if not channel.get_lock(result.new_state.partner_state,
                                mediator_state.secrethash):
            transfer_pair.payer_state = 'payer_expired'
            events.extend(result.events)

    return TransitionResult(mediator_state, events)
Ejemplo n.º 6
0
def events_to_remove_expired_locks(
        mediator_state: MediatorTransferState,
        channelidentifiers_to_channels: typing.ChannelMap,
        block_number: typing.BlockNumber,
        pseudo_random_generator: random.Random,
):
    """ Clear the channels which have expired locks.

    This only considers the *sent* transfers, received transfers can only be
    updated by the partner.
    """
    events = list()

    for transfer_pair in mediator_state.transfers_pair:
        balance_proof = transfer_pair.payee_transfer.balance_proof
        channel_identifier = balance_proof.channel_identifier
        channel_state = channelidentifiers_to_channels.get(channel_identifier)

        assert channel_state, "Couldn't find channel for channel_id: {}".format(channel_identifier)

        secrethash = mediator_state.secrethash
        lock = None
        if secrethash in channel_state.our_state.secrethashes_to_lockedlocks:
            assert secrethash not in channel_state.our_state.secrethashes_to_unlockedlocks
            lock = channel_state.our_state.secrethashes_to_lockedlocks.get(secrethash)
        elif secrethash in channel_state.our_state.secrethashes_to_unlockedlocks:
            lock = channel_state.our_state.secrethashes_to_unlockedlocks.get(secrethash)

        if lock:
            lock_expiration_threshold = (
                lock.expiration +
                DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2
            )
            has_lock_expired, _ = channel.is_lock_expired(
                end_state=channel_state.our_state,
                lock=lock,
                block_number=block_number,
                lock_expiration_threshold=lock_expiration_threshold,
            )

            if has_lock_expired:
                transfer_pair.payee_state = 'payee_expired'
                expired_lock_events = channel.events_for_expired_lock(
                    channel_state=channel_state,
                    locked_lock=lock,
                    pseudo_random_generator=pseudo_random_generator,
                )
                events.extend(expired_lock_events)

                unlock_failed = EventUnlockFailed(
                    transfer_pair.payee_transfer.payment_identifier,
                    transfer_pair.payee_transfer.lock.secrethash,
                    'lock expired',
                )
                events.append(unlock_failed)

    return events
Ejemplo n.º 7
0
def handle_lock_expired(
    payment_state: InitiatorPaymentState,
    state_change: ReceiveLockExpired,
    channelidentifiers_to_channels: ChannelMap,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[InitiatorPaymentState]:
    """Initiator also needs to handle LockExpired messages when refund transfers are involved.

    A -> B -> C

    - A sends locked transfer to B
    - B attempted to forward to C but has not enough capacity
    - B sends a refund transfer with the same secrethash back to A
    - When the lock expires B will also send a LockExpired message to A
    - A needs to be able to properly process it

    Related issue: https://github.com/raiden-network/raiden/issues/3183
"""
    initiator_state = payment_state.initiator_transfers.get(
        state_change.secrethash)
    if not initiator_state:
        return TransitionResult(payment_state, list())

    channel_identifier = initiator_state.channel_identifier
    channel_state = channelidentifiers_to_channels.get(channel_identifier)

    if not channel_state:
        return TransitionResult(payment_state, list())

    secrethash = initiator_state.transfer.lock.secrethash
    result = channel.handle_receive_lock_expired(
        channel_state=channel_state,
        state_change=state_change,
        block_number=block_number,
    )

    if not channel.get_lock(result.new_state.partner_state, secrethash):
        transfer = initiator_state.transfer
        unlock_failed = EventUnlockClaimFailed(
            identifier=transfer.payment_identifier,
            secrethash=transfer.lock.secrethash,
            reason='Lock expired',
        )
        result.events.append(unlock_failed)

    return TransitionResult(payment_state, result.events)
Ejemplo n.º 8
0
def handle_block(
    payment_state: InitiatorPaymentState,
    state_change: Block,
    channelidentifiers_to_channels: typing.ChannelMap,
    pseudo_random_generator: random.Random,
) -> TransitionResult:
    channel_identifier = payment_state.initiator.channel_identifier
    channel_state = channelidentifiers_to_channels.get(channel_identifier)
    if not channel_state:
        return TransitionResult(payment_state, list())

    sub_iteration = initiator.handle_block(
        initiator_state=payment_state.initiator,
        state_change=state_change,
        channel_state=channel_state,
        pseudo_random_generator=pseudo_random_generator,
    )
    iteration = iteration_from_sub(payment_state, sub_iteration)
    return iteration
Ejemplo n.º 9
0
def handle_block(
        payment_state: InitiatorPaymentState,
        state_change: Block,
        channelidentifiers_to_channels: ChannelMap,
        pseudo_random_generator: random.Random,
) -> TransitionResult:
    channel_identifier = payment_state.initiator.channel_identifier
    channel_state = channelidentifiers_to_channels.get(channel_identifier)
    if not channel_state:
        return TransitionResult(payment_state, list())

    sub_iteration = initiator.handle_block(
        initiator_state=payment_state.initiator,
        state_change=state_change,
        channel_state=channel_state,
        pseudo_random_generator=pseudo_random_generator,
    )
    iteration = iteration_from_sub(payment_state, sub_iteration)
    return iteration
Ejemplo n.º 10
0
def handle_init(
    state_change: ActionInitMediator,
    channelidentifiers_to_channels: typing.ChannelMap,
    pseudo_random_generator: random.Random,
    block_number: typing.BlockNumber,
):
    routes = state_change.routes

    from_route = state_change.from_route
    from_transfer = state_change.from_transfer
    payer_channel = channelidentifiers_to_channels.get(
        from_route.channel_identifier)

    # There is no corresponding channel for the message, ignore it
    if not payer_channel:
        return TransitionResult(None, [])

    mediator_state = MediatorTransferState(from_transfer.lock.secrethash)

    is_valid, events, _ = channel.handle_receive_lockedtransfer(
        payer_channel,
        from_transfer,
    )
    if not is_valid:
        # If the balance proof is not valid, do *not* create a task. Otherwise it's
        # possible for an attacker to send multiple invalid transfers, and increase
        # the memory usage of this Node.
        return TransitionResult(None, events)

    iteration = mediate_transfer(
        mediator_state,
        routes,
        payer_channel,
        channelidentifiers_to_channels,
        pseudo_random_generator,
        from_transfer,
        block_number,
    )

    events.extend(iteration.events)
    return TransitionResult(iteration.new_state, events)
Ejemplo n.º 11
0
def events_for_expired_locks(
    mediator_state: MediatorTransferState,
    channelidentifiers_to_channels: typing.ChannelMap,
    block_number: typing.BlockNumber,
    pseudo_random_generator: random.Random,
):
    events = list()

    for transfer_pair in mediator_state.transfers_pair:
        balance_proof = transfer_pair.payee_transfer.balance_proof
        channel_identifier = balance_proof.channel_identifier
        channel_state = channelidentifiers_to_channels.get(channel_identifier)

        assert channel_state, "Couldn't find channel for channel_id: {}".format(
            channel_identifier)

        secrethash = mediator_state.secrethash
        locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get(
            secrethash)

        if locked_lock:
            lock_expiration_threshold = (
                locked_lock.expiration +
                DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2)
            has_lock_expired, _ = channel.is_lock_expired(
                end_state=channel_state.our_state,
                lock=locked_lock,
                block_number=block_number,
                lock_expiration_threshold=lock_expiration_threshold,
            )

            if has_lock_expired:
                transfer_pair.payee_state = 'payee_expired'
                expired_lock_events = channel.events_for_expired_lock(
                    channel_state,
                    secrethash,
                    locked_lock,
                    pseudo_random_generator,
                )
                events.extend(expired_lock_events)
    return events
Ejemplo n.º 12
0
def handle_init(
    state_change: ActionInitMediator,
    channelidentifiers_to_channels: typing.ChannelMap,
    pseudo_random_generator: random.Random,
    block_number: typing.BlockNumber,
):
    routes = state_change.routes

    from_route = state_change.from_route
    from_transfer = state_change.from_transfer
    payer_channel = channelidentifiers_to_channels.get(
        from_route.channel_identifier)

    # There is no corresponding channel for the message, ignore it
    if not payer_channel:
        return TransitionResult(None, [])

    mediator_state = MediatorTransferState(from_transfer.lock.secrethash)

    is_valid, events, _ = channel.handle_receive_lockedtransfer(
        payer_channel,
        from_transfer,
    )
    if not is_valid:
        return TransitionResult(None, events)

    iteration = mediate_transfer(
        mediator_state,
        routes,
        payer_channel,
        channelidentifiers_to_channels,
        pseudo_random_generator,
        from_transfer,
        block_number,
    )

    events.extend(iteration.events)
    return TransitionResult(iteration.new_state, events)
Ejemplo n.º 13
0
def handle_transferrefundcancelroute(
    payment_state: InitiatorPaymentState,
    state_change: ReceiveTransferRefundCancelRoute,
    channelidentifiers_to_channels: ChannelMap,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[InitiatorPaymentState]:
    initiator_state = payment_state.initiator_transfers.get(
        state_change.transfer.lock.secrethash)
    if not initiator_state:
        return TransitionResult(payment_state, list())

    channel_identifier = initiator_state.channel_identifier
    channel_state = channelidentifiers_to_channels.get(channel_identifier)

    if not channel_state:
        return TransitionResult(payment_state, list())

    refund_transfer = state_change.transfer
    original_transfer = initiator_state.transfer

    is_valid_lock = (
        refund_transfer.lock.secrethash == original_transfer.lock.secrethash
        and refund_transfer.lock.amount == original_transfer.lock.amount and
        refund_transfer.lock.expiration == original_transfer.lock.expiration)

    is_valid_refund = channel.refund_transfer_matches_transfer(
        refund_transfer, original_transfer)

    events = list()
    if not is_valid_lock or not is_valid_refund:
        return TransitionResult(payment_state, list())

    is_valid, channel_events, _ = channel.handle_receive_refundtransfercancelroute(
        channel_state, refund_transfer)

    events.extend(channel_events)

    if not is_valid:
        return TransitionResult(payment_state, list())

    route_failed_event = EventRouteFailed(
        secrethash=original_transfer.lock.secrethash)
    events.append(route_failed_event)

    old_description = initiator_state.transfer_description
    transfer_description = TransferDescriptionWithSecretState(
        payment_network_identifier=old_description.payment_network_identifier,
        payment_identifier=old_description.payment_identifier,
        amount=old_description.amount,
        token_network_identifier=old_description.token_network_identifier,
        allocated_fee=old_description.allocated_fee,
        initiator=old_description.initiator,
        target=old_description.target,
        secret=state_change.secret,
    )

    sub_iteration = maybe_try_new_route(
        payment_state=payment_state,
        initiator_state=initiator_state,
        transfer_description=transfer_description,
        available_routes=state_change.routes,
        channelidentifiers_to_channels=channelidentifiers_to_channels,
        pseudo_random_generator=pseudo_random_generator,
        block_number=block_number,
    )

    events.extend(sub_iteration.events)

    iteration = TransitionResult(payment_state, events)

    return iteration
Ejemplo n.º 14
0
def events_for_expired_pairs(
        channelidentifiers_to_channels: typing.ChannelMap,
        transfers_pair: typing.List[MediationPairState],
        waiting_transfer: WaitingTransferState,
        block_number: typing.BlockNumber,
) -> typing.List[Event]:
    """ Informational events for expired locks. """
    pending_transfers_pairs = get_pending_transfer_pairs(transfers_pair)

    events = list()
    for pair in pending_transfers_pairs:
        payer_balance_proof = pair.payer_transfer.balance_proof
        payer_channel = channelidentifiers_to_channels.get(payer_balance_proof.channel_identifier)
        payer_lock_expiration_threshold = (
            pair.payer_transfer.lock.expiration +
            DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2
        )
        has_payer_lock_expired, _ = channel.is_lock_expired(
            end_state=payer_channel.our_state,
            lock=pair.payer_transfer.lock,
            block_number=block_number,
            lock_expiration_threshold=payer_lock_expiration_threshold,
        )
        has_payer_transfer_expired = (
            has_payer_lock_expired and
            pair.payer_state != 'payer_expired'
        )

        if has_payer_transfer_expired:
            # For safety, the correct behavior is:
            #
            # - If the payee has been paid, then the payer must pay too.
            #
            #   And the corollary:
            #
            # - If the payer transfer has expired, then the payee transfer must
            #   have expired too.
            #
            # The problem is that this corollary cannot be asserted. If a user
            # is running Raiden without a monitoring service, then it may go
            # offline after having paid a transfer to a payee, but without
            # getting a balance proof of the payer, and once it comes back
            # online the transfer may have expired.
            #
            # assert pair.payee_state == 'payee_expired'

            pair.payer_state = 'payer_expired'
            unlock_claim_failed = EventUnlockClaimFailed(
                pair.payer_transfer.payment_identifier,
                pair.payer_transfer.lock.secrethash,
                'lock expired',
            )
            events.append(unlock_claim_failed)

    if waiting_transfer and waiting_transfer.state != 'expired':
        waiting_transfer.state = 'expired'
        unlock_claim_failed = EventUnlockClaimFailed(
            waiting_transfer.transfer.payment_identifier,
            waiting_transfer.transfer.lock.secrethash,
            'lock expired',
        )
        events.append(unlock_claim_failed)

    return events