예제 #1
0
def test_mediated_transfer_with_fees(raiden_network, number_of_nodes, deposit,
                                     token_addresses, network_wait, case_no):
    """
    Test mediation with a variety of fee schedules
    """
    apps = raiden_network
    token_address = token_addresses[0]
    chain_state = views.state_from_app(apps[0])
    token_network_registry_address = apps[0].raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        chain_state, token_network_registry_address, token_address)
    assert token_network_address

    def set_fee_schedule(app: App, other_app: App,
                         fee_schedule: FeeScheduleState):
        channel_state = views.get_channelstate_by_token_network_and_partner(  # type: ignore
            chain_state=views.state_from_raiden(app.raiden),
            token_network_address=token_network_address,
            partner_address=other_app.raiden.address,
        )
        assert channel_state
        channel_state.fee_schedule = fee_schedule

    def get_best_routes_with_fees(*args, **kwargs):
        routes = get_best_routes_internal(*args, **kwargs)
        for r in routes:
            r.estimated_fee = fee_without_margin
        return routes

    def assert_balances(expected_transferred_amounts=List[int]):
        for i, transferred_amount in enumerate(expected_transferred_amounts):
            assert_synced_channel_state(  # type: ignore
                token_network_address=token_network_address,
                app0=apps[i],
                balance0=deposit - transferred_amount,
                pending_locks0=[],
                app1=apps[i + 1],
                balance1=deposit + transferred_amount,
                pending_locks1=[],
            )

    amount = PaymentAmount(35)
    fee_without_margin = FeeAmount(20)
    fee = FeeAmount(fee_without_margin +
                    calculate_fee_margin(amount, fee_without_margin))

    no_fees = FeeScheduleState(flat=FeeAmount(0),
                               proportional=ProportionalFeeAmount(0),
                               imbalance_penalty=None)
    no_fees_no_cap = FeeScheduleState(
        flat=FeeAmount(0),
        proportional=ProportionalFeeAmount(0),
        imbalance_penalty=None,
        cap_fees=False,
    )
    cases = [
        # The fee is added by the initiator, but no mediator deducts fees. As a
        # result, the target receives the fee.
        dict(
            fee_schedules=[no_fees, no_fees, no_fees],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee, amount + fee, amount + fee
            ],
        ),
        # The first mediator claims all of the fee.
        dict(
            fee_schedules=[no_fees,
                           FeeScheduleState(flat=fee), no_fees],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[amount + fee, amount, amount],
        ),
        # The first mediator has a proportional fee of 20%
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    proportional=ProportionalFeeAmount(int(0.20e6))),
                no_fees,
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee -
                9,  # See test_get_lock_amount_after_fees for where 9 comes from
                amount + fee -
                9,  # See test_get_lock_amount_after_fees for where 9 comes from
            ],
        ),
        # Both mediators have a proportional fee of 20%
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    proportional=ProportionalFeeAmount(int(0.20e6))),
                FeeScheduleState(
                    proportional=ProportionalFeeAmount(int(0.20e6))),
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee -
                9,  # See test_get_lock_amount_after_fees for where 9 comes from
                # See test_get_lock_amount_after_fees for where 9 and 8 come from
                amount + fee - 9 - 8,
            ],
        ),
        # The first mediator has an imbalance fee that works like a 20%
        # proportional fee when using the channel in this direction.
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    imbalance_penalty=[(0, 200), (1000, 0)]),  # type: ignore
                no_fees,
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee - (amount + fee) // 5 + 2,
                amount + fee - (amount + fee) // 5 + 2,
            ],
        ),
        # Using the same fee_schedules as above on the incoming channel instead
        # of the outgoing channel of mediator 1 should yield the same result.
        dict(
            fee_schedules=[no_fees, no_fees, no_fees],
            incoming_fee_schedules=[
                FeeScheduleState(
                    imbalance_penalty=[(0, 0), (1000, 200)]),  # type: ignore
                None,
                None,
            ],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee - (amount + fee) // 5,
                amount + fee - (amount + fee) // 5,
            ],
        ),
        # The first mediator has an imbalance fee which will add 1/20 token for
        # for every token transferred as a reward for moving the channel into a
        # better state. This causes the target to receive more than the `amount
        # + fees` which is sent by the initiator.
        # transferred amount is 55, so 3 token get added from imbalance fee
        # Here we also need to disable fee capping
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    cap_fees=False,
                    imbalance_penalty=[(0, 0), (1000, 50)]  # type: ignore
                ),
                no_fees,
            ],
            incoming_fee_schedules=[no_fees_no_cap, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee, amount + fee + 3, amount + fee + 3
            ],
        ),
        # Same case as above, but with fee capping enabled
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    cap_fees=True,
                    imbalance_penalty=[(0, 0), (1000, 50)]  # type: ignore
                ),
                no_fees,
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee, amount + fee, amount + fee
            ],
        ),
    ]

    case = cases[case_no]
    for i, fee_schedule in enumerate(case.get("fee_schedules", [])):
        if fee_schedule:
            set_fee_schedule(apps[i], apps[i + 1], fee_schedule)
예제 #2
0
def test_mediated_transfer_with_entire_deposit(raiden_network, number_of_nodes,
                                               token_addresses, deposit,
                                               network_wait) -> None:
    app0, app1, app2 = raiden_network
    token_address = token_addresses[0]
    chain_state = views.state_from_app(app0)
    token_network_registry_address = app0.raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        chain_state, token_network_registry_address, token_address)

    # The test uses internal routing at the moment, that's why this is set like that.
    # However, the actual calculated fee is 3 instead of the 4 calculated here, therefore
    # the amounts are adjusted below
    fee1 = FeeAmount(int(deposit * INTERNAL_ROUTING_DEFAULT_FEE_PERC))
    fee_margin1 = calculate_fee_margin(deposit, fee1)
    fee_difference = 1
    secrethash = transfer_and_assert_path(
        path=raiden_network,
        token_address=token_address,
        amount=deposit - fee1 - fee_margin1,
        identifier=PaymentID(1),
        timeout=network_wait * number_of_nodes,
    )

    with block_timeout_for_transfer_by_secrethash(app1.raiden, secrethash):
        wait_assert(
            func=assert_succeeding_transfer_invariants,
            token_network_address=token_network_address,
            app0=app0,
            balance0=0,
            pending_locks0=[],
            app1=app1,
            balance1=deposit * 2,
            pending_locks1=[],
        )
    with block_timeout_for_transfer_by_secrethash(app2.raiden, secrethash):
        wait_assert(
            func=assert_succeeding_transfer_invariants,
            token_network_address=token_network_address,
            app0=app1,
            balance0=fee1 - fee_difference,
            pending_locks0=[],
            app1=app2,
            balance1=deposit * 2 - fee1 + fee_difference,
            pending_locks1=[],
        )

    app2_capacity = 2 * deposit - fee1
    fee2 = FeeAmount(
        int(round(app2_capacity * INTERNAL_ROUTING_DEFAULT_FEE_PERC)))
    fee_margin2 = calculate_fee_margin(app2_capacity, fee2)
    reverse_path = list(raiden_network[::-1])
    transfer_and_assert_path(
        path=reverse_path,
        token_address=token_address,
        amount=app2_capacity - fee2 - fee_margin2,
        identifier=PaymentID(2),
        timeout=network_wait * number_of_nodes,
    )

    with block_timeout_for_transfer_by_secrethash(app1.raiden, secrethash):
        wait_assert(
            func=assert_succeeding_transfer_invariants,
            token_network_address=token_network_address,
            app0=app0,
            balance0=2 * deposit - fee2 + fee_difference,
            pending_locks0=[],
            app1=app1,
            balance1=fee2 - fee_difference,
            pending_locks1=[],
        )
    with block_timeout_for_transfer_by_secrethash(app2.raiden, secrethash):
        wait_assert(
            func=assert_succeeding_transfer_invariants,
            token_network_address=token_network_address,
            app0=app1,
            balance0=deposit * 2 - fee_difference,
            pending_locks0=[],
            app1=app2,
            balance1=fee_difference,
            pending_locks1=[],
        )
예제 #3
0
def test_mediated_transfer_with_node_consuming_more_than_allocated_fee(
        raiden_network, number_of_nodes, deposit, token_addresses,
        network_wait):
    """
    Tests a mediator node consuming more fees than allocated.
    Which means that the initiator will not reveal the secret
    to the target.
    """
    app0, app1, app2 = raiden_network
    token_address = token_addresses[0]
    chain_state = views.state_from_app(app0)
    token_network_registry_address = app0.raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        chain_state, token_network_registry_address, token_address)
    assert token_network_address
    amount = PaymentAmount(100)
    fee = FeeAmount(5)
    fee_margin = calculate_fee_margin(amount, fee)

    app1_app2_channel_state = views.get_channelstate_by_token_network_and_partner(
        chain_state=views.state_from_raiden(app1.raiden),
        token_network_address=token_network_address,
        partner_address=app2.raiden.address,
    )
    assert app1_app2_channel_state

    # Let app1 consume all of the allocated mediation fee
    app1_app2_channel_state.fee_schedule = FeeScheduleState(
        flat=FeeAmount(fee * 2))

    secret = factories.make_secret(0)
    secrethash = sha256_secrethash(secret)

    wait_message_handler = WaitForMessage()
    app0.raiden.message_handler = wait_message_handler
    secret_request_received = wait_message_handler.wait_for_message(
        SecretRequest, {"secrethash": secrethash})

    def get_best_routes_with_fees(*args, **kwargs):
        routes = get_best_routes_internal(*args, **kwargs)
        for r in routes:
            r.estimated_fee = fee
        return routes

    with patch("raiden.routing.get_best_routes_internal",
               get_best_routes_with_fees):
        app0.raiden.start_mediated_transfer_with_secret(
            token_network_address=token_network_address,
            amount=amount,
            target=app2.raiden.address,
            identifier=1,
            secret=secret,
        )

    app0_app1_channel_state = views.get_channelstate_by_token_network_and_partner(
        chain_state=views.state_from_raiden(app0.raiden),
        token_network_address=token_network_address,
        partner_address=app1.raiden.address,
    )
    assert app0_app1_channel_state

    msg = "App0 should have the transfer in secrethashes_to_lockedlocks"
    assert secrethash in app0_app1_channel_state.our_state.secrethashes_to_lockedlocks, msg

    msg = "App0 should have locked the amount + fee"
    lock_amount = app0_app1_channel_state.our_state.secrethashes_to_lockedlocks[
        secrethash].amount
    assert lock_amount == amount + fee + fee_margin, msg

    secret_request_received.wait()

    app0_chain_state = views.state_from_app(app0)
    initiator_task = cast(
        InitiatorTask,
        app0_chain_state.payment_mapping.secrethashes_to_task[secrethash])

    msg = "App0 should have never revealed the secret"
    transfer_state = initiator_task.manager_state.initiator_transfers[
        secrethash].transfer_state
    assert transfer_state != "transfer_secret_revealed", msg
예제 #4
0
def test_pending_transfers_endpoint(raiden_network: List[RaidenService],
                                    token_addresses):
    initiator, mediator, target = raiden_network
    token_address = token_addresses[0]
    token_network_address = views.get_token_network_address_by_token_address(
        views.state_from_raiden(mediator), mediator.default_registry.address,
        token_address)
    assert token_network_address

    amount_to_send = PaymentAmount(150)
    # Remove when https://github.com/raiden-network/raiden/issues/4982 is tackled
    expected_fee = FeeAmount(
        int(amount_to_send * INTERNAL_ROUTING_DEFAULT_FEE_PERC))
    fee_margin = calculate_fee_margin(amount_to_send, expected_fee)
    # This is 0,4% of ~150, so ~1.2 which gets rounded to 1
    actual_fee = 1
    identifier = PaymentID(42)

    initiator_server = prepare_api_server(initiator)
    mediator_server = prepare_api_server(mediator)
    target_server = prepare_api_server(target)

    target.message_handler = target_wait = WaitForMessage()
    mediator.message_handler = mediator_wait = WaitForMessage()

    secret = factories.make_secret()
    secrethash = sha256_secrethash(secret)

    request = grequests.get(
        api_url_for(mediator_server,
                    "pending_transfers_resource_by_token",
                    token_address=token_address))
    response = request.send().response
    assert response.status_code == 200 and response.content == b"[]"

    target_hold = target.raiden_event_handler
    assert isinstance(
        target_hold,
        HoldRaidenEventHandler), "test app must use HoldRaidenEventHandler"

    target_hold.hold_secretrequest_for(secrethash=secrethash)

    initiator.mediated_transfer_async(
        token_network_address=token_network_address,
        amount=PaymentAmount(amount_to_send - expected_fee - fee_margin),
        target=TargetAddress(target.address),
        identifier=identifier,
        secret=secret,
        route_states=[
            create_route_state_for_route(
                apps=raiden_network,
                token_address=token_address,
                fee_estimate=expected_fee,
            )
        ],
    )

    transfer_arrived = target_wait.wait_for_message(LockedTransfer,
                                                    {"payment_identifier": 42})
    transfer_arrived.wait(timeout=30.0)

    for server in (initiator_server, mediator_server, target_server):
        request = grequests.get(
            api_url_for(server, "pending_transfers_resource"))
        response = request.send().response
        assert response.status_code == 200
        content = json.loads(response.content)
        assert len(content) == 1
        assert content[0]["payment_identifier"] == str(identifier)
        if server == target_server:
            assert content[0]["locked_amount"] == str(amount_to_send -
                                                      actual_fee)
        else:
            assert content[0]["locked_amount"] == str(amount_to_send)
        assert content[0]["token_address"] == to_checksum_address(
            token_address)
        assert content[0]["token_network_address"] == to_checksum_address(
            token_network_address)

    mediator_unlock = mediator_wait.wait_for_message(Unlock, {})
    target_unlock = target_wait.wait_for_message(Unlock, {})
    target_hold.release_secretrequest_for(target, secrethash)
    gevent.joinall({mediator_unlock, target_unlock}, raise_error=True)

    for server in (initiator_server, mediator_server, target_server):
        request = grequests.get(
            api_url_for(server, "pending_transfers_resource"))
        response = request.send().response
        assert response.status_code == 200 and response.content == b"[]"

    request = grequests.get(
        api_url_for(
            initiator_server,
            "pending_transfers_resource_by_token",
            token_address=to_hex(b"notaregisteredtokenn"),
        ))
    response = request.send().response
    assert response.status_code == 404 and b"Token" in response.content

    request = grequests.get(
        api_url_for(
            target_server,
            "pending_transfers_resource_by_token_and_partner",
            token_address=token_address,
            partner_address=to_hex(b"~nonexistingchannel~"),
        ))
    response = request.send().response
    assert response.status_code == 404 and b"Channel" in response.content
예제 #5
0
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 = PaymentID(1)
    amount_path = PaymentAmount(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 = PaymentID(2)
    amount_drain = PaymentAmount(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 = 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.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,
            [],
        )
예제 #6
0
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=PaymentID(1),
    )

    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
    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(
            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
    with gevent.Timeout(network_wait):
        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=[],
        )
예제 #7
0
def test_different_view_of_last_bp_during_unlock(
    raiden_chain: List[App],
    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)
    assert token_network_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 = PaymentID(1)
    amount_path = PaymentAmount(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 = PaymentID(2)
    amount_drain = PaymentAmount(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 = 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.raiden.mediated_transfer_async(
        token_network_address, amount_refund,
        TargetAddress(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.greenlet.get()
        assert not app1.raiden

        # Wait for lock expiration so that app0 sends a LockExpired
        wait_for_block(
            raiden=app0.raiden,
            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.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)

    setattr(app1.raiden.raiden_event_handler, "on_raiden_event",
            patched_on_raiden_event)  # NOQA

    # 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
    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
    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
예제 #8
0
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 = PaymentID(1)
    amount_path = PaymentAmount(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 = PaymentID(2)
    amount_drain = PaymentAmount(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 = 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.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=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.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