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
def test_refund_transfer( 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, ): """A failed transfer must send a refund back. TODO: - Unlock the token on refund #1091 - Clear the merkletree 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] 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, ) # 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 # Wait for lock lock expiration but make sure app0 never processes LockExpired with dont_handle_lock_expired_mock(app0): wait_for_block( raiden=app0.raiden, block_number=channel.get_sender_expiration_threshold(lock) + 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_identifier( 0, 'latest') 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
def test_refund_transfer( 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, ): """A failed transfer must send a refund back. TODO: - Unlock the token on refund #1091 - Clear the merkletree 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] 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, ) # 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 # Wait for lock lock expiration but make sure app0 never processes LockExpired with dont_handle_lock_expired_mock(app0): wait_for_block( raiden=app0.raiden, block_number=channel.get_sender_expiration_threshold(lock) + 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_must_contain_entry( 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_identifier(0, 'latest') assert not must_contain_entry( 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