Exemplo n.º 1
0
def test_initiator_handle_contract_receive_secret_reveal():
    """ Initiator must unlock off-chain if the secret is revealed on-chain and
    the channel is open.
    """
    setup = setup_initiator_tests(amount=UNIT_TRANSFER_AMOUNT * 2, block_number=10)

    transfer = setup.current_state.initiator.transfer
    assert transfer.lock.secrethash in setup.channel.our_state.secrethashes_to_lockedlocks

    state_change = ContractReceiveSecretReveal(
        transaction_hash=factories.make_transaction_hash(),
        secret_registry_address=factories.make_address(),
        secrethash=transfer.lock.secrethash,
        secret=UNIT_SECRET,
        block_number=transfer.lock.expiration,
    )

    message_identifier = message_identifier_from_prng(deepcopy(setup.prng))

    iteration = initiator_manager.handle_onchain_secretreveal(
        payment_state=setup.current_state,
        state_change=state_change,
        channelidentifiers_to_channels=setup.channel_map,
        pseudo_random_generator=setup.prng,
    )

    payment_identifier = setup.current_state.initiator.transfer_description.payment_identifier
    assert events.must_contain_entry(iteration.events, SendBalanceProof, {
        'message_identifier': message_identifier,
        'payment_identifier': payment_identifier,
    })
Exemplo n.º 2
0
def handle_secretrequest(
        initiator_state: InitiatorTransferState,
        state_change: ReceiveSecretRequest,
        pseudo_random_generator: random.Random,
) -> TransitionResult:

    request_from_target = (
        state_change.sender == initiator_state.transfer_description.target and
        state_change.secrethash == initiator_state.transfer_description.secrethash
    )

    is_valid_payment_id = (
        state_change.payment_identifier == initiator_state.transfer_description.payment_identifier
    )

    valid_secretrequest = (
        request_from_target and
        is_valid_payment_id and
        state_change.amount == initiator_state.transfer_description.amount
    )

    invalid_secretrequest = request_from_target and (
        is_valid_payment_id or
        state_change.amount != initiator_state.transfer_description.amount
    )

    if valid_secretrequest:
        # Reveal the secret to the target node and wait for its confirmation.
        # At this point the transfer is not cancellable anymore as either the lock
        # timeouts or a secret reveal is received.
        #
        # Note: The target might be the first hop
        #
        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        transfer_description = initiator_state.transfer_description
        recipient = transfer_description.target
        queue_name = b'global'
        revealsecret = SendRevealSecret(
            recipient,
            queue_name,
            message_identifier,
            transfer_description.secret,
        )

        initiator_state.revealsecret = revealsecret
        iteration = TransitionResult(initiator_state, [revealsecret])

    elif invalid_secretrequest:
        cancel = EventTransferSentFailed(
            identifier=initiator_state.transfer_description.payment_identifier,
            reason='bad secret request message from target',
        )
        iteration = TransitionResult(None, [cancel])

    else:
        iteration = TransitionResult(initiator_state, list())

    return iteration
Exemplo n.º 3
0
def events_for_refund_transfer(
        refund_channel,
        refund_transfer,
        pseudo_random_generator,
        timeout_blocks,
        block_number,
):
    """ Refund the transfer.
    Args:
        refund_route (RouteState): The original route that sent the mediated
            transfer to this node.
        refund_transfer (LockedTransferSignedState): The original mediated transfer
            from the refund_route.
        timeout_blocks (int): The number of blocks available from the /latest
            transfer/ received by this node, this transfer might be the
            original mediated transfer (if no route was available) or a refund
            transfer from a down stream node.
        block_number (int): The current block number.
    Returns:
        An empty list if there are not enough blocks to safely create a refund,
        or a list with a refund event."""
    # A refund transfer works like a special SendLockedTransfer, so it must
    # follow the same rules and decrement reveal_timeout from the
    # payee_transfer.
    new_lock_timeout = timeout_blocks - refund_channel.reveal_timeout

    distributable = channel.get_distributable(
        refund_channel.our_state,
        refund_channel.partner_state,
    )

    is_valid = (
        new_lock_timeout > 0 and
        refund_transfer.lock.amount <= distributable and
        channel.is_valid_amount(refund_channel.our_state, refund_transfer.lock.amount)
    )

    if is_valid:
        new_lock_expiration = new_lock_timeout + block_number

        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        refund_transfer = channel.send_refundtransfer(
            refund_channel,
            refund_transfer.initiator,
            refund_transfer.target,
            refund_transfer.lock.amount,
            message_identifier,
            refund_transfer.payment_identifier,
            new_lock_expiration,
            refund_transfer.lock.secrethash,
        )

        return [refund_transfer]

    # Can not create a refund lock with a safe expiration, so don't do anything
    # and wait for the received lock to expire.
    return list()
Exemplo n.º 4
0
def handle_inittarget(
        state_change,
        channel_state,
        pseudo_random_generator,
        block_number,
):
    """ Handles an ActionInitTarget state change. """
    transfer = state_change.transfer
    route = state_change.route

    target_state = TargetTransferState(
        route,
        transfer,
    )

    assert channel_state.identifier == transfer.balance_proof.channel_address
    is_valid, _, errormsg = channel.handle_receive_lockedtransfer(
        channel_state,
        transfer,
    )

    safe_to_wait = is_safe_to_wait(
        transfer.lock.expiration,
        channel_state.reveal_timeout,
        block_number,
    )

    # if there is not enough time to safely unlock the token on-chain
    # silently let the transfer expire.
    if is_valid and safe_to_wait:
        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        recipient = transfer.initiator
        queue_name = b'global'
        secret_request = SendSecretRequest(
            recipient,
            queue_name,
            message_identifier,
            transfer.payment_identifier,
            transfer.lock.amount,
            transfer.lock.secrethash,
        )

        iteration = TransitionResult(target_state, [secret_request])
    else:
        if not is_valid:
            failure_reason = errormsg
        elif not safe_to_wait:
            failure_reason = 'lock expiration is not safe'

        unlock_failed = EventUnlockClaimFailed(
            identifier=transfer.payment_identifier,
            secrethash=transfer.lock.secrethash,
            reason=failure_reason,
        )
        iteration = TransitionResult(target_state, [unlock_failed])

    return iteration
Exemplo n.º 5
0
def handle_secretreveal(
        initiator_state: InitiatorTransferState,
        state_change: ReceiveSecretReveal,
        channel_state: NettingChannelState,
        pseudo_random_generator: random.Random,
) -> TransitionResult:
    """ Send a balance proof to the next hop with the current mediated transfer
    lock removed and the balance updated.
    """
    is_valid_secret_reveal = (
        state_change.sender == channel_state.partner_state.address and
        state_change.secrethash == initiator_state.transfer_description.secrethash
    )

    # If the channel is closed the balance proof must not be sent
    is_channel_open = channel.get_status(channel_state) == CHANNEL_STATE_OPENED

    if is_valid_secret_reveal and is_channel_open:
        # next hop learned the secret, unlock the token locally and send the
        # lock claim message to next hop
        transfer_description = initiator_state.transfer_description

        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        unlock_lock = channel.send_unlock(
            channel_state,
            transfer_description.payment_identifier,
            message_identifier,
            state_change.secret,
            state_change.secrethash,
        )

        # TODO: Emit these events after on-chain unlock
        transfer_success = EventTransferSentSuccess(
            transfer_description.payment_identifier,
            transfer_description.amount,
            transfer_description.target,
        )

        unlock_success = EventUnlockSuccess(
            transfer_description.payment_identifier,
            transfer_description.secrethash,
        )

        iteration = TransitionResult(None, [transfer_success, unlock_success, unlock_lock])
    else:
        iteration = TransitionResult(initiator_state, list())

    return iteration
Exemplo n.º 6
0
def try_new_route(
        channelidentifiers_to_channels: ChannelMap,
        available_routes: typing.List[RouteState],
        transfer_description: TransferDescriptionWithSecretState,
        pseudo_random_generator: random.Random,
        block_number: typing.BlockNumber,
) -> TransitionResult:

    channel_state = next_channel_from_routes(
        available_routes,
        channelidentifiers_to_channels,
        transfer_description.amount,
    )

    events = list()
    if channel_state is None:
        if not available_routes:
            reason = 'there is no route available'
        else:
            reason = 'none of the available routes could be used'

        transfer_failed = EventTransferSentFailed(
            identifier=transfer_description.payment_identifier,
            reason=reason,
        )
        events.append(transfer_failed)

        initiator_state = None

    else:
        initiator_state = InitiatorTransferState(
            transfer_description,
            channel_state.identifier,
        )

        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        lockedtransfer_event = send_lockedtransfer(
            initiator_state,
            channel_state,
            message_identifier,
            block_number,
        )
        assert lockedtransfer_event

        events.append(lockedtransfer_event)

    return TransitionResult(initiator_state, events)
Exemplo n.º 7
0
def handle_offchain_secretreveal(
        target_state: TargetTransferState,
        state_change: ReceiveSecretReveal,
        channel_state: NettingChannelState,
        pseudo_random_generator: random.Random,
        block_number: BlockNumber,
):
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = is_valid_secret_reveal(
        state_change=state_change,
        transfer_secrethash=target_state.transfer.lock.secrethash,
        secret=state_change.secret,
    )
    has_transfer_expired = channel.transfer_expired(
        transfer=target_state.transfer,
        affected_channel=channel_state,
        block_number=block_number,
    )

    if valid_secret and not has_transfer_expired:
        channel.register_offchain_secret(
            channel_state=channel_state,
            secret=state_change.secret,
            secrethash=state_change.secrethash,
        )

        route = target_state.route
        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL
        target_state.secret = state_change.secret
        recipient = route.node_address

        reveal = SendSecretReveal(
            recipient=recipient,
            channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
            message_identifier=message_identifier,
            secret=target_state.secret,
        )

        iteration = TransitionResult(target_state, [reveal])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 8
0
def events_for_revealsecret(transfers_pair, secret, pseudo_random_generator):
    """ Reveal the secret backwards.

    This node is named N, suppose there is a mediated transfer with two refund
    transfers, one from B and one from C:

        A-N-B...B-N-C..C-N-D

    Under normal operation N will first learn the secret from D, then reveal to
    C, wait for C to inform the secret is known before revealing it to B, and
    again wait for B before revealing the secret to A.

    If B somehow sent a reveal secret before C and D, then the secret will be
    revealed to A, but not C and D, meaning the secret won't be propagated
    forward. Even if D sent a reveal secret at about the same time, the secret
    will only be revealed to B upon confirmation from C.

    Even though B somehow learnt the secret out-of-order N is safe to proceed
    with the protocol, the TRANSIT_BLOCKS configuration adds enough time for
    the reveal secrets to propagate backwards and for B to send the balance
    proof. If the proof doesn't arrive in time and the lock's expiration is at
    risk, N won't lose tokens since it knows the secret can go on-chain at any
    time.
    """
    events = list()
    for pair in reversed(transfers_pair):
        payee_secret = pair.payee_state in STATE_SECRET_KNOWN
        payer_secret = pair.payer_state in STATE_SECRET_KNOWN

        if payee_secret and not payer_secret:
            message_identifier = message_identifier_from_prng(
                pseudo_random_generator)
            pair.payer_state = 'payer_secret_revealed'
            payer_transfer = pair.payer_transfer
            queue_name = b'global'
            revealsecret = SendRevealSecret(
                payer_transfer.balance_proof.sender,
                queue_name,
                message_identifier,
                secret,
                payer_transfer.token,
            )

            events.append(revealsecret)

    return events
Exemplo n.º 9
0
def handle_secretreveal(
    target_state,
    state_change,
    channel_state,
    pseudo_random_generator,
):
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash

    if valid_secret:
        if isinstance(state_change, ReceiveSecretReveal):
            channel.register_secret(
                channel_state,
                state_change.secret,
                state_change.secrethash,
            )
        elif isinstance(state_change, ContractReceiveSecretReveal):
            channel.register_onchain_secret(
                channel_state,
                state_change.secret,
                state_change.secrethash,
            )
        else:
            assert False, 'Got unexpected StateChange'

        route = target_state.route
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        target_state.state = 'reveal_secret'
        target_state.secret = state_change.secret
        recipient = route.node_address
        queue_name = b'global'
        reveal = SendRevealSecret(
            recipient,
            queue_name,
            message_identifier,
            target_state.secret,
        )

        iteration = TransitionResult(target_state, [reveal])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 10
0
def events_for_balanceproof(
        channelidentifiers_to_channels,
        transfers_pair,
        pseudo_random_generator,
        block_number,
        secret,
        secrethash,
):
    """ Send the balance proof to nodes that know the secret. """

    events = list()
    for pair in reversed(transfers_pair):
        payee_knows_secret = pair.payee_state in STATE_SECRET_KNOWN
        payee_payed = pair.payee_state in STATE_TRANSFER_PAID

        payee_channel = get_payee_channel(channelidentifiers_to_channels, pair)
        payee_channel_open = channel.get_status(payee_channel) == CHANNEL_STATE_OPENED

        # XXX: All nodes must close the channel and unlock on-chain if the
        # lock is nearing it's expiration block, what should be the strategy
        # for sending a balance proof to a node that knowns the secret but has
        # not gone on-chain while near the expiration? (The problem is how to
        # define the unsafe region, since that is a local configuration)
        lock_valid = is_lock_valid(pair.payee_transfer.lock.expiration, block_number)

        if payee_channel_open and payee_knows_secret and not payee_payed and lock_valid:
            pair.payee_state = 'payee_balance_proof'

            message_identifier = message_identifier_from_prng(pseudo_random_generator)
            unlock_lock = channel.send_unlock(
                payee_channel,
                pair.payee_transfer.payment_identifier,
                message_identifier,
                secret,
                secrethash,
            )

            unlock_success = EventUnlockSuccess(
                pair.payer_transfer.payment_identifier,
                pair.payer_transfer.lock.secrethash,
            )
            events.append(unlock_lock)
            events.append(unlock_success)

    return events
Exemplo n.º 11
0
def handle_secretreveal(
        target_state,
        state_change,
        channel_state,
        pseudo_random_generator,
):
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash

    if valid_secret:
        if isinstance(state_change, ReceiveSecretReveal):
            channel.register_secret(
                channel_state,
                state_change.secret,
                state_change.secrethash,
            )
        elif isinstance(state_change, ContractReceiveSecretReveal):
            channel.register_onchain_secret(
                channel_state,
                state_change.secret,
                state_change.secrethash,
            )
        else:
            assert False, 'Got unexpected StateChange'

        route = target_state.route
        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        target_state.state = 'reveal_secret'
        target_state.secret = state_change.secret
        recipient = route.node_address
        queue_name = b'global'
        reveal = SendRevealSecret(
            recipient,
            queue_name,
            message_identifier,
            target_state.secret,
        )

        iteration = TransitionResult(target_state, [reveal])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 12
0
def events_for_balanceproof(
        channelidentifiers_to_channels,
        transfers_pair,
        pseudo_random_generator,
        block_number,
        secret,
        secrethash,
):
    """ Send the balance proof to nodes that know the secret. """

    events = list()
    for pair in reversed(transfers_pair):
        payee_knows_secret = pair.payee_state in STATE_SECRET_KNOWN
        payee_payed = pair.payee_state in STATE_TRANSFER_PAID

        payee_channel = get_payee_channel(channelidentifiers_to_channels, pair)
        payee_channel_open = channel.get_status(payee_channel) == CHANNEL_STATE_OPENED

        # XXX: All nodes must close the channel and unlock on-chain if the
        # lock is nearing it's expiration block, what should be the strategy
        # for sending a balance proof to a node that knowns the secret but has
        # not gone on-chain while near the expiration? (The problem is how to
        # define the unsafe region, since that is a local configuration)
        lock_valid = is_lock_valid(pair.payee_transfer.lock.expiration, block_number)

        if payee_channel_open and payee_knows_secret and not payee_payed and lock_valid:
            pair.payee_state = 'payee_balance_proof'

            message_identifier = message_identifier_from_prng(pseudo_random_generator)
            unlock_lock = channel.send_unlock(
                payee_channel,
                pair.payee_transfer.payment_identifier,
                message_identifier,
                secret,
                secrethash,
            )

            unlock_success = EventUnlockSuccess(
                pair.payer_transfer.payment_identifier,
                pair.payer_transfer.lock.secrethash,
            )
            events.append(unlock_lock)
            events.append(unlock_success)

    return events
Exemplo n.º 13
0
def events_for_revealsecret(transfers_pair, secret, pseudo_random_generator):
    """ Reveal the secret backwards.

    This node is named N, suppose there is a mediated transfer with two refund
    transfers, one from B and one from C:

        A-N-B...B-N-C..C-N-D

    Under normal operation N will first learn the secret from D, then reveal to
    C, wait for C to inform the secret is known before revealing it to B, and
    again wait for B before revealing the secret to A.

    If B somehow sent a reveal secret before C and D, then the secret will be
    revealed to A, but not C and D, meaning the secret won't be propagated
    forward. Even if D sent a reveal secret at about the same time, the secret
    will only be revealed to B upon confirmation from C.

    Even though B somehow learnt the secret out-of-order N is safe to proceed
    with the protocol, the TRANSIT_BLOCKS configuration adds enough time for
    the reveal secrets to propagate backwards and for B to send the balance
    proof. If the proof doesn't arrive in time and the lock's expiration is at
    risk, N won't lose tokens since it knows the secret can go on-chain at any
    time.
    """
    events = list()
    for pair in reversed(transfers_pair):
        payee_secret = pair.payee_state in STATE_SECRET_KNOWN
        payer_secret = pair.payer_state in STATE_SECRET_KNOWN

        if payee_secret and not payer_secret:
            message_identifier = message_identifier_from_prng(pseudo_random_generator)
            pair.payer_state = 'payer_secret_revealed'
            payer_transfer = pair.payer_transfer
            queue_name = b'global'
            revealsecret = SendRevealSecret(
                payer_transfer.balance_proof.sender,
                queue_name,
                message_identifier,
                secret,
            )

            events.append(revealsecret)

    return events
Exemplo n.º 14
0
def handle_offchain_secretreveal(
    target_state: TargetTransferState,
    state_change: ReceiveSecretReveal,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[TargetTransferState]:
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = is_valid_secret_reveal(
        state_change=state_change,
        transfer_secrethash=target_state.transfer.lock.secrethash)
    has_transfer_expired = channel.is_transfer_expired(
        transfer=target_state.transfer,
        affected_channel=channel_state,
        block_number=block_number)

    if valid_secret and not has_transfer_expired:
        channel.register_offchain_secret(
            channel_state=channel_state,
            secret=state_change.secret,
            secrethash=state_change.secrethash,
        )

        route = target_state.from_hop
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL
        target_state.secret = state_change.secret
        recipient = route.node_address

        reveal = SendSecretReveal(
            recipient=recipient,
            message_identifier=message_identifier,
            secret=target_state.secret,
            canonical_identifier=CANONICAL_IDENTIFIER_GLOBAL_QUEUE,
        )

        iteration = TransitionResult(target_state, [reveal])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 15
0
def events_for_refund_transfer(
    refund_channel,
    transfer_to_refund,
    pseudo_random_generator,
    block_number,
):
    """ Refund the transfer.
    Args:
        refund_route (RouteState): The original route that sent the mediated
            transfer to this node.
        transfer_to_refund (LockedTransferSignedState): The original mediated transfer
            from the refund_route.
        timeout_blocks (int): The number of blocks available from the /latest
            transfer/ received by this node, this transfer might be the
            original mediated transfer (if no route was available) or a refund
            transfer from a down stream node.
        block_number (int): The current block number.
    Returns:
        An empty list if there are not enough blocks to safely create a refund,
        or a list with a refund event."""
    lock_timeout = transfer_to_refund.lock.expiration - block_number
    transfer_amount = transfer_to_refund.lock.amount

    if is_channel_usable(refund_channel, transfer_amount, lock_timeout):
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        refund_transfer = channel.send_refundtransfer(
            refund_channel,
            transfer_to_refund.initiator,
            transfer_to_refund.target,
            transfer_to_refund.lock.amount,
            message_identifier,
            transfer_to_refund.payment_identifier,
            transfer_to_refund.lock.expiration,
            transfer_to_refund.lock.secrethash,
        )

        return [refund_transfer]

    # Can not create a refund lock with a safe expiration, so don't do anything
    # and wait for the received lock to expire.
    return list()
Exemplo n.º 16
0
def test_handle_offchain_secretreveal():
    setup = setup_initiator_tests()

    secret_reveal = ReceiveSecretReveal(
        secret=UNIT_SECRET,
        sender=setup.channel.partner_state.address,
    )
    message_identifier = message_identifier_from_prng(deepcopy(setup.prng))
    iteration = initiator.handle_offchain_secretreveal(
        initiator_state=setup.current_state.initiator,
        state_change=secret_reveal,
        channel_state=setup.channel,
        pseudo_random_generator=setup.prng,
    )

    payment_identifier = setup.current_state.initiator.transfer_description.payment_identifier
    assert events.must_contain_entry(iteration.events, SendBalanceProof, {
        'message_identifier': message_identifier,
        'payment_identifier': payment_identifier,
    })
Exemplo n.º 17
0
def test_handle_offchain_secretreveal():
    setup = setup_initiator_tests()

    secret_reveal = ReceiveSecretReveal(
        secret=UNIT_SECRET,
        sender=setup.channel.partner_state.address,
    )
    message_identifier = message_identifier_from_prng(deepcopy(setup.prng))
    iteration = initiator.handle_offchain_secretreveal(
        initiator_state=setup.current_state.initiator,
        state_change=secret_reveal,
        channel_state=setup.channel,
        pseudo_random_generator=setup.prng,
    )

    payment_identifier = setup.current_state.initiator.transfer_description.payment_identifier
    assert events.must_contain_entry(iteration.events, SendBalanceProof, {
        'message_identifier': message_identifier,
        'payment_identifier': payment_identifier,
    })
Exemplo n.º 18
0
def handle_offchain_secretreveal(
    target_state: TargetTransferState,
    state_change: ReceiveSecretReveal,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
):
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = is_valid_secret_reveal(
        state_change=state_change,
        transfer_secrethash=target_state.transfer.lock.secrethash,
        secret=state_change.secret,
    )

    if valid_secret:
        channel.register_offchain_secret(
            channel_state=channel_state,
            secret=state_change.secret,
            secrethash=state_change.secrethash,
        )

        route = target_state.route
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        target_state.state = 'reveal_secret'
        target_state.secret = state_change.secret
        recipient = route.node_address

        reveal = SendSecretReveal(
            recipient=recipient,
            channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
            message_identifier=message_identifier,
            secret=target_state.secret,
        )

        iteration = TransitionResult(target_state, [reveal])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 19
0
def events_for_revealsecret(transfers_pair, secret, pseudo_random_generator):
    """ Reveal the secret backwards.

    This node is named N, suppose there is a mediated transfer with two refund
    transfers, one from B and one from C:

        A-N-B...B-N-C..C-N-D

    Under normal operation N will first learn the secret from D, then reveal to
    C, wait for C to inform the secret is known before revealing it to B, and
    again wait for B before revealing the secret to A.

    If B somehow sent a reveal secret before C and D, then the secret will be
    revealed to A, but not C and D, meaning the secret won't be propagated
    forward. Even if D sent a reveal secret at about the same time, the secret
    will only be revealed to B upon confirmation from C.

    If the proof doesn't arrive in time and the lock's expiration is at risk, N
    won't lose tokens since it knows the secret can go on-chain at any time.
    """
    events = list()
    for pair in reversed(transfers_pair):
        payee_secret = pair.payee_state in STATE_SECRET_KNOWN
        payer_secret = pair.payer_state in STATE_SECRET_KNOWN
        should_send_secret = pair.payer_state == 'payer_pending'

        if payee_secret and not payer_secret and should_send_secret:
            message_identifier = message_identifier_from_prng(
                pseudo_random_generator)
            pair.payer_state = 'payer_secret_revealed'
            payer_transfer = pair.payer_transfer
            revealsecret = SendSecretReveal(
                recipient=payer_transfer.balance_proof.sender,
                channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
                message_identifier=message_identifier,
                secret=secret,
            )

            events.append(revealsecret)

    return events
Exemplo n.º 20
0
def test_handle_secretreveal():
    channel1 = factories.make_channel(
        our_balance=UNIT_TRANSFER_AMOUNT,
        token_address=UNIT_TOKEN_ADDRESS,
        token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS,
    )
    channel_map = {channel1.identifier: channel1}
    available_routes = [factories.route_from_channel(channel1)]
    pseudo_random_generator = random.Random()
    block_number = 10

    manager_state = make_initiator_manager_state(
        available_routes,
        factories.UNIT_TRANSFER_DESCRIPTION,
        channel_map,
        pseudo_random_generator,
        block_number,
    )

    secret_reveal = ReceiveSecretReveal(
        secret=UNIT_SECRET,
        sender=channel1.partner_state.address,
    )

    message_identifier = message_identifier_from_prng(
        deepcopy(pseudo_random_generator))

    iteration = initiator.handle_secretreveal(
        initiator_state=manager_state.initiator,
        state_change=secret_reveal,
        channel_state=channel1,
        pseudo_random_generator=pseudo_random_generator,
    )

    assert events.must_contain_entry(
        iteration.events, SendBalanceProof, {
            'message_identifier':
            message_identifier,
            'payment_identifier':
            manager_state.initiator.transfer_description.payment_identifier,
        })
Exemplo n.º 21
0
def handle_secretreveal(
    target_state,
    state_change,
    channel_state,
    pseudo_random_generator,
):
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash

    if valid_secret:
        channel.register_secret(
            channel_state,
            state_change.secret,
            state_change.secrethash,
        )

        transfer = target_state.transfer
        route = target_state.route

        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        target_state.state = 'reveal_secret'
        target_state.secret = state_change.secret
        recipient = route.node_address
        queue_name = 'global'
        reveal = SendRevealSecret(
            recipient,
            queue_name,
            message_identifier,
            target_state.secret,
            transfer.token,
        )

        iteration = TransitionResult(target_state, [reveal])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 22
0
def events_for_unlock_lock(
    initiator_state: InitiatorTransferState,
    channel_state: NettingChannelState,
    secret: Secret,
    secrethash: SecretHash,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> List[Event]:
    """ Unlocks the lock offchain, and emits the events for the successful payment. """
    # next hop learned the secret, unlock the token locally and send the
    # lock claim message to next hop
    transfer_description = initiator_state.transfer_description

    message_identifier = message_identifier_from_prng(pseudo_random_generator)
    unlock_lock = channel.send_unlock(
        channel_state=channel_state,
        message_identifier=message_identifier,
        payment_identifier=transfer_description.payment_identifier,
        secret=secret,
        secrethash=secrethash,
        block_number=block_number,
    )

    payment_sent_success = EventPaymentSentSuccess(
        token_network_registry_address=channel_state.
        token_network_registry_address,
        token_network_address=channel_state.token_network_address,
        identifier=transfer_description.payment_identifier,
        amount=transfer_description.amount,
        target=transfer_description.target,
        secret=secret,
        route=initiator_state.route.route,
    )

    unlock_success = EventUnlockSuccess(
        transfer_description.payment_identifier,
        transfer_description.secrethash)

    return [unlock_lock, payment_sent_success, unlock_success]
Exemplo n.º 23
0
def handle_secretrequest(
    initiator_state: InitiatorTransferState,
    state_change: ReceiveSecretRequest,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
) -> TransitionResult[InitiatorTransferState]:

    is_message_from_target = (
        state_change.sender == initiator_state.transfer_description.target
        and state_change.secrethash
        == initiator_state.transfer_description.secrethash
        and state_change.payment_identifier
        == initiator_state.transfer_description.payment_identifier)

    lock = channel.get_lock(channel_state.our_state,
                            initiator_state.transfer_description.secrethash)

    # This should not ever happen. This task clears itself when the lock is
    # removed.
    assert lock is not None, "channel is does not have the transfer's lock"

    already_received_secret_request = initiator_state.received_secret_request

    # transfer_description.amount is the actual payment amount without fees.
    # For the transfer to be valid and the unlock allowed the target must
    # receive at least that amount.
    is_valid_secretrequest = (
        state_change.amount >= initiator_state.transfer_description.amount
        and state_change.expiration == lock.expiration
        and initiator_state.transfer_description.secret != ABSENT_SECRET)

    if already_received_secret_request and is_message_from_target:
        # A secret request was received earlier, all subsequent are ignored
        # as it might be an attack
        iteration = TransitionResult(initiator_state, list())

    elif is_valid_secretrequest and is_message_from_target:
        # Reveal the secret to the target node and wait for its confirmation.
        # At this point the transfer is not cancellable anymore as either the lock
        # timeouts or a secret reveal is received.
        #
        # Note: The target might be the first hop
        #
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        transfer_description = initiator_state.transfer_description
        recipient = transfer_description.target
        revealsecret = SendSecretReveal(
            recipient=Address(recipient),
            message_identifier=message_identifier,
            secret=transfer_description.secret,
            canonical_identifier=CANONICAL_IDENTIFIER_GLOBAL_QUEUE,
        )

        initiator_state.transfer_state = "transfer_secret_revealed"
        initiator_state.received_secret_request = True
        iteration = TransitionResult(initiator_state, [revealsecret])

    elif not is_valid_secretrequest and is_message_from_target:
        initiator_state.received_secret_request = True
        invalid_request = EventInvalidSecretRequest(
            payment_identifier=state_change.payment_identifier,
            intended_amount=initiator_state.transfer_description.amount,
            actual_amount=state_change.amount,
        )
        iteration = TransitionResult(initiator_state, [invalid_request])

    else:
        iteration = TransitionResult(initiator_state, list())

    return iteration
Exemplo n.º 24
0
def try_new_route(
    channelidentifiers_to_channels: Dict[ChannelID, NettingChannelState],
    nodeaddresses_to_networkstates: NodeNetworkStateMap,
    candidate_route_states: List[RouteState],
    transfer_description: TransferDescriptionWithSecretState,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[Optional[InitiatorTransferState]]:

    initiator_state = None
    events: List[Event] = list()
    route_fee_exceeds_max = False

    channel_state = None
    route_state = None

    reachable_route_states = routes.filter_reachable_routes(
        candidate_route_states, nodeaddresses_to_networkstates)

    for reachable_route_state in reachable_route_states:
        forward_channel_id = reachable_route_state.forward_channel_id

        candidate_channel_state = forward_channel_id and channelidentifiers_to_channels.get(
            forward_channel_id)

        assert isinstance(candidate_channel_state, NettingChannelState)

        amount_with_fee = calculate_safe_amount_with_fee(
            payment_amount=transfer_description.amount,
            estimated_fee=reachable_route_state.estimated_fee,
        )
        # https://github.com/raiden-network/raiden/issues/4751
        # If the transfer amount + fees exceeds a percentage of the
        # initial amount then don't use this route
        max_amount_limit = transfer_description.amount + int(
            transfer_description.amount * MAX_MEDIATION_FEE_PERC)
        if amount_with_fee > max_amount_limit:
            route_fee_exceeds_max = True
            continue

        is_channel_usable = channel.is_channel_usable_for_new_transfer(
            channel_state=candidate_channel_state,
            transfer_amount=amount_with_fee,
            lock_timeout=transfer_description.lock_timeout,
        )
        if is_channel_usable:
            channel_state = candidate_channel_state
            route_state = reachable_route_state
            break

    if route_state is None:
        if not reachable_route_states:
            reason = "there is no route available"
        else:
            reason = "none of the available routes could be used"

        if route_fee_exceeds_max:
            reason += " and at least one of them exceeded the maximum fee limit"

        transfer_failed = EventPaymentSentFailed(
            token_network_registry_address=transfer_description.
            token_network_registry_address,
            token_network_address=transfer_description.token_network_address,
            identifier=transfer_description.payment_identifier,
            target=transfer_description.target,
            reason=reason,
        )
        events.append(transfer_failed)

        initiator_state = None

    else:
        assert channel_state is not None

        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        lockedtransfer_event = send_lockedtransfer(
            transfer_description=transfer_description,
            channel_state=channel_state,
            message_identifier=message_identifier,
            block_number=block_number,
            route_state=route_state,
            route_states=reachable_route_states,
        )
        assert lockedtransfer_event

        initiator_state = InitiatorTransferState(
            route=route_state,
            transfer_description=transfer_description,
            channel_identifier=channel_state.identifier,
            transfer=lockedtransfer_event.transfer,
        )
        events.append(lockedtransfer_event)

    return TransitionResult(initiator_state, events)
def test_regression_mediator_task_no_routes():
    """ The mediator must only be cleared after the waiting transfer's lock has
    been handled.

    If a node receives a transfer to mediate, but there is no route available
    (because there is no sufficient capacity or the partner nodes are offline),
    and a refund is not possible, the mediator task must not be cleared,
    otherwise followup remove expired lock messages wont be processed and the
    nodes will get out of sync.
    """
    pseudo_random_generator = random.Random()

    channels = make_channel_set([
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=0),
            partner_state=NettingChannelEndStateProperties(
                balance=10,
                address=HOP2,
                privatekey=HOP2_KEY,
            ),
        ),
    ])

    payer_transfer = factories.make_signed_transfer_for(
        channels[0],
        factories.LockedTransferSignedStateProperties(
            sender=HOP2,
            pkey=HOP2_KEY,
            transfer=factories.LockedTransferProperties(expiration=30),
        ))

    init_state_change = ActionInitMediator(
        channels.get_routes(),
        channels.get_route(0),
        payer_transfer,
    )
    init_iteration = mediator.state_transition(
        mediator_state=None,
        state_change=init_state_change,
        channelidentifiers_to_channels=channels.channel_map,
        nodeaddresses_to_networkstates=channels.nodeaddresses_to_networkstates,
        pseudo_random_generator=pseudo_random_generator,
        block_number=5,
        block_hash=factories.make_block_hash(),
    )

    msg = 'The task must not be cleared, even if there is no route to forward the transfer'
    assert init_iteration.new_state is not None, msg
    assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer
    assert search_for_item(init_iteration.events, SendLockedTransfer,
                           {}) is None
    assert search_for_item(init_iteration.events, SendRefundTransfer,
                           {}) is None

    secrethash = UNIT_SECRETHASH
    lock = channels[0].partner_state.secrethashes_to_lockedlocks[secrethash]

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=channels[0].partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=channels[0].chain_id,
        token_network_identifier=channels[0].token_network_identifier,
        channel_identifier=channels[0].identifier,
        recipient=channels[0].our_state.address,
    )
    assert send_lock_expired
    lock_expired_message = message_from_sendevent(send_lock_expired, HOP1)
    lock_expired_message.sign(LocalSigner(channels.partner_privatekeys[0]))
    balance_proof = balanceproof_from_envelope(lock_expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)

    # Regression: The mediator must still be able to process the block which
    # expires the lock
    expired_block_number = channel.get_sender_expiration_threshold(lock)
    block_hash = factories.make_block_hash()
    expire_block_iteration = mediator.state_transition(
        mediator_state=init_iteration.new_state,
        state_change=Block(
            block_number=expired_block_number,
            gas_limit=0,
            block_hash=block_hash,
        ),
        channelidentifiers_to_channels=channels.channel_map,
        nodeaddresses_to_networkstates=channels.nodeaddresses_to_networkstates,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
        block_hash=block_hash,
    )
    assert expire_block_iteration.new_state is not None

    receive_expired_iteration = mediator.state_transition(
        mediator_state=expire_block_iteration.new_state,
        state_change=ReceiveLockExpired(
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channelidentifiers_to_channels=channels.channel_map,
        nodeaddresses_to_networkstates=channels.nodeaddresses_to_networkstates,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
        block_hash=block_hash,
    )

    msg = 'The only used channel had the lock cleared, the task must be cleared'
    assert receive_expired_iteration.new_state is None, msg
    assert secrethash not in channels[
        0].partner_state.secrethashes_to_lockedlocks
Exemplo n.º 26
0
def handle_inittarget(
    state_change: ActionInitTarget,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
    block_number: typing.BlockNumber,
):
    """ Handles an ActionInitTarget state change. """
    transfer = state_change.transfer
    route = state_change.route

    target_state = TargetTransferState(
        route,
        transfer,
    )

    assert channel_state.identifier == transfer.balance_proof.channel_identifier
    is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer(
        channel_state,
        transfer,
    )

    safe_to_wait, unsafe_msg = is_safe_to_wait(
        transfer.lock.expiration,
        channel_state.reveal_timeout,
        block_number,
    )

    # if there is not enough time to safely unlock the token on-chain
    # silently let the transfer expire.
    if is_valid and safe_to_wait:
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        recipient = transfer.initiator
        secret_request = SendSecretRequest(
            recipient=recipient,
            channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
            message_identifier=message_identifier,
            payment_identifier=transfer.payment_identifier,
            amount=transfer.lock.amount,
            expiration=transfer.lock.expiration,
            secrethash=transfer.lock.secrethash,
        )

        channel_events.append(secret_request)
        iteration = TransitionResult(target_state, channel_events)
    else:
        if not is_valid:
            failure_reason = errormsg
        elif not safe_to_wait:
            failure_reason = unsafe_msg

        unlock_failed = EventUnlockClaimFailed(
            identifier=transfer.payment_identifier,
            secrethash=transfer.lock.secrethash,
            reason=failure_reason,
        )

        channel_events.append(unlock_failed)
        iteration = TransitionResult(target_state, channel_events)

    return iteration
Exemplo n.º 27
0
def make_transfers_pair(privatekeys, amount, block_number):
    transfers_pair = list()
    channel_map = dict()
    pseudo_random_generator = random.Random()

    addresses = list()
    for pkey in privatekeys:
        pubkey = pkey.public_key.format(compressed=False)
        address = publickey_to_address(pubkey)
        addresses.append(address)

    key_address = list(zip(privatekeys, addresses))

    deposit_amount = amount * 5
    channels_state = {
        address: make_channel(
            our_address=HOP1,
            our_balance=deposit_amount,
            partner_balance=deposit_amount,
            partner_address=address,
            token_address=UNIT_TOKEN_ADDRESS,
            token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS,
        )
        for address in addresses
    }

    lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2
    for (payer_key,
         payer_address), payee_address in zip(key_address[:-1], addresses[1:]):
        pay_channel = channels_state[payee_address]
        receive_channel = channels_state[payer_address]

        received_transfer = make_signed_transfer(
            amount=amount,
            initiator=UNIT_TRANSFER_INITIATOR,
            target=UNIT_TRANSFER_TARGET,
            expiration=lock_expiration,
            secret=UNIT_SECRET,
            payment_identifier=UNIT_TRANSFER_IDENTIFIER,
            channel_identifier=receive_channel.identifier,
            pkey=payer_key,
            sender=payer_address,
        )

        is_valid, _, msg = channel.handle_receive_lockedtransfer(
            receive_channel,
            received_transfer,
        )
        assert is_valid, msg

        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        lockedtransfer_event = channel.send_lockedtransfer(
            channel_state=pay_channel,
            initiator=UNIT_TRANSFER_INITIATOR,
            target=UNIT_TRANSFER_TARGET,
            amount=amount,
            message_identifier=message_identifier,
            payment_identifier=UNIT_TRANSFER_IDENTIFIER,
            expiration=lock_expiration,
            secrethash=UNIT_SECRETHASH,
        )
        assert lockedtransfer_event
        lock_timeout = lock_expiration - block_number
        assert mediator.is_channel_usable(
            candidate_channel_state=pay_channel,
            transfer_amount=amount,
            lock_timeout=lock_timeout,
        )
        sent_transfer = lockedtransfer_event.transfer

        pair = MediationPairState(
            received_transfer,
            lockedtransfer_event.recipient,
            sent_transfer,
        )
        transfers_pair.append(pair)

        channel_map[receive_channel.identifier] = receive_channel
        channel_map[pay_channel.identifier] = pay_channel

        assert channel.is_lock_locked(receive_channel.partner_state,
                                      UNIT_SECRETHASH)
        assert channel.is_lock_locked(pay_channel.our_state, UNIT_SECRETHASH)

    return channel_map, transfers_pair
Exemplo n.º 28
0
def make_transfers_pair(
        number_of_channels: int,
        amount: int = UNIT_TRANSFER_AMOUNT,
        block_number: int = 5,
) -> MediatorTransfersPair:

    deposit = 5 * amount
    defaults = create_properties(NettingChannelStateProperties(
        our_state=NettingChannelEndStateProperties(balance=deposit),
        partner_state=NettingChannelEndStateProperties(balance=deposit),
        open_transaction=TransactionExecutionStatusProperties(finished_block_number=10),
    ))
    properties_list = [
        NettingChannelStateProperties(
            identifier=i,
            our_state=NettingChannelEndStateProperties(
                address=ChannelSet.ADDRESSES[0],
                privatekey=ChannelSet.PKEYS[0],
            ),
            partner_state=NettingChannelEndStateProperties(
                address=ChannelSet.ADDRESSES[i + 1],
                privatekey=ChannelSet.PKEYS[i + 1],
            ),
        )
        for i in range(number_of_channels)
    ]
    channels = make_channel_set(properties_list, defaults)

    lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2
    pseudo_random_generator = random.Random()
    transfers_pairs = list()

    for payer_index in range(number_of_channels - 1):
        payee_index = payer_index + 1

        receiver_channel = channels[payer_index]
        received_transfer = make_signed_transfer(
            amount=amount,
            initiator=UNIT_TRANSFER_INITIATOR,
            target=UNIT_TRANSFER_TARGET,
            expiration=lock_expiration,
            secret=UNIT_SECRET,
            payment_identifier=UNIT_TRANSFER_IDENTIFIER,
            channel_identifier=receiver_channel.identifier,
            pkey=channels.partner_privatekeys[payer_index],
            sender=channels.partner_address(payer_index),
        )

        is_valid, _, msg = channel.handle_receive_lockedtransfer(
            receiver_channel,
            received_transfer,
        )
        assert is_valid, msg

        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        lockedtransfer_event = channel.send_lockedtransfer(
            channel_state=channels[payee_index],
            initiator=UNIT_TRANSFER_INITIATOR,
            target=UNIT_TRANSFER_TARGET,
            amount=amount,
            message_identifier=message_identifier,
            payment_identifier=UNIT_TRANSFER_IDENTIFIER,
            expiration=lock_expiration,
            secrethash=UNIT_SECRETHASH,
        )
        assert lockedtransfer_event

        lock_timeout = lock_expiration - block_number
        assert mediator.is_channel_usable(
            candidate_channel_state=channels[payee_index],
            transfer_amount=amount,
            lock_timeout=lock_timeout,
        )
        sent_transfer = lockedtransfer_event.transfer

        pair = MediationPairState(
            received_transfer,
            lockedtransfer_event.recipient,
            sent_transfer,
        )
        transfers_pairs.append(pair)

    return MediatorTransfersPair(channels, transfers_pairs, amount, block_number)
Exemplo n.º 29
0
def handle_inittarget(
        state_change: ActionInitTarget,
        channel_state: NettingChannelState,
        pseudo_random_generator: random.Random,
        block_number: BlockNumber,
):
    """ Handles an ActionInitTarget state change. """
    transfer = state_change.transfer
    route = state_change.route

    assert channel_state.identifier == transfer.balance_proof.channel_identifier
    is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer(
        channel_state,
        transfer,
    )

    if is_valid:
        # A valid balance proof does not mean the payment itself is still valid.
        # e.g. the lock may be near expiration or have expired. This is fine. The
        # message with an unusable lock must be handled to properly synchronize the
        # local view of the partner's channel state, allowing the next balance
        # proofs to be handled. This however, must only be done once, which is
        # enforced by the nonce increasing sequentially, which is verified by
        # the handler handle_receive_lockedtransfer.
        target_state = TargetTransferState(route, transfer)

        safe_to_wait, _ = is_safe_to_wait(
            transfer.lock.expiration,
            channel_state.reveal_timeout,
            block_number,
        )

        # If there is not enough time to safely unlock the lock on-chain
        # silently let the transfer expire. The target task must be created to
        # handle the ReceiveLockExpired state change, which will clear the
        # expired lock.
        if safe_to_wait:
            message_identifier = message_identifier_from_prng(pseudo_random_generator)
            recipient = transfer.initiator
            secret_request = SendSecretRequest(
                recipient=Address(recipient),
                channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
                message_identifier=message_identifier,
                payment_identifier=transfer.payment_identifier,
                amount=transfer.lock.amount,
                expiration=transfer.lock.expiration,
                secrethash=transfer.lock.secrethash,
            )
            channel_events.append(secret_request)

        iteration = TransitionResult(target_state, channel_events)
    else:
        # 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.
        unlock_failed = EventUnlockClaimFailed(
            identifier=transfer.payment_identifier,
            secrethash=transfer.lock.secrethash,
            reason=errormsg,
        )
        channel_events.append(unlock_failed)
        iteration = TransitionResult(None, channel_events)

    return iteration
Exemplo n.º 30
0
def handle_offchain_secretreveal_light(
    target_state: TargetTransferState,
    state_change: ReceiveSecretRevealLight,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[TargetTransferState]:
    """ Validates and handles a ReceiveSecretReveal state change. """
    valid_secret = is_valid_secret_reveal(
        state_change=state_change,
        transfer_secrethash=target_state.transfer.lock.secrethash,
        secret=state_change.secret,
    )
    has_transfer_expired = channel.is_transfer_expired(
        transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number
    )

    if valid_secret and not has_transfer_expired:
        # TODO mmarcosmartinez7 this cannot be done without LC interaction
        # channel.register_offchain_secret(
        #     channel_state=channel_state,
        #     secret=state_change.secret,
        #     secrethash=state_change.secrethash,
        # )

        route = target_state.route
        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL
        target_state.secret = state_change.secret
        recipient = route.node_address

        # Store reveal secret 7, create reveal secret 9 and store it for LC signing.

        received_reveal_secret = state_change.secret_reveal_message
        reveal_secret_to_send_event = SendSecretReveal(
            recipient=recipient,
            channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
            message_identifier=message_identifier,
            secret=target_state.secret,
        )
        reveal_secret_to_send_msg = message_from_sendevent(reveal_secret_to_send_event)

        store_received_reveal = StoreMessageEvent(
            received_reveal_secret.message_identifier,
            target_state.transfer.payment_identifier,
            7,
            received_reveal_secret,
            True
        )

        store_reveal_to_send = StoreMessageEvent(
            message_identifier,
            target_state.transfer.payment_identifier,
            9,
            reveal_secret_to_send_msg,
            False
        )

        iteration = TransitionResult(target_state, [store_received_reveal, store_reveal_to_send])

    else:
        # TODO: event for byzantine behavior
        iteration = TransitionResult(target_state, list())

    return iteration
Exemplo n.º 31
0
def next_transfer_pair(
        payer_transfer: LockedTransferSignedState,
        available_routes: List['RouteState'],
        channelidentifiers_to_channels: Dict,
        pseudo_random_generator: random.Random,
        timeout_blocks: int,
        block_number: int,
):
    """ Given a payer transfer tries a new route to proceed with the mediation.
    Args:
        payer_transfer: The transfer received from the payer_channel.
        routes: Current available routes that may be used, it's assumed that
            the routes list is ordered from best to worst.
        timeout_blocks: Base number of available blocks used to compute
            the lock timeout.
        block_number: The current block number.
    """
    assert timeout_blocks > 0
    assert timeout_blocks <= payer_transfer.lock.expiration - block_number

    transfer_pair = None
    mediated_events = list()

    payee_channel = next_channel_from_routes(
        available_routes,
        channelidentifiers_to_channels,
        payer_transfer.lock.amount,
        timeout_blocks,
    )

    if payee_channel:
        assert payee_channel.reveal_timeout < timeout_blocks
        assert payee_channel.token_address == payer_transfer.token

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

        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        lockedtransfer_event = channel.send_lockedtransfer(
            payee_channel,
            payer_transfer.initiator,
            payer_transfer.target,
            payer_transfer.lock.amount,
            message_identifier,
            payer_transfer.payment_identifier,
            lock_expiration,
            payer_transfer.lock.secrethash,
        )
        assert lockedtransfer_event

        transfer_pair = MediationPairState(
            payer_transfer,
            payee_channel.partner_state.address,
            lockedtransfer_event.transfer,
        )

        mediated_events = [lockedtransfer_event]

    return (
        transfer_pair,
        mediated_events,
    )
def test_regression_mediator_task_no_routes():
    """ The mediator must only be cleared after the waiting transfer's lock has
    been handled.

    If a node receives a transfer to mediate, but there is no route available
    (because there is no sufficient capacity or the partner nodes are offline),
    and a refund is not possible, the mediator task must not be cleared,
    otherwise followup remove expired lock messages wont be processed and the
    nodes will get out of sync.
    """
    pseudo_random_generator = random.Random()

    channels = make_channel_set([
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=0),
            partner_state=NettingChannelEndStateProperties(
                balance=10,
                address=HOP2,
                privatekey=HOP2_KEY,
            ),
        ),
    ])

    payer_transfer = factories.make_signed_transfer_for(
        channels[0],
        factories.LockedTransferSignedStateProperties(
            sender=HOP2,
            pkey=HOP2_KEY,
            transfer=factories.LockedTransferProperties(expiration=30),
        ))

    init_state_change = ActionInitMediator(
        channels.get_routes(),
        channels.get_route(0),
        payer_transfer,
    )
    init_iteration = mediator.state_transition(
        mediator_state=None,
        state_change=init_state_change,
        channelidentifiers_to_channels=channels.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=5,
    )

    msg = 'The task must not be cleared, even if there is no route to forward the transfer'
    assert init_iteration.new_state is not None, msg
    assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer
    assert must_contain_entry(init_iteration.events, SendLockedTransfer, {}) is None
    assert must_contain_entry(init_iteration.events, SendRefundTransfer, {}) is None

    secrethash = UNIT_SECRETHASH
    lock = channels[0].partner_state.secrethashes_to_lockedlocks[secrethash]

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=channels[0].partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=channels[0].chain_id,
        token_network_identifier=channels[0].token_network_identifier,
        channel_identifier=channels[0].identifier,
        recipient=channels[0].our_state.address,
    )
    assert send_lock_expired
    lock_expired_message = message_from_sendevent(send_lock_expired, HOP1)
    lock_expired_message.sign(channels.partner_privatekeys[0])
    balance_proof = balanceproof_from_envelope(lock_expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)

    # Regression: The mediator must still be able to process the block which
    # expires the lock
    expired_block_number = channel.get_sender_expiration_threshold(lock)
    expire_block_iteration = mediator.state_transition(
        mediator_state=init_iteration.new_state,
        state_change=Block(
            block_number=expired_block_number,
            gas_limit=0,
            block_hash=None,
        ),
        channelidentifiers_to_channels=channels.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
    )
    assert expire_block_iteration.new_state is not None

    receive_expired_iteration = mediator.state_transition(
        mediator_state=expire_block_iteration.new_state,
        state_change=ReceiveLockExpired(
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channelidentifiers_to_channels=channels.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
    )

    msg = 'The only used channel had the lock cleared, the task must be cleared'
    assert receive_expired_iteration.new_state is None, msg
    assert secrethash not in channels[0].partner_state.secrethashes_to_lockedlocks
def test_regression_mediator_task_no_routes():
    """ The mediator must only be cleared after the waiting transfer's lock has
    been handled.

    If a node receives a transfer to mediate, but there is no route available
    (because there is no sufficient capacity or the partner nodes are offline),
    and a refund is not possible, the mediator task must not be cleared,
    otherwise followup remove expired lock messages wont be processed and the
    nodes will get out of sync.
    """

    amount = 10
    block_number = 5
    target = HOP2
    expiration = 30
    pseudo_random_generator = random.Random()

    payer_channel = factories.make_channel(
        partner_balance=amount,
        partner_address=HOP2,
        token_address=UNIT_TOKEN_ADDRESS,
    )
    payer_route = factories.route_from_channel(payer_channel)

    payer_transfer = factories.make_signed_transfer_for(
        payer_channel,
        amount,
        HOP1,
        target,
        expiration,
        UNIT_SECRET,
        pkey=HOP2_KEY,
        sender=HOP2,
    )

    available_routes = []
    channel_map = {
        payer_channel.identifier: payer_channel,
    }

    init_state_change = ActionInitMediator(
        available_routes,
        payer_route,
        payer_transfer,
    )
    initial_state = None
    init_iteration = mediator.state_transition(
        initial_state,
        init_state_change,
        channel_map,
        pseudo_random_generator,
        block_number,
    )
    msg = 'The task must not be cleared, even if there is no route to forward the transfer'
    assert init_iteration.new_state is not None, msg
    assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer
    assert must_contain_entry(init_iteration.events, SendLockedTransfer,
                              {}) is None
    assert must_contain_entry(init_iteration.events, SendRefundTransfer,
                              {}) is None

    secrethash = UNIT_SECRETHASH
    lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash]

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=payer_channel.partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=payer_channel.chain_id,
        token_network_identifier=payer_channel.token_network_identifier,
        channel_identifier=payer_channel.identifier,
        recipient=payer_channel.our_state.address,
    )
    assert send_lock_expired
    lock_expired_message = message_from_sendevent(send_lock_expired, HOP1)
    lock_expired_message.sign(HOP2_KEY)
    balance_proof = balanceproof_from_envelope(lock_expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)
    expired_block_number = lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2

    # Regression: The mediator must still be able to process the block which
    # expires the lock
    expire_block_iteration = mediator.state_transition(
        init_iteration.new_state,
        Block(
            block_number=expired_block_number,
            gas_limit=0,
            block_hash=None,
        ),
        channel_map,
        pseudo_random_generator,
        expired_block_number,
    )
    assert expire_block_iteration.new_state is not None

    receive_expired_iteration = mediator.state_transition(
        expire_block_iteration.new_state,
        ReceiveLockExpired(
            sender=payer_channel.partner_state.address,
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channel_map,
        pseudo_random_generator,
        expired_block_number,
    )

    msg = 'The only used channel had the lock cleared, the task must be cleared'
    assert receive_expired_iteration.new_state is None, msg
    assert secrethash not in payer_channel.partner_state.secrethashes_to_lockedlocks
Exemplo n.º 34
0
def handle_secretrequest(
    initiator_state: InitiatorTransferState,
    state_change: ReceiveSecretRequest,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
) -> TransitionResult:

    is_message_from_target = (
        state_change.sender == initiator_state.transfer_description.target
        and state_change.secrethash
        == initiator_state.transfer_description.secrethash
        and state_change.payment_identifier
        == initiator_state.transfer_description.payment_identifier)

    lock = channel.get_lock(
        channel_state.our_state,
        initiator_state.transfer_description.secrethash,
    )

    already_received_secret_request = initiator_state.received_secret_request

    is_valid_secretrequest = (state_change.amount
                              == initiator_state.transfer_description.amount
                              and state_change.expiration == lock.expiration)

    if already_received_secret_request and is_message_from_target:
        # A secret request was received earlier, all subsequent are ignored
        # as it might be an attack
        iteration = TransitionResult(initiator_state, list())

    elif is_valid_secretrequest and is_message_from_target:
        # Reveal the secret to the target node and wait for its confirmation.
        # At this point the transfer is not cancellable anymore as either the lock
        # timeouts or a secret reveal is received.
        #
        # Note: The target might be the first hop
        #
        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        transfer_description = initiator_state.transfer_description
        recipient = transfer_description.target
        revealsecret = SendSecretReveal(
            recipient=recipient,
            channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
            message_identifier=message_identifier,
            secret=transfer_description.secret,
        )

        initiator_state.revealsecret = revealsecret
        initiator_state.received_secret_request = True
        iteration = TransitionResult(initiator_state, [revealsecret])

    elif not is_valid_secretrequest and is_message_from_target:
        cancel = EventPaymentSentFailed(
            payment_network_identifier=channel_state.
            payment_network_identifier,
            token_network_identifier=channel_state.token_network_identifier,
            identifier=initiator_state.transfer_description.payment_identifier,
            target=initiator_state.transfer_description.target,
            reason='bad secret request message from target',
        )

        initiator_state.received_secret_request = True
        iteration = TransitionResult(initiator_state, [cancel])

    else:
        iteration = TransitionResult(initiator_state, list())

    return iteration
Exemplo n.º 35
0
def try_new_route(
    addresses_to_channel: Dict[Tuple[TokenNetworkAddress, Address],
                               NettingChannelState],
    nodeaddresses_to_networkstates: NodeNetworkStateMap,
    candidate_route_states: List[RouteState],
    transfer_description: TransferDescriptionWithSecretState,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
) -> TransitionResult[Optional[InitiatorTransferState]]:

    initiator_state = None
    events: List[Event] = list()
    route_fee_exceeds_max = False

    channel_state = None
    route_state = None

    reachable_route_states = routes.filter_reachable_routes(
        candidate_route_states, nodeaddresses_to_networkstates)

    for reachable_route_state in reachable_route_states:
        candidate_channel_state = addresses_to_channel[(
            transfer_description.token_network_address,
            reachable_route_state.route[1])]

        amount_with_fee = calculate_safe_amount_with_fee(
            payment_amount=transfer_description.amount,
            estimated_fee=reachable_route_state.estimated_fee,
        )
        # https://github.com/raiden-network/raiden/issues/4751
        # If the transfer amount + fees exceeds a percentage of the
        # initial amount then don't use this route
        max_amount_limit = transfer_description.amount + int(
            transfer_description.amount * MAX_MEDIATION_FEE_PERC)
        if amount_with_fee > max_amount_limit:
            route_fee_exceeds_max = True
            continue

        channel_usability_state = channel.is_channel_usable_for_new_transfer(
            channel_state=candidate_channel_state,
            transfer_amount=amount_with_fee,
            lock_timeout=transfer_description.lock_timeout,
        )
        if channel_usability_state is channel.ChannelUsability.USABLE:
            channel_state = candidate_channel_state
            route_state = reachable_route_state
            break

    if route_state is None:
        if not reachable_route_states:
            reason = "there is no route available"
        else:
            reason = "none of the available routes could be used"

        if route_fee_exceeds_max:
            reason += (
                " and at least one of them exceeded the maximum fee limit "
                "(see https://docs.raiden.network/using-raiden/mediation-fees#frequently-asked-questions)"  # noqa
            )

        transfer_failed = EventPaymentSentFailed(
            token_network_registry_address=transfer_description.
            token_network_registry_address,
            token_network_address=transfer_description.token_network_address,
            identifier=transfer_description.payment_identifier,
            target=transfer_description.target,
            reason=reason,
        )
        events.append(transfer_failed)

        initiator_state = None

    else:
        assert channel_state is not None, "We must have a channel_state if we have a route_state"

        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        lockedtransfer_event = send_lockedtransfer(
            transfer_description=transfer_description,
            channel_state=channel_state,
            message_identifier=message_identifier,
            block_number=block_number,
            route_state=route_state,
            route_states=reachable_route_states,
        )

        initiator_state = InitiatorTransferState(
            route=route_state,
            transfer_description=transfer_description,
            channel_identifier=channel_state.identifier,
            transfer=lockedtransfer_event.transfer,
        )
        events.append(lockedtransfer_event)

    return TransitionResult(initiator_state, events)
Exemplo n.º 36
0
def handle_secretrequest(
        initiator_state: InitiatorTransferState,
        state_change: ReceiveSecretRequest,
        channel_state: NettingChannelState,
        pseudo_random_generator: random.Random,
) -> TransitionResult:

    request_from_target = (
        state_change.sender == initiator_state.transfer_description.target and
        state_change.secrethash == initiator_state.transfer_description.secrethash
    )

    is_valid_payment_id = (
        state_change.payment_identifier == initiator_state.transfer_description.payment_identifier
    )

    valid_secretrequest = (
        request_from_target and
        is_valid_payment_id and
        state_change.amount == initiator_state.transfer_description.amount
    )

    invalid_secretrequest = request_from_target and (
        is_valid_payment_id or
        state_change.amount != initiator_state.transfer_description.amount
    )

    if valid_secretrequest:
        # Reveal the secret to the target node and wait for its confirmation.
        # At this point the transfer is not cancellable anymore as either the lock
        # timeouts or a secret reveal is received.
        #
        # Note: The target might be the first hop
        #
        message_identifier = message_identifier_from_prng(pseudo_random_generator)
        transfer_description = initiator_state.transfer_description
        recipient = transfer_description.target
        revealsecret = SendRevealSecret(
            recipient=recipient,
            channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
            message_identifier=message_identifier,
            secret=transfer_description.secret,
        )

        initiator_state.revealsecret = revealsecret
        iteration = TransitionResult(initiator_state, [revealsecret])

    elif invalid_secretrequest:
        cancel = EventPaymentSentFailed(
            payment_network_identifier=channel_state.payment_network_identifier,
            token_network_identifier=channel_state.token_network_identifer,
            identifier=initiator_state.transfer_description.payment_identifier,
            target=initiator_state.transfer_description.target,
            reason='bad secret request message from target',
        )
        iteration = TransitionResult(None, [cancel])

    else:
        iteration = TransitionResult(initiator_state, list())

    return iteration
Exemplo n.º 37
0
def test_regression_mediator_task_no_routes():
    """ The mediator must only be cleared after the waiting transfer's lock has
    been handled.

    If a node receives a transfer to mediate, but there is no route available
    (because there is no sufficient capacity or the partner nodes are offline),
    and a refund is not possible, the mediator task must not be cleared,
    otherwise followup remove expired lock messages wont be processed and the
    nodes will get out of sync.
    """
    pseudo_random_generator = random.Random()

    channels = factories.make_channel_set([
        {
            'our_state': {
                'balance': 0
            },
            'partner_state': {
                'balance': 10,
                'address': HOP2
            },
            'open_transaction':
            factories.make_transaction_execution_status(
                finished_block_number=10, ),
        },
    ])

    payer_transfer = factories.make_default_signed_transfer_for(
        channels[0],
        initiator=HOP1,
        expiration=30,
        pkey=HOP2_KEY,
        sender=HOP2,
    )

    init_state_change = ActionInitMediator(
        channels.get_routes(),
        channels.get_route(0),
        payer_transfer,
    )
    init_iteration = mediator.state_transition(
        mediator_state=None,
        state_change=init_state_change,
        channelidentifiers_to_channels=channels.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=5,
    )

    msg = 'The task must not be cleared, even if there is no route to forward the transfer'
    assert init_iteration.new_state is not None, msg
    assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer
    assert must_contain_entry(init_iteration.events, SendLockedTransfer,
                              {}) is None
    assert must_contain_entry(init_iteration.events, SendRefundTransfer,
                              {}) is None

    secrethash = UNIT_SECRETHASH
    lock = channels[0].partner_state.secrethashes_to_lockedlocks[secrethash]

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=channels[0].partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=channels[0].chain_id,
        token_network_identifier=channels[0].token_network_identifier,
        channel_identifier=channels[0].identifier,
        recipient=channels[0].our_state.address,
    )
    assert send_lock_expired
    lock_expired_message = message_from_sendevent(send_lock_expired, HOP1)
    lock_expired_message.sign(HOP2_KEY)
    balance_proof = balanceproof_from_envelope(lock_expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)
    expired_block_number = lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2

    # Regression: The mediator must still be able to process the block which
    # expires the lock
    expire_block_iteration = mediator.state_transition(
        mediator_state=init_iteration.new_state,
        state_change=Block(
            block_number=expired_block_number,
            gas_limit=0,
            block_hash=None,
        ),
        channelidentifiers_to_channels=channels.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
    )
    assert expire_block_iteration.new_state is not None

    receive_expired_iteration = mediator.state_transition(
        mediator_state=expire_block_iteration.new_state,
        state_change=ReceiveLockExpired(
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channelidentifiers_to_channels=channels.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
    )

    msg = 'The only used channel had the lock cleared, the task must be cleared'
    assert receive_expired_iteration.new_state is None, msg
    assert secrethash not in channels[
        0].partner_state.secrethashes_to_lockedlocks
Exemplo n.º 38
0
def next_transfer_pair(
    payer_transfer: LockedTransferSignedState,
    available_routes: List['RouteState'],
    channelidentifiers_to_channels: Dict,
    pseudo_random_generator: random.Random,
    timeout_blocks: int,
    block_number: int,
):
    """ Given a payer transfer tries a new route to proceed with the mediation.
    Args:
        payer_transfer: The transfer received from the payer_channel.
        routes: Current available routes that may be used, it's assumed that
            the routes list is ordered from best to worst.
        timeout_blocks: Base number of available blocks used to compute
            the lock timeout.
        block_number: The current block number.
    """
    assert timeout_blocks > 0
    assert timeout_blocks <= payer_transfer.lock.expiration - block_number

    transfer_pair = None
    mediated_events = list()

    payee_channel = next_channel_from_routes(
        available_routes,
        channelidentifiers_to_channels,
        payer_transfer.lock.amount,
        timeout_blocks,
    )

    if payee_channel:
        assert payee_channel.reveal_timeout < timeout_blocks
        assert payee_channel.token_address == payer_transfer.token

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

        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        lockedtransfer_event = channel.send_lockedtransfer(
            payee_channel,
            payer_transfer.initiator,
            payer_transfer.target,
            payer_transfer.lock.amount,
            message_identifier,
            payer_transfer.payment_identifier,
            lock_expiration,
            payer_transfer.lock.secrethash,
        )
        assert lockedtransfer_event

        transfer_pair = MediationPairState(
            payer_transfer,
            payee_channel.partner_state.address,
            lockedtransfer_event.transfer,
        )

        mediated_events = [lockedtransfer_event]

    return (
        transfer_pair,
        mediated_events,
    )
Exemplo n.º 39
0
def test_regression_onchain_secret_reveal_must_update_channel_state():
    """ If a secret is learned off-chain and then on-chain, the state of the
    lock must be updated in the channel.
    """
    amount = 10
    block_number = 10
    pseudo_random_generator = random.Random()

    channel_map, transfers_pair = factories.make_transfers_pair(
        [HOP2_KEY, HOP3_KEY],
        amount,
        block_number,
    )

    mediator_state = MediatorTransferState(UNIT_SECRETHASH)
    mediator_state.transfers_pair = transfers_pair

    secret = UNIT_SECRET
    secrethash = UNIT_SECRETHASH
    payer_channelid = transfers_pair[
        0].payer_transfer.balance_proof.channel_identifier
    payee_channelid = transfers_pair[
        0].payee_transfer.balance_proof.channel_identifier
    payer_channel = channel_map[payer_channelid]
    payee_channel = channel_map[payee_channelid]
    lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash]

    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ReceiveSecretReveal(secret,
                                         payee_channel.partner_state.address),
        channelidentifiers_to_channels=channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=block_number,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_unlockedlocks

    secret_registry_address = factories.make_address()
    transaction_hash = factories.make_address()
    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ContractReceiveSecretReveal(
            transaction_hash,
            secret_registry_address,
            secrethash,
            secret,
            block_number,
        ),
        channelidentifiers_to_channels=channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=block_number,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=payer_channel.partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=payer_channel.chain_id,
        token_network_identifier=payer_channel.token_network_identifier,
        channel_identifier=payer_channel.identifier,
        recipient=payer_channel.our_state.address,
    )
    assert send_lock_expired
    lock_expired_message = message_from_sendevent(send_lock_expired, HOP1)
    lock_expired_message.sign(HOP2_KEY)
    balance_proof = balanceproof_from_envelope(lock_expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)
    expired_block_number = lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2
    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ReceiveLockExpired(
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channelidentifiers_to_channels=channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks
Exemplo n.º 40
0
def make_transfers_pair(number_of_channels: int,
                        amount: int = UNIT_TRANSFER_AMOUNT,
                        block_number: int = 5) -> MediatorTransfersPair:

    deposit = 5 * amount
    defaults = create_properties(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=deposit),
            partner_state=NettingChannelEndStateProperties(balance=deposit),
            open_transaction=TransactionExecutionStatusProperties(
                finished_block_number=10),
        ))
    properties_list = [
        NettingChannelStateProperties(
            canonical_identifier=make_canonical_identifier(
                channel_identifier=i),
            our_state=NettingChannelEndStateProperties(
                address=ChannelSet.ADDRESSES[0],
                privatekey=ChannelSet.PKEYS[0]),
            partner_state=NettingChannelEndStateProperties(
                address=ChannelSet.ADDRESSES[i + 1],
                privatekey=ChannelSet.PKEYS[i + 1]),
        ) for i in range(number_of_channels)
    ]
    channels = make_channel_set(properties_list, defaults)

    lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2
    pseudo_random_generator = random.Random()
    transfers_pairs = list()

    for payer_index in range(number_of_channels - 1):
        payee_index = payer_index + 1

        receiver_channel = channels[payer_index]
        received_transfer = create(
            LockedTransferSignedStateProperties(
                amount=amount,
                expiration=lock_expiration,
                payment_identifier=UNIT_TRANSFER_IDENTIFIER,
                canonical_identifier=receiver_channel.canonical_identifier,
                sender=channels.partner_address(payer_index),
                pkey=channels.partner_privatekeys[payer_index],
            ))

        is_valid, _, msg = channel.handle_receive_lockedtransfer(
            receiver_channel, received_transfer)
        assert is_valid, msg

        message_identifier = message_identifier_from_prng(
            pseudo_random_generator)
        lockedtransfer_event = channel.send_lockedtransfer(
            channel_state=channels[payee_index],
            initiator=UNIT_TRANSFER_INITIATOR,
            target=UNIT_TRANSFER_TARGET,
            amount=amount,
            message_identifier=message_identifier,
            payment_identifier=UNIT_TRANSFER_IDENTIFIER,
            expiration=lock_expiration,
            secrethash=UNIT_SECRETHASH,
        )
        assert lockedtransfer_event

        lock_timeout = lock_expiration - block_number
        assert mediator.is_channel_usable(
            candidate_channel_state=channels[payee_index],
            transfer_amount=amount,
            lock_timeout=lock_timeout,
        )
        sent_transfer = lockedtransfer_event.transfer

        pair = MediationPairState(received_transfer,
                                  lockedtransfer_event.recipient,
                                  sent_transfer)
        transfers_pairs.append(pair)

    return MediatorTransfersPair(
        channels=channels,
        transfers_pair=transfers_pairs,
        amount=amount,
        block_number=block_number,
        block_hash=make_block_hash(),
    )
Exemplo n.º 41
0
def test_initiator_handle_contract_receive_secret_reveal():
    """ Initiator must unlock off-chain if the secret is revealed on-chain and
    the channel is open.
    """
    amount = UNIT_TRANSFER_AMOUNT * 2
    block_number = 1
    pseudo_random_generator = random.Random()

    channel1 = factories.make_channel(
        our_balance=amount,
        token_address=UNIT_TOKEN_ADDRESS,
        token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS,
    )
    pseudo_random_generator = random.Random()

    channel_map = {
        channel1.identifier: channel1,
    }

    available_routes = [
        factories.route_from_channel(channel1),
    ]

    block_number = 10
    current_state = make_initiator_manager_state(
        routes=available_routes,
        transfer_description=factories.UNIT_TRANSFER_DESCRIPTION,
        channel_map=channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=block_number,
    )

    transfer = current_state.initiator.transfer

    assert transfer.lock.secrethash in channel1.our_state.secrethashes_to_lockedlocks

    state_change = ContractReceiveSecretReveal(
        transaction_hash=factories.make_transaction_hash(),
        secret_registry_address=factories.make_address(),
        secrethash=transfer.lock.secrethash,
        secret=UNIT_SECRET,
        block_number=transfer.lock.expiration,
    )

    message_identifier = message_identifier_from_prng(
        deepcopy(pseudo_random_generator))

    iteration = initiator_manager.handle_onchain_secretreveal(
        payment_state=current_state,
        state_change=state_change,
        channelidentifiers_to_channels=channel_map,
        pseudo_random_generator=pseudo_random_generator,
    )

    assert events.must_contain_entry(
        iteration.events, SendBalanceProof, {
            'message_identifier':
            message_identifier,
            'payment_identifier':
            current_state.initiator.transfer_description.payment_identifier,
        })
def test_regression_onchain_secret_reveal_must_update_channel_state():
    """ If a secret is learned off-chain and then on-chain, the state of the
    lock must be updated in the channel.
    """
    pseudo_random_generator = random.Random()

    setup = factories.make_transfers_pair(2, block_number=10)

    mediator_state = MediatorTransferState(
        secrethash=UNIT_SECRETHASH,
        routes=setup.channels.get_routes(),
    )
    mediator_state.transfers_pair = setup.transfers_pair

    secret = UNIT_SECRET
    secrethash = UNIT_SECRETHASH
    payer_channel = mediator.get_payer_channel(setup.channel_map,
                                               setup.transfers_pair[0])
    payee_channel = mediator.get_payee_channel(setup.channel_map,
                                               setup.transfers_pair[0])
    lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash]

    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ReceiveSecretReveal(secret,
                                         payee_channel.partner_state.address),
        channelidentifiers_to_channels=setup.channel_map,
        nodeaddresses_to_networkstates=setup.channels.
        nodeaddresses_to_networkstates,
        pseudo_random_generator=pseudo_random_generator,
        block_number=setup.block_number,
        block_hash=setup.block_hash,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_unlockedlocks

    secret_registry_address = factories.make_address()
    transaction_hash = factories.make_address()
    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ContractReceiveSecretReveal(
            transaction_hash=transaction_hash,
            secret_registry_address=secret_registry_address,
            secrethash=secrethash,
            secret=secret,
            block_number=setup.block_number,
            block_hash=setup.block_hash,
        ),
        channelidentifiers_to_channels=setup.channel_map,
        nodeaddresses_to_networkstates=setup.channels.
        nodeaddresses_to_networkstates,
        pseudo_random_generator=pseudo_random_generator,
        block_number=setup.block_number,
        block_hash=setup.block_hash,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=payer_channel.partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=payer_channel.chain_id,
        token_network_identifier=payer_channel.token_network_identifier,
        channel_identifier=payer_channel.identifier,
        recipient=payer_channel.our_state.address,
    )
    assert send_lock_expired
    expired_message = message_from_sendevent(send_lock_expired,
                                             setup.channels.our_address(0))
    expired_message.sign(LocalSigner(setup.channels.partner_privatekeys[0]))
    balance_proof = balanceproof_from_envelope(expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)
    expired_block_number = channel.get_sender_expiration_threshold(lock)
    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ReceiveLockExpired(
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channelidentifiers_to_channels=setup.channel_map,
        nodeaddresses_to_networkstates=setup.channels.
        nodeaddresses_to_networkstates,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
        block_hash=factories.make_block_hash(),
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks
def test_regression_onchain_secret_reveal_must_update_channel_state():
    """ If a secret is learned off-chain and then on-chain, the state of the
    lock must be updated in the channel.
    """
    pseudo_random_generator = random.Random()

    setup = factories.make_transfers_pair(2, block_number=10)

    mediator_state = MediatorTransferState(UNIT_SECRETHASH)
    mediator_state.transfers_pair = setup.transfers_pair

    secret = UNIT_SECRET
    secrethash = UNIT_SECRETHASH
    payer_channel = mediator.get_payer_channel(setup.channel_map, setup.transfers_pair[0])
    payee_channel = mediator.get_payee_channel(setup.channel_map, setup.transfers_pair[0])
    lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash]

    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ReceiveSecretReveal(secret, payee_channel.partner_state.address),
        channelidentifiers_to_channels=setup.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=setup.block_number,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_unlockedlocks

    secret_registry_address = factories.make_address()
    transaction_hash = factories.make_address()
    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ContractReceiveSecretReveal(
            transaction_hash,
            secret_registry_address,
            secrethash,
            secret,
            setup.block_number,
        ),
        channelidentifiers_to_channels=setup.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=setup.block_number,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks

    # Creates a transfer as it was from the *partner*
    send_lock_expired, _ = channel.create_sendexpiredlock(
        sender_end_state=payer_channel.partner_state,
        locked_lock=lock,
        pseudo_random_generator=pseudo_random_generator,
        chain_id=payer_channel.chain_id,
        token_network_identifier=payer_channel.token_network_identifier,
        channel_identifier=payer_channel.identifier,
        recipient=payer_channel.our_state.address,
    )
    assert send_lock_expired
    expired_message = message_from_sendevent(send_lock_expired, setup.channels.our_address(0))
    expired_message.sign(setup.channels.partner_privatekeys[0])
    balance_proof = balanceproof_from_envelope(expired_message)

    message_identifier = message_identifier_from_prng(pseudo_random_generator)
    expired_block_number = channel.get_sender_expiration_threshold(lock)
    mediator.state_transition(
        mediator_state=mediator_state,
        state_change=ReceiveLockExpired(
            balance_proof=balance_proof,
            secrethash=secrethash,
            message_identifier=message_identifier,
        ),
        channelidentifiers_to_channels=setup.channel_map,
        pseudo_random_generator=pseudo_random_generator,
        block_number=expired_block_number,
    )
    assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks
Exemplo n.º 44
0
def handle_inittarget(
    state_change: ActionInitTarget,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
    block_number: typing.BlockNumber,
):
    """ Handles an ActionInitTarget state change. """
    transfer = state_change.transfer
    route = state_change.route

    assert channel_state.identifier == transfer.balance_proof.channel_identifier
    is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer(
        channel_state,
        transfer,
    )

    if is_valid:
        # A valid balance proof does not mean the payment itself is still valid.
        # e.g. the lock may be near expiration or have expired. This is fine. The
        # message with an unusable lock must be handled to properly synchronize the
        # local view of the partner's channel state, allowing the next balance
        # proofs to be handled. This however, must only be done once, which is
        # enforced by the nonce increasing sequentially, which is verified by
        # the handler handle_receive_lockedtransfer.
        target_state = TargetTransferState(route, transfer)

        safe_to_wait, _ = is_safe_to_wait(
            transfer.lock.expiration,
            channel_state.reveal_timeout,
            block_number,
        )

        # If there is not enough time to safely unlock the lock on-chain
        # silently let the transfer expire. The target task must be created to
        # handle the ReceiveLockExpired state change, which will clear the
        # expired lock.
        if safe_to_wait:
            message_identifier = message_identifier_from_prng(
                pseudo_random_generator)
            recipient = transfer.initiator
            secret_request = SendSecretRequest(
                recipient=typing.Address(recipient),
                channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
                message_identifier=message_identifier,
                payment_identifier=transfer.payment_identifier,
                amount=transfer.lock.amount,
                expiration=transfer.lock.expiration,
                secrethash=transfer.lock.secrethash,
            )
            channel_events.append(secret_request)

        iteration = TransitionResult(target_state, channel_events)
    else:
        # 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.
        unlock_failed = EventUnlockClaimFailed(
            identifier=transfer.payment_identifier,
            secrethash=transfer.lock.secrethash,
            reason=errormsg,
        )
        channel_events.append(unlock_failed)
        iteration = TransitionResult(None, channel_events)

    return iteration
Exemplo n.º 45
0
def handle_inittarget_light(
    state_change: ActionInitTargetLight,
    channel_state: NettingChannelState,
    pseudo_random_generator: random.Random,
    block_number: BlockNumber,
    storage
) -> TransitionResult[TargetTransferState]:
    """ Handles an ActionInitTarget state change. """
    transfer = state_change.transfer
    route = state_change.route

    assert channel_state.identifier == transfer.balance_proof.channel_identifier
    is_valid, channel_events, errormsg, handle_invoice_result = channel.handle_receive_lockedtransfer_light(
        channel_state, transfer, storage
    )

    if is_valid:
        # A valid balance proof does not mean the payment itself is still valid.
        # e.g. the lock may be near expiration or have expired. This is fine. The
        # message with an unusable lock must be handled to properly synchronize the
        # local view of the partner's channel state, allowing the next balance
        # proofs to be handled. This however, must only be done once, which is
        # enforced by the nonce increasing sequentially, which is verified by
        # the handler handle_receive_lockedtransfer.
        target_state = TargetTransferState(route, transfer)

        safe_to_wait, _ = is_safe_to_wait(
            transfer.lock.expiration, channel_state.reveal_timeout, block_number
        )

        # If there is not enough time to safely unlock the lock on-chain
        # silently let the transfer expire. The target task must be created to
        # handle the ReceiveLockExpired state change, which will clear the
        # expired lock.
        #
        # We add a new validation.
        # It is verified that if there was an invoice it was paid successfully,
        # if it was not, the payment is interrupted
        # by not generating an event send secret request
        if safe_to_wait and handle_invoice_result['is_valid']:
            payment = LightClientPayment(
                state_change.transfer.target, state_change.transfer.initiator,
                False,
                channel_state.token_network_identifier,
                transfer.lock.amount,
                str(date.today()),
                LightClientPaymentStatus.Pending,
                transfer.payment_identifier
            )

            payment_exists = LightClientService.get_light_client_payment(payment.payment_id, storage)
            if not payment_exists:
                LightClientMessageHandler.store_light_client_payment(payment, storage)

            message_identifier = message_identifier_from_prng(pseudo_random_generator)
            recipient = transfer.initiator
            secret_request = SendSecretRequest(
                recipient=Address(recipient),
                channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE,
                message_identifier=message_identifier,
                payment_identifier=transfer.payment_identifier,
                amount=transfer.lock.amount,
                expiration=transfer.lock.expiration,
                secrethash=transfer.lock.secrethash,
            )

            store_locked_transfer_event = StoreMessageEvent(transfer.message_identifier, transfer.payment_identifier, 1,
                                                            state_change.signed_lockedtransfer, True)

            secret_request_message = SecretRequest.from_event(secret_request)
            store_secret_request_event = StoreMessageEvent(message_identifier, transfer.payment_identifier, 5,
                                                           secret_request_message, False)
            channel_events.append(store_secret_request_event)
            channel_events.append(store_locked_transfer_event)

        iteration = TransitionResult(target_state, channel_events)
    else:
        # 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.
        assert errormsg, "handle_receive_lockedtransfer should return error msg if not valid"
        unlock_failed = EventUnlockClaimFailed(
            identifier=transfer.payment_identifier,
            secrethash=transfer.lock.secrethash,
            reason=errormsg,
        )
        channel_events.append(unlock_failed)
        iteration = TransitionResult(None, channel_events)

    return iteration