def test_different_view_of_last_bp_during_unlock( raiden_chain, number_of_nodes, token_addresses, deposit, network_wait, retry_timeout, # UDP does not seem to retry messages until processed # https://github.com/raiden-network/raiden/issues/3185 skip_if_not_matrix, ): """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] payment_network_identifier = app0.raiden.default_registry.address token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(app0), payment_network_identifier, token_address, ) token_proxy = app0.raiden.chain.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 mediated_transfer( app0, app2, token_network_identifier, amount_path, identifier_path, timeout=network_wait * number_of_nodes, ) # drain the channel app1 -> app2 identifier_drain = 2 amount_drain = deposit * 8 // 10 mediated_transfer( initiator_app=app1, target_app=app2, token_network_identifier=token_network_identifier, amount=amount_drain, identifier=identifier_drain, timeout=network_wait, ) # wait for the nodes to sync gevent.sleep(0.2) assert_synced_channel_state( token_network_identifier, app0, deposit - amount_path, [], app1, deposit + amount_path, [], ) assert_synced_channel_state( token_network_identifier, 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 async_result = app0.raiden.mediated_transfer_async( token_network_identifier, amount_refund, app2.raiden.address, identifier_refund, ) assert async_result.wait( ) is False, 'there is no path with capacity, the transfer must fail' gevent.sleep(0.2) # A lock structure with the correct amount send_locked = raiden_events_search_for_item( app0.raiden, SendLockedTransfer, {'transfer': { 'lock': { 'amount': amount_refund } }}, ) 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 assert_synced_channel_state( token_network_identifier, app0, deposit - amount_path, [lockstate_from_lock(lock)], app1, deposit + amount_path, [lockstate_from_lock(refund_lock)], ) assert_synced_channel_state( token_network_identifier, 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) + 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=payment_network_identifier, token_address=token_address, partner_address=app1.raiden.address, ) count = 0 original_update = app1.raiden.raiden_event_handler.handle_contract_send_channelupdate def patched_update(raiden, event): nonlocal count count += 1 original_update(raiden, event) app1.raiden.raiden_event_handler.handle_contract_send_channelupdate = patched_update # 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_identifier).identifier # and we wait for settlement wait_for_settle( raiden=app0.raiden, payment_network_id=payment_network_identifier, token_address=token_address, channel_ids=[channel_identifier], retry_timeout=app0.raiden.alarm.sleep_time, ) with gevent.Timeout(10): unlock_app0 = wait_for_state_change( app0.raiden, ContractReceiveChannelBatchUnlock, {'participant': app0.raiden.address}, retry_timeout, ) assert unlock_app0.returned_tokens == 50 with gevent.Timeout(10): unlock_app1 = wait_for_state_change( app1.raiden, ContractReceiveChannelBatchUnlock, {'participant': app1.raiden.address}, retry_timeout, ) assert unlock_app1.returned_tokens == 50 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_different_view_of_last_bp_during_unlock( raiden_chain, number_of_nodes, token_addresses, deposit, network_wait, retry_timeout, # UDP does not seem to retry messages until processed # https://github.com/raiden-network/raiden/issues/3185 skip_if_not_matrix, ): """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] payment_network_identifier = app0.raiden.default_registry.address token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(app0), payment_network_identifier, token_address, ) token_proxy = app0.raiden.chain.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 mediated_transfer( app0, app2, token_network_identifier, amount_path, identifier_path, timeout=network_wait * number_of_nodes, ) # drain the channel app1 -> app2 identifier_drain = 2 amount_drain = deposit * 8 // 10 mediated_transfer( initiator_app=app1, target_app=app2, token_network_identifier=token_network_identifier, amount=amount_drain, identifier=identifier_drain, timeout=network_wait, ) # wait for the nodes to sync gevent.sleep(0.2) assert_synced_channel_state( token_network_identifier, app0, deposit - amount_path, [], app1, deposit + amount_path, [], ) assert_synced_channel_state( token_network_identifier, 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 async_result = app0.raiden.mediated_transfer_async( token_network_identifier, amount_refund, app2.raiden.address, identifier_refund, ) assert async_result.wait() is False, 'there is no path with capacity, the transfer must fail' gevent.sleep(0.2) # A lock structure with the correct amount send_locked = raiden_events_must_contain_entry( app0.raiden, SendLockedTransfer, {'transfer': {'lock': {'amount': amount_refund}}}, ) assert send_locked secrethash = send_locked.transfer.lock.secrethash send_refund = raiden_events_must_contain_entry(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 assert_synced_channel_state( token_network_identifier, app0, deposit - amount_path, [lockstate_from_lock(lock)], app1, deposit + amount_path, [lockstate_from_lock(refund_lock)], ) assert_synced_channel_state( token_network_identifier, 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) + 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=payment_network_identifier, token_address=token_address, partner_address=app1.raiden.address, ) count = 0 original_update = app1.raiden.raiden_event_handler.handle_contract_send_channelupdate def patched_update(raiden, event): nonlocal count count += 1 original_update(raiden, event) app1.raiden.raiden_event_handler.handle_contract_send_channelupdate = patched_update # 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_identifier).identifier # and we wait for settlement wait_for_settle( raiden=app0.raiden, payment_network_id=payment_network_identifier, token_address=token_address, channel_ids=[channel_identifier], retry_timeout=app0.raiden.alarm.sleep_time, ) with gevent.Timeout(10): unlock_app0 = wait_for_state_change( app0.raiden, ContractReceiveChannelBatchUnlock, {'participant': app0.raiden.address}, retry_timeout, ) assert unlock_app0.returned_tokens == 50 with gevent.Timeout(10): unlock_app1 = wait_for_state_change( app1.raiden, ContractReceiveChannelBatchUnlock, {'participant': app1.raiden.address}, retry_timeout, ) assert unlock_app1.returned_tokens == 50 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_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_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