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)
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=[], )
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
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
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, [], )
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=[], )
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
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