def test_refund_transfer_after_2nd_hop(raiden_chain, number_of_nodes,
                                       token_addresses, deposit, network_wait):
    """Test the refund transfer sent due to failure after 2nd hop"""
    # Topology:
    #
    #  0 -> 1 -> 2 -> 3
    #
    app0, app1, app2, app3 = raiden_chain
    token_address = token_addresses[0]
    token_network_registry_address = app0.raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_app(app0), token_network_registry_address,
        token_address)

    # make a transfer to test the path app0 -> app1 -> app2 -> app3
    identifier_path = 1
    amount_path = 1
    transfer(
        initiator_app=app0,
        target_app=app3,
        token_address=token_address,
        amount=amount_path,
        identifier=identifier_path,
        timeout=network_wait * number_of_nodes,
    )

    # drain the channel app2 -> app3
    identifier_drain = 2
    amount_drain = deposit * 8 // 10
    transfer(
        initiator_app=app2,
        target_app=app3,
        token_address=token_address,
        amount=amount_drain,
        identifier=identifier_drain,
        timeout=network_wait,
    )

    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [],
            app1,
            deposit + amount_path,
            [],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path,
            [],
            app2,
            deposit + amount_path,
            [],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app2,
            deposit - amount_path - amount_drain,
            [],
            app3,
            deposit + amount_path + amount_drain,
            [],
        )

    # app0 -> app1 -> app2 > app3 is the only available path, but the channel
    # app2 -> app3 doesn't have capacity, so a refund will be sent on
    # app2 -> app1 -> app0
    identifier_refund = 3
    amount_refund = 50
    amount_refund_with_fees = amount_refund + calculate_fee_for_amount(
        amount_refund)
    payment_status = app0.raiden.mediated_transfer_async(
        token_network_address, amount_refund, app3.raiden.address,
        identifier_refund)
    msg = "there is no path with capacity, the transfer must fail"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # Lock structures with the correct amount

    send_locked1 = raiden_events_search_for_item(
        app0.raiden,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked1

    send_refund1 = raiden_events_search_for_item(app1.raiden,
                                                 SendRefundTransfer, {})
    assert send_refund1

    lock1 = send_locked1.transfer.lock
    refund_lock1 = send_refund1.transfer.lock
    assert lock1.amount == refund_lock1.amount
    assert lock1.secrethash == refund_lock1.secrethash

    send_locked2 = raiden_events_search_for_item(
        app1.raiden,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked2

    send_refund2 = raiden_events_search_for_item(app2.raiden,
                                                 SendRefundTransfer, {})
    assert send_refund2

    lock2 = send_locked2.transfer.lock
    refund_lock2 = send_refund2.transfer.lock
    assert lock2.amount == refund_lock2.amount
    assert lock2.secrethash
    assert lock2.expiration

    # channels have the amount locked because of the refund message
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [lock1],
            app1,
            deposit + amount_path,
            [refund_lock1],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path,
            [lock2],
            app2,
            deposit + amount_path,
            [refund_lock2],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app2,
            deposit - amount_path - amount_drain,
            [],
            app3,
            deposit + amount_path + amount_drain,
            [],
        )
def test_different_view_of_last_bp_during_unlock(
    raiden_chain,
    number_of_nodes,
    token_addresses,
    deposit,
    network_wait,
    retry_timeout,
    blockchain_type,
):
    """Test for https://github.com/raiden-network/raiden/issues/3196#issuecomment-449163888"""
    # Topology:
    #
    #  0 -> 1 -> 2
    #
    app0, app1, app2 = raiden_chain
    token_address = token_addresses[0]
    token_network_registry_address = app0.raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_app(app0), token_network_registry_address,
        token_address)
    token_proxy = app0.raiden.proxy_manager.token(token_address)
    initial_balance0 = token_proxy.balance_of(app0.raiden.address)
    initial_balance1 = token_proxy.balance_of(app1.raiden.address)

    # make a transfer to test the path app0 -> app1 -> app2
    identifier_path = 1
    amount_path = 1
    transfer(
        initiator_app=app0,
        target_app=app2,
        token_address=token_address,
        amount=amount_path,
        identifier=identifier_path,
        timeout=network_wait * number_of_nodes,
    )

    # drain the channel app1 -> app2
    identifier_drain = 2
    amount_drain = deposit * 8 // 10
    transfer(
        initiator_app=app1,
        target_app=app2,
        token_address=token_address,
        amount=amount_drain,
        identifier=identifier_drain,
        timeout=network_wait,
    )

    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [],
            app1,
            deposit + amount_path,
            [],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # app0 -> app1 -> app2 is the only available path, but the channel app1 ->
    # app2 doesn't have capacity, so a refund will be sent on app1 -> app0
    identifier_refund = 3
    amount_refund = 50
    amount_refund_with_fees = amount_refund + calculate_fee_for_amount(50)
    payment_status = app0.raiden.mediated_transfer_async(
        token_network_address, amount_refund, app2.raiden.address,
        identifier_refund)
    msg = "there is no path with capacity, the transfer must fail"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # A lock structure with the correct amount

    send_locked = raiden_events_search_for_item(
        app0.raiden,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked
    secrethash = send_locked.transfer.lock.secrethash

    send_refund = raiden_events_search_for_item(app1.raiden,
                                                SendRefundTransfer, {})
    assert send_refund

    lock = send_locked.transfer.lock
    refund_lock = send_refund.transfer.lock
    assert lock.amount == refund_lock.amount
    assert lock.secrethash
    assert lock.expiration
    assert lock.secrethash == refund_lock.secrethash

    # Both channels have the amount locked because of the refund message
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [lock],
            app1,
            deposit + amount_path,
            [refund_lock],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # Additional checks for LockExpired causing nonce mismatch after refund transfer:
    # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046
    # At this point make sure that the initiator has not deleted the payment task
    assert secrethash in state_from_raiden(
        app0.raiden).payment_mapping.secrethashes_to_task

    with dont_handle_node_change_network_state():
        # now app1 goes offline
        app1.raiden.stop()
        app1.raiden.get()
        assert not app1.raiden

        # Wait for lock expiration so that app0 sends a LockExpired
        wait_for_block(
            raiden=app0.raiden,
            block_number=channel.get_sender_expiration_threshold(
                lock.expiration) + 1,
            retry_timeout=retry_timeout,
        )

        # make sure that app0 sent a lock expired message for the secrethash
        wait_for_raiden_event(app0.raiden, SendLockExpired,
                              {"secrethash": secrethash}, retry_timeout)

        # now app0 closes the channel
        RaidenAPI(app0.raiden).channel_close(
            registry_address=token_network_registry_address,
            token_address=token_address,
            partner_address=app1.raiden.address,
        )

    count = 0
    on_raiden_event_original = app1.raiden.raiden_event_handler.on_raiden_event

    def patched_on_raiden_event(raiden, chain_state, event):
        if type(event) == ContractSendChannelUpdateTransfer:
            nonlocal count
            count += 1

        on_raiden_event_original(raiden, chain_state, event)

    app1.raiden.raiden_event_handler.on_raiden_event = patched_on_raiden_event
    # and now app1 comes back online
    app1.raiden.start()
    # test for https://github.com/raiden-network/raiden/issues/3216
    assert count == 1, "Update transfer should have only been called once during restart"
    channel_identifier = get_channelstate(app0, app1,
                                          token_network_address).identifier

    # and we wait for settlement
    wait_for_settle(
        raiden=app0.raiden,
        token_network_registry_address=token_network_registry_address,
        token_address=token_address,
        channel_ids=[channel_identifier],
        retry_timeout=app0.raiden.alarm.sleep_time,
    )

    timeout = 30 if blockchain_type == "parity" else 10
    with gevent.Timeout(timeout):
        unlock_app0 = wait_for_state_change(
            app0.raiden,
            ContractReceiveChannelBatchUnlock,
            {"receiver": app0.raiden.address},
            retry_timeout,
        )
    assert unlock_app0.returned_tokens == amount_refund_with_fees
    with gevent.Timeout(timeout):
        unlock_app1 = wait_for_state_change(
            app1.raiden,
            ContractReceiveChannelBatchUnlock,
            {"receiver": app1.raiden.address},
            retry_timeout,
        )
    assert unlock_app1.returned_tokens == amount_refund_with_fees
    final_balance0 = token_proxy.balance_of(app0.raiden.address)
    final_balance1 = token_proxy.balance_of(app1.raiden.address)

    assert final_balance0 - deposit - initial_balance0 == -1
    assert final_balance1 - deposit - initial_balance1 == 1
def test_refund_messages(raiden_chain, token_addresses, deposit, network_wait):
    # The network has the following topology:
    #
    #   App0 <---> App1 <---> App2
    app0, app1, app2 = raiden_chain  # pylint: disable=unbalanced-tuple-unpacking
    token_address = token_addresses[0]
    token_network_registry_address = app0.raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_app(app0), token_network_registry_address,
        token_address)

    # Exhaust the channel App1 <-> App2 (to force the refund transfer)
    # Here we make a single-hop transfer, no fees are charged so we should
    # send the whole deposit amount to drain the channel.
    transfer(
        initiator_app=app1,
        target_app=app2,
        token_address=token_address,
        amount=deposit,
        identifier=1,
    )

    refund_amount = deposit // 2
    refund_fees = calculate_fee_for_amount(refund_amount)
    refund_amount_with_fees = refund_amount + refund_fees
    identifier = 1
    payment_status = app0.raiden.mediated_transfer_async(
        token_network_address, refund_amount, app2.raiden.address, identifier)
    msg = "Must fail, there are no routes available"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # The transfer from app0 to app2 failed, so the balances did change.
    # Since the refund is not unlocked both channels have the corresponding
    # amount locked (issue #1091)
    send_lockedtransfer = raiden_events_search_for_item(
        app0.raiden,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": refund_amount_with_fees
            }
        }},
    )
    assert send_lockedtransfer

    send_refundtransfer = raiden_events_search_for_item(
        app1.raiden, SendRefundTransfer, {})
    assert send_refundtransfer

    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit,
            [send_lockedtransfer.transfer.lock],
            app1,
            deposit,
            [send_refundtransfer.transfer.lock],
        )

    # This channel was exhausted to force the refund transfer except for the fees
    with gevent.Timeout(network_wait):
        wait_assert(assert_synced_channel_state, token_network_address, app1,
                    0, [], app2, deposit * 2, [])
def test_refund_transfer(raiden_chain, number_of_nodes, token_addresses,
                         deposit, network_wait, retry_timeout):
    """A failed transfer must send a refund back.

    TODO:
        - Unlock the token on refund #1091
        - Clear the pending locks and update the locked amount #193
        - Remove the refund message type #490"""
    # Topology:
    #
    #  0 -> 1 -> 2
    #
    app0, app1, app2 = raiden_chain
    token_address = token_addresses[0]
    token_network_registry_address = app0.raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_app(app0), token_network_registry_address,
        token_address)

    # make a transfer to test the path app0 -> app1 -> app2
    identifier_path = 1
    amount_path = 1
    transfer(
        initiator_app=app0,
        target_app=app2,
        token_address=token_address,
        amount=amount_path,
        identifier=identifier_path,
        timeout=network_wait * number_of_nodes,
    )

    # drain the channel app1 -> app2
    identifier_drain = 2
    amount_drain = deposit * 8 // 10
    transfer(
        initiator_app=app1,
        target_app=app2,
        token_address=token_address,
        amount=amount_drain,
        identifier=identifier_drain,
        timeout=network_wait,
    )

    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [],
            app1,
            deposit + amount_path,
            [],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # app0 -> app1 -> app2 is the only available path, but the channel app1 ->
    # app2 doesn't have capacity, so a refund will be sent on app1 -> app0
    identifier_refund = 3
    amount_refund = 50
    amount_refund_with_fees = amount_refund + calculate_fee_for_amount(
        amount_refund)
    payment_status = app0.raiden.mediated_transfer_async(
        token_network_address, amount_refund, app2.raiden.address,
        identifier_refund)
    msg = "there is no path with capacity, the transfer must fail"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # A lock structure with the correct amount

    send_locked = raiden_events_search_for_item(
        app0.raiden,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked
    secrethash = send_locked.transfer.lock.secrethash

    send_refund = raiden_events_search_for_item(app1.raiden,
                                                SendRefundTransfer, {})
    assert send_refund

    lock = send_locked.transfer.lock
    refund_lock = send_refund.transfer.lock
    assert lock.amount == refund_lock.amount
    assert lock.secrethash
    assert lock.expiration
    assert lock.secrethash == refund_lock.secrethash

    # Both channels have the amount locked because of the refund message
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [lock],
            app1,
            deposit + amount_path,
            [refund_lock],
        )
    with gevent.Timeout(network_wait):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # Additional checks for LockExpired causing nonce mismatch after refund transfer:
    # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046
    # At this point make sure that the initiator has not deleted the payment task
    assert secrethash in state_from_raiden(
        app0.raiden).payment_mapping.secrethashes_to_task

    # Wait for lock lock expiration but make sure app0 never processes LockExpired
    with dont_handle_lock_expired_mock(app0):
        wait_for_block(
            raiden=app0.raiden,
            block_number=channel.get_sender_expiration_threshold(
                lock.expiration) + 1,
            retry_timeout=retry_timeout,
        )
        # make sure that app0 still has the payment task for the secrethash
        # https://github.com/raiden-network/raiden/issues/3183
        assert secrethash in state_from_raiden(
            app0.raiden).payment_mapping.secrethashes_to_task

        # make sure that app1 sent a lock expired message for the secrethash
        send_lock_expired = raiden_events_search_for_item(
            app1.raiden, SendLockExpired, {"secrethash": secrethash})
        assert send_lock_expired
        # make sure that app0 never got it
        state_changes = app0.raiden.wal.storage.get_statechanges_by_range(
            RANGE_ALL_STATE_CHANGES)
        assert not search_for_item(state_changes, ReceiveLockExpired,
                                   {"secrethash": secrethash})

    # Out of the handicapped app0 transport.
    # Now wait till app0 receives and processes LockExpired
    receive_lock_expired = wait_for_state_change(app0.raiden,
                                                 ReceiveLockExpired,
                                                 {"secrethash": secrethash},
                                                 retry_timeout)
    # And also till app1 received the processed
    wait_for_state_change(
        app1.raiden,
        ReceiveProcessed,
        {"message_identifier": receive_lock_expired.message_identifier},
        retry_timeout,
    )

    # make sure app1 queue has cleared the SendLockExpired
    chain_state1 = views.state_from_app(app1)
    queues1 = views.get_all_messagequeues(chain_state=chain_state1)
    result = [(queue_id, queue) for queue_id, queue in queues1.items()
              if queue_id.recipient == app0.raiden.address and queue]
    assert not result

    # and now wait for 1 more block so that the payment task can be deleted
    wait_for_block(
        raiden=app0.raiden,
        block_number=app0.raiden.get_block_number() + 1,
        retry_timeout=retry_timeout,
    )

    # and since the lock expired message has been sent and processed then the
    # payment task should have been deleted from both nodes
    # https://github.com/raiden-network/raiden/issues/3183
    assert secrethash not in state_from_raiden(
        app0.raiden).payment_mapping.secrethashes_to_task
    assert secrethash not in state_from_raiden(
        app1.raiden).payment_mapping.secrethashes_to_task
Beispiel #5
0
def test_refund_transfer_after_2nd_hop(raiden_chain: List[RaidenService],
                                       number_of_nodes, token_addresses,
                                       deposit, network_wait):
    """Test the refund transfer sent due to failure after 2nd hop"""
    # Topology:
    #
    #  0 -> 1 -> 2 -> 3
    #
    app0, app1, app2, app3 = raiden_chain
    token_address = token_addresses[0]
    token_network_registry_address = app0.default_registry.address

    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_raiden(app0), token_network_registry_address,
        token_address)
    assert token_network_address, "token_address must be registered by the fixtures."

    # make a transfer to test the path app0 -> app1 -> app2 -> app3
    identifier_path = PaymentID(1)
    amount_path = PaymentAmount(1)
    with block_offset_timeout(app0):
        transfer(
            initiator_app=app0,
            target_app=app3,
            token_address=token_address,
            amount=amount_path,
            identifier=identifier_path,
            timeout=network_wait * number_of_nodes,
            routes=[[app0.address, app1.address, app2.address, app3.address]],
        )

    # drain the channel app2 -> app3
    identifier_drain = PaymentID(2)
    amount_drain = PaymentAmount(deposit * 8 // 10)
    with block_offset_timeout(app2):
        transfer(
            initiator_app=app2,
            target_app=app3,
            token_address=token_address,
            amount=amount_drain,
            identifier=identifier_drain,
            timeout=network_wait,
            routes=[[app2.address, app3.address]],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [],
            app1,
            deposit + amount_path,
            [],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path,
            [],
            app2,
            deposit + amount_path,
            [],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app2,
            deposit - amount_path - amount_drain,
            [],
            app3,
            deposit + amount_path + amount_drain,
            [],
        )

    # app0 -> app1 -> app2 > app3 is the only available path, but the channel
    # app2 -> app3 doesn't have capacity, so a refund will be sent on
    # app2 -> app1 -> app0
    identifier_refund = PaymentID(3)
    amount_refund = PaymentAmount(50)
    fee = calculate_fee_for_amount(amount_refund)
    fee_margin = calculate_fee_margin(amount_refund, fee)
    amount_refund_with_fees = amount_refund + fee + fee_margin

    payment_status = app0.mediated_transfer_async(
        token_network_address=token_network_address,
        amount=amount_refund,
        target=TargetAddress(app3.address),
        identifier=identifier_refund,
        route_states=[
            create_route_state_for_route(
                apps=[app0, app1, app2],
                token_address=token_address,
                fee_estimate=FeeAmount(
                    round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount_refund)),
            )
        ],
    )
    msg = "there is no path with capacity, the transfer must fail"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # Lock structures with the correct amount

    send_locked1 = raiden_events_search_for_item(
        app0,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked1

    send_refund1 = raiden_events_search_for_item(app1, SendRefundTransfer, {})
    assert send_refund1

    lock1 = send_locked1.transfer.lock
    refund_lock1 = send_refund1.transfer.lock
    assert lock1.amount == refund_lock1.amount
    assert lock1.secrethash == refund_lock1.secrethash

    send_locked2 = raiden_events_search_for_item(
        app1,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked2

    send_refund2 = raiden_events_search_for_item(app2, SendRefundTransfer, {})
    assert send_refund2

    lock2 = send_locked2.transfer.lock
    refund_lock2 = send_refund2.transfer.lock
    assert lock2.amount == refund_lock2.amount
    assert lock2.secrethash
    assert lock2.expiration

    # channels have the amount locked because of the refund message

    with block_offset_timeout(app0):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [lock1],
            app1,
            deposit + amount_path,
            [refund_lock1],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path,
            [lock2],
            app2,
            deposit + amount_path,
            [refund_lock2],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app2,
            deposit - amount_path - amount_drain,
            [],
            app3,
            deposit + amount_path + amount_drain,
            [],
        )
Beispiel #6
0
def test_refund_messages(raiden_chain: List[RaidenService], token_addresses,
                         deposit):
    # The network has the following topology:
    #
    #   App0 <---> App1 <---> App2
    app0, app1, app2 = raiden_chain  # pylint: disable=unbalanced-tuple-unpacking
    token_address = token_addresses[0]
    token_network_registry_address = app0.default_registry.address

    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_raiden(app0), token_network_registry_address,
        token_address)
    assert token_network_address, "token_address must be registered by the fixtures."

    identifier = PaymentID(1)

    # Exhaust the channel App1 <-> App2 (to force the refund transfer)
    # Here we make a single-hop transfer, no fees are charged so we should
    # send the whole deposit amount to drain the channel.
    transfer(
        initiator_app=app1,
        target_app=app2,
        token_address=token_address,
        amount=deposit,
        identifier=identifier,
        routes=[[app1.address, app2.address]],
    )

    refund_amount = deposit // 2
    refund_fees = calculate_fee_for_amount(refund_amount)
    fee_margin = calculate_fee_margin(refund_amount, refund_fees)
    refund_amount_with_fees = refund_amount + refund_fees + fee_margin

    payment_status = app0.mediated_transfer_async(
        token_network_address=token_network_address,
        amount=refund_amount,
        target=TargetAddress(app2.address),
        identifier=identifier,
        route_states=[
            create_route_state_for_route(
                apps=raiden_chain,
                token_address=token_address,
                fee_estimate=FeeAmount(
                    round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * refund_amount)),
            )
        ],
    )
    msg = "Must fail, there are no routes available"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # The transfer from app0 to app2 failed, so the balances did change.
    # Since the refund is not unlocked both channels have the corresponding
    # amount locked (issue #1091)
    send_lockedtransfer = raiden_events_search_for_item(
        app0,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": refund_amount_with_fees
            }
        }},
    )
    assert send_lockedtransfer

    send_refundtransfer = raiden_events_search_for_item(
        app1, SendRefundTransfer, {})
    assert send_refundtransfer

    with block_offset_timeout(app1):
        wait_assert(
            func=assert_synced_channel_state,
            token_network_address=token_network_address,
            app0=app0,
            balance0=deposit,
            pending_locks0=[send_lockedtransfer.transfer.lock],
            app1=app1,
            balance1=deposit,
            pending_locks1=[send_refundtransfer.transfer.lock],
        )
        # This channel was exhausted to force the refund transfer except for the fees
        wait_assert(
            func=assert_succeeding_transfer_invariants,
            token_network_address=token_network_address,
            app0=app1,
            balance0=0,
            pending_locks0=[],
            app1=app2,
            balance1=deposit * 2,
            pending_locks1=[],
        )
Beispiel #7
0
def test_different_view_of_last_bp_during_unlock(
    raiden_chain: List[RaidenService],
    restart_node,
    token_addresses,
    deposit,
    retry_timeout,
    blockchain_type,
):
    """Test for https://github.com/raiden-network/raiden/issues/3196#issuecomment-449163888"""
    # Topology:
    #
    #  0 -> 1 -> 2
    #
    app0, app1, app2 = raiden_chain
    token_address = token_addresses[0]
    token_network_registry_address = app0.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_raiden(app0), token_network_registry_address,
        token_address)
    assert token_network_address
    token_proxy = app0.proxy_manager.token(token_address, BLOCK_ID_LATEST)
    initial_balance0 = token_proxy.balance_of(app0.address)
    initial_balance1 = token_proxy.balance_of(app1.address)

    # make a transfer to test the path app0 -> app1 -> app2
    identifier_path = PaymentID(1)
    amount_path = PaymentAmount(1)
    with block_offset_timeout(app0):
        transfer(
            initiator_app=app0,
            target_app=app2,
            token_address=token_address,
            amount=amount_path,
            identifier=identifier_path,
            routes=[[app0.address, app1.address, app2.address]],
        )

    # drain the channel app1 -> app2
    identifier_drain = PaymentID(2)
    amount_drain = PaymentAmount(deposit * 8 // 10)
    with block_offset_timeout(app1):
        transfer(
            initiator_app=app1,
            target_app=app2,
            token_address=token_address,
            amount=amount_drain,
            identifier=identifier_drain,
            routes=[[app1.address, app2.address]],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [],
            app1,
            deposit + amount_path,
            [],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # app0 -> app1 -> app2 is the only available path, but the channel app1 ->
    # app2 doesn't have capacity, so a refund will be sent on app1 -> app0
    identifier_refund = PaymentID(3)
    amount_refund = PaymentAmount(50)
    fee = calculate_fee_for_amount(amount_refund)
    fee_margin = calculate_fee_margin(amount_refund, fee)
    amount_refund_with_fees = amount_refund + fee + fee_margin

    payment_status = app0.mediated_transfer_async(
        token_network_address=token_network_address,
        amount=amount_refund,
        target=TargetAddress(app2.address),
        identifier=identifier_refund,
        route_states=[
            create_route_state_for_route(
                apps=raiden_chain,
                token_address=token_address,
                fee_estimate=FeeAmount(
                    round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount_refund)),
            )
        ],
    )
    msg = "there is no path with capacity, the transfer must fail"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # A lock structure with the correct amount

    send_locked = raiden_events_search_for_item(
        app0,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked
    secrethash = send_locked.transfer.lock.secrethash

    send_refund = raiden_events_search_for_item(app1, SendRefundTransfer, {})
    assert send_refund

    lock = send_locked.transfer.lock
    refund_lock = send_refund.transfer.lock
    assert lock.amount == refund_lock.amount
    assert lock.secrethash
    assert lock.expiration
    assert lock.secrethash == refund_lock.secrethash

    # Both channels have the amount locked because of the refund message
    with block_offset_timeout(app0):
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [lock],
            app1,
            deposit + amount_path,
            [refund_lock],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # Additional checks for LockExpired causing nonce mismatch after refund transfer:
    # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046
    # At this point make sure that the initiator has not deleted the payment task
    assert secrethash in state_from_raiden(
        app0).payment_mapping.secrethashes_to_task

    with dont_handle_node_change_network_state():
        # now app1 goes offline
        app1.stop()
        app1.greenlet.get()
        assert not app1

        # Wait for lock expiration so that app0 sends a LockExpired
        wait_for_block(
            raiden=app0,
            block_number=BlockNumber(
                channel.get_sender_expiration_threshold(lock.expiration) + 1),
            retry_timeout=retry_timeout,
        )

        # make sure that app0 sent a lock expired message for the secrethash
        wait_for_raiden_event(app0, SendLockExpired,
                              {"secrethash": secrethash}, retry_timeout)

        # now app0 closes the channel
        RaidenAPI(app0).channel_close(
            registry_address=token_network_registry_address,
            token_address=token_address,
            partner_address=app1.address,
        )

    count = 0
    on_raiden_events_original = app1.raiden_event_handler.on_raiden_events

    def patched_on_raiden_events(raiden, chain_state, events):
        nonlocal count

        count += sum(1 for event in events
                     if type(event) == ContractSendChannelUpdateTransfer)

        on_raiden_events_original(raiden, chain_state, events)

    setattr(app1.raiden_event_handler, "on_raiden_events",
            patched_on_raiden_events)  # NOQA

    # and now app1 comes back online
    restart_node(app1)
    # test for https://github.com/raiden-network/raiden/issues/3216
    assert count == 1, "Update transfer should have only been called once during restart"
    channel_identifier = get_channelstate(app0, app1,
                                          token_network_address).identifier

    # and we wait for settlement
    wait_for_settle(
        raiden=app0,
        token_network_registry_address=token_network_registry_address,
        token_address=token_address,
        channel_ids=[channel_identifier],
        retry_timeout=app0.alarm.sleep_time,
    )

    timeout = 30 if blockchain_type == "parity" else 10
    with gevent.Timeout(timeout):
        unlock_app0 = wait_for_state_change(
            app0,
            ContractReceiveChannelBatchUnlock,
            {"receiver": app0.address},
            retry_timeout,
        )
    assert unlock_app0
    assert unlock_app0.returned_tokens == amount_refund_with_fees
    with gevent.Timeout(timeout):
        unlock_app1 = wait_for_state_change(
            app1,
            ContractReceiveChannelBatchUnlock,
            {"receiver": app1.address},
            retry_timeout,
        )
    assert unlock_app1
    assert unlock_app1.returned_tokens == amount_refund_with_fees
    final_balance0 = token_proxy.balance_of(app0.address)
    final_balance1 = token_proxy.balance_of(app1.address)

    assert final_balance0 - deposit - initial_balance0 == -1
    assert final_balance1 - deposit - initial_balance1 == 1
Beispiel #8
0
def test_refund_transfer(raiden_chain: List[RaidenService], token_addresses,
                         deposit, retry_timeout):
    """A failed transfer must send a refund back.

    TODO:
        - Unlock the token on refund #1091
    """
    # Topology:
    #
    #  0 -> 1 -> 2
    #
    app0, app1, app2 = raiden_chain
    token_address = token_addresses[0]
    token_network_registry_address = app0.default_registry.address

    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_raiden(app0), token_network_registry_address,
        token_address)
    assert token_network_address, "token_address must be registered by the fixtures."

    # make a transfer to test the path app0 -> app1 -> app2
    identifier_path = PaymentID(1)
    amount_path = PaymentAmount(1)
    with block_offset_timeout(app0):
        transfer(
            initiator_app=app0,
            target_app=app2,
            token_address=token_address,
            amount=amount_path,
            identifier=identifier_path,
            routes=[[app0.address, app1.address, app2.address]],
        )

    # drain the channel app1 -> app2
    identifier_drain = PaymentID(2)
    amount_drain = PaymentAmount(deposit * 8 // 10)

    transfer_timeout = block_offset_timeout(app1)
    with transfer_timeout:
        transfer(
            initiator_app=app1,
            target_app=app2,
            token_address=token_address,
            amount=amount_drain,
            identifier=identifier_drain,
            routes=[[app1.address, app2.address]],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [],
            app1,
            deposit + amount_path,
            [],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # app0 -> app1 -> app2 is the only available path, but the channel app1 ->
    # app2 doesn't have capacity, so a refund will be sent on app1 -> app0
    identifier_refund = PaymentID(3)
    amount_refund = PaymentAmount(50)
    fee = calculate_fee_for_amount(amount_refund)
    fee_margin = calculate_fee_margin(amount_refund, fee)
    amount_refund_with_fees = amount_refund + fee + fee_margin

    payment_status = app0.mediated_transfer_async(
        token_network_address=token_network_address,
        amount=amount_refund,
        target=TargetAddress(app2.address),
        identifier=identifier_refund,
        route_states=[
            create_route_state_for_route(
                apps=raiden_chain,
                token_address=token_address,
                fee_estimate=FeeAmount(
                    round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount_refund)),
            )
        ],
    )
    msg = "there is no path with capacity, the transfer must fail"
    assert isinstance(payment_status.payment_done.wait(),
                      EventPaymentSentFailed), msg

    # A lock structure with the correct amount

    send_locked = raiden_events_search_for_item(
        app0,
        SendLockedTransfer,
        {"transfer": {
            "lock": {
                "amount": amount_refund_with_fees
            }
        }},
    )
    assert send_locked
    secrethash = send_locked.transfer.lock.secrethash

    send_refund = raiden_events_search_for_item(app1, SendRefundTransfer, {})
    assert send_refund

    lock = send_locked.transfer.lock
    refund_lock = send_refund.transfer.lock
    assert lock.amount == refund_lock.amount
    assert lock.secrethash
    assert lock.expiration
    assert lock.secrethash == refund_lock.secrethash

    # Both channels have the amount locked because of the refund message
    with transfer_timeout:
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app0,
            deposit - amount_path,
            [lock],
            app1,
            deposit + amount_path,
            [refund_lock],
        )
        wait_assert(
            assert_synced_channel_state,
            token_network_address,
            app1,
            deposit - amount_path - amount_drain,
            [],
            app2,
            deposit + amount_path + amount_drain,
            [],
        )

    # Additional checks for LockExpired causing nonce mismatch after refund transfer:
    # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046
    # At this point make sure that the initiator has not deleted the payment task
    assert secrethash in state_from_raiden(
        app0).payment_mapping.secrethashes_to_task

    # Wait for lock lock expiration but make sure app0 never processes LockExpired
    with dont_handle_lock_expired_mock(app0):
        wait_for_block(
            raiden=app0,
            block_number=BlockNumber(
                channel.get_sender_expiration_threshold(lock.expiration) + 1),
            retry_timeout=retry_timeout,
        )
        # make sure that app0 still has the payment task for the secrethash
        # https://github.com/raiden-network/raiden/issues/3183
        assert secrethash in state_from_raiden(
            app0).payment_mapping.secrethashes_to_task

        # make sure that app1 sent a lock expired message for the secrethash
        send_lock_expired = raiden_events_search_for_item(
            app1, SendLockExpired, {"secrethash": secrethash})
        assert send_lock_expired

        assert app0.wal, "test apps must be started by the fixtures."
        state_changes = app0.wal.storage.get_statechanges_by_range(
            RANGE_ALL_STATE_CHANGES)

        # make sure that app0 never got it
        assert not search_for_item(state_changes, ReceiveLockExpired,
                                   {"secrethash": secrethash})

    # Out of the handicapped app0 transport.
    # Now wait till app0 receives and processes LockExpired
    receive_lock_expired = wait_for_state_change(app0, ReceiveLockExpired,
                                                 {"secrethash": secrethash},
                                                 retry_timeout)
    # And also till app1 received the processed
    wait_for_state_change(
        app1,
        ReceiveProcessed,
        {"message_identifier": receive_lock_expired.message_identifier},
        retry_timeout,
    )

    # make sure app1 queue has cleared the SendLockExpired
    chain_state1 = views.state_from_raiden(app1)
    queues1 = views.get_all_messagequeues(chain_state=chain_state1)
    result = [(queue_id, queue) for queue_id, queue in queues1.items()
              if queue_id.recipient == app0.address and queue]
    assert not result

    # and now wait for 1 more block so that the payment task can be deleted
    wait_for_block(
        raiden=app0,
        block_number=BlockNumber(app0.get_block_number() + 1),
        retry_timeout=retry_timeout,
    )

    # and since the lock expired message has been sent and processed then the
    # payment task should have been deleted from both nodes
    # https://github.com/raiden-network/raiden/issues/3183
    assert secrethash not in state_from_raiden(
        app0).payment_mapping.secrethashes_to_task
    assert secrethash not in state_from_raiden(
        app1).payment_mapping.secrethashes_to_task