def test_channel_withdraw_expired( raiden_network: List[App], network_wait: float, number_of_nodes: int, token_addresses: List[TokenAddress], deposit: TokenAmount, retry_timeout: float, ) -> None: """ Tests withdraw expiration. """ alice_app, bob_app = raiden_network token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(alice_app), alice_app.raiden.default_registry.address, token_address ) assert token_network_address msg = "hold event handler necessary to control messages" assert isinstance(alice_app.raiden.raiden_event_handler, HoldRaidenEventHandler), msg assert isinstance(alice_app.raiden.message_handler, WaitForMessage), msg msg = "hold event handler necessary to control messages" assert isinstance(bob_app.raiden.raiden_event_handler, HoldRaidenEventHandler), msg assert isinstance(bob_app.raiden.message_handler, WaitForMessage), msg # Prevent withdraw confirmation from being sent send_withdraw_confirmation_event = alice_app.raiden.raiden_event_handler.hold( SendWithdrawConfirmation, {} ) alice_to_bob_amount = PaymentAmount(10) total_withdraw = WithdrawAmount(deposit + alice_to_bob_amount) wait_for_withdraw_expired_message = alice_app.raiden.message_handler.wait_for_message( WithdrawExpired, {"total_withdraw": total_withdraw} ) identifier = PaymentID(1) target = TargetAddress(bob_app.raiden.address) secret = factories.make_secret() payment_status = alice_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=alice_to_bob_amount, target=target, identifier=identifier, secret=secret, ) wait_for_unlock = bob_app.raiden.message_handler.wait_for_message( Unlock, {"payment_identifier": identifier} ) with block_offset_timeout(alice_app.raiden): wait_for_unlock.get() msg = ( f"transfer from {to_checksum_address(alice_app.raiden.address)} " f"to {to_checksum_address(bob_app.raiden.address)} failed." ) assert payment_status.payment_done.get(), msg bob_alice_channel_state = get_channelstate(bob_app, alice_app, token_network_address) bob_app.raiden.withdraw( canonical_identifier=bob_alice_channel_state.canonical_identifier, total_withdraw=total_withdraw, ) with block_offset_timeout(bob_app.raiden): send_withdraw_confirmation_event.wait() # Make sure proper withdraw state is set in both channel states bob_alice_channel_state = get_channelstate(bob_app, alice_app, token_network_address) assert bob_alice_channel_state.our_total_withdraw == total_withdraw assert bob_alice_channel_state.our_state.withdraws_pending.get(total_withdraw) is not None alice_bob_channel_state = get_channelstate(alice_app, bob_app, token_network_address) assert alice_bob_channel_state.partner_total_withdraw == total_withdraw assert alice_bob_channel_state.partner_state.withdraws_pending.get(total_withdraw) is not None withdraw_expiration = bob_alice_channel_state.our_state.withdraws_pending[ total_withdraw ].expiration expiration_threshold = channel.get_sender_expiration_threshold(withdraw_expiration) waiting.wait_for_block( raiden=bob_app.raiden, block_number=BlockNumber(expiration_threshold + 1), retry_timeout=retry_timeout, ) bob_alice_channel_state = get_channelstate(bob_app, alice_app, token_network_address) assert bob_alice_channel_state.our_total_withdraw == 0 assert bob_alice_channel_state.our_state.withdraws_pending.get(total_withdraw) is None with gevent.Timeout(network_wait * number_of_nodes): wait_for_withdraw_expired_message.wait() alice_bob_channel_state = get_channelstate(alice_app, bob_app, token_network_address) assert alice_bob_channel_state.partner_total_withdraw == 0 assert alice_bob_channel_state.partner_state.withdraws_pending.get(total_withdraw) is None
def test_lock_expiry( raiden_network: List[App], token_addresses: List[TokenAddress], deposit: TokenAmount ) -> None: """Test lock expiry and removal.""" alice_app, bob_app = raiden_network token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(alice_app), alice_app.raiden.default_registry.address, token_address ) assert token_network_address hold_event_handler = bob_app.raiden.raiden_event_handler wait_message_handler = bob_app.raiden.message_handler msg = "hold event handler necessary to control messages" assert isinstance(hold_event_handler, HoldRaidenEventHandler), msg assert isinstance(wait_message_handler, WaitForMessage), msg token_network = views.get_token_network_by_address( views.state_from_app(alice_app), token_network_address ) assert token_network channel_state = get_channelstate(alice_app, bob_app, token_network_address) channel_identifier = channel_state.identifier assert ( channel_identifier in token_network.partneraddresses_to_channelidentifiers[bob_app.raiden.address] ) alice_to_bob_amount = PaymentAmount(10) identifier = factories.make_payment_id() target = TargetAddress(bob_app.raiden.address) transfer_1_secret = factories.make_secret(0) transfer_1_secrethash = sha256_secrethash(transfer_1_secret) transfer_2_secret = factories.make_secret(1) transfer_2_secrethash = sha256_secrethash(transfer_2_secret) hold_event_handler.hold_secretrequest_for(secrethash=transfer_1_secrethash) transfer1_received = wait_message_handler.wait_for_message( LockedTransfer, {"lock": {"secrethash": transfer_1_secrethash}} ) transfer2_received = wait_message_handler.wait_for_message( LockedTransfer, {"lock": {"secrethash": transfer_2_secrethash}} ) remove_expired_lock_received = wait_message_handler.wait_for_message( LockExpired, {"secrethash": transfer_1_secrethash} ) alice_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=alice_to_bob_amount, target=target, identifier=identifier, secret=transfer_1_secret, ) transfer1_received.wait() alice_bob_channel_state = get_channelstate(alice_app, bob_app, token_network_address) lock = channel.get_lock(alice_bob_channel_state.our_state, transfer_1_secrethash) assert lock # This is the current state of the protocol: # # A -> B LockedTransfer # B -> A SecretRequest # - protocol didn't continue assert_synced_channel_state( token_network_address, alice_app, Balance(deposit), [lock], bob_app, Balance(deposit), [] ) # Verify lock is registered in both channel states alice_channel_state = get_channelstate(alice_app, bob_app, token_network_address) assert transfer_1_secrethash in alice_channel_state.our_state.secrethashes_to_lockedlocks bob_channel_state = get_channelstate(bob_app, alice_app, token_network_address) assert transfer_1_secrethash in bob_channel_state.partner_state.secrethashes_to_lockedlocks alice_chain_state = views.state_from_raiden(alice_app.raiden) assert transfer_1_secrethash in alice_chain_state.payment_mapping.secrethashes_to_task remove_expired_lock_received.wait() alice_channel_state = get_channelstate(alice_app, bob_app, token_network_address) assert transfer_1_secrethash not in alice_channel_state.our_state.secrethashes_to_lockedlocks # Verify Bob received the message and processed the LockExpired message bob_channel_state = get_channelstate(bob_app, alice_app, token_network_address) assert transfer_1_secrethash not in bob_channel_state.partner_state.secrethashes_to_lockedlocks alice_chain_state = views.state_from_raiden(alice_app.raiden) assert transfer_1_secrethash not in alice_chain_state.payment_mapping.secrethashes_to_task # Make another transfer alice_to_bob_amount = PaymentAmount(10) identifier = factories.make_payment_id() hold_event_handler.hold_secretrequest_for(secrethash=transfer_2_secrethash) alice_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=alice_to_bob_amount, target=target, identifier=identifier, secret=transfer_2_secret, ) transfer2_received.wait() # Make sure the other transfer still exists alice_chain_state = views.state_from_raiden(alice_app.raiden) assert transfer_2_secrethash in alice_chain_state.payment_mapping.secrethashes_to_task bob_channel_state = get_channelstate(bob_app, alice_app, token_network_address) assert transfer_2_secrethash in bob_channel_state.partner_state.secrethashes_to_lockedlocks
def test_batch_unlock( raiden_network: List[App], token_addresses: List[TokenAddress], secret_registry_address: SecretRegistryAddress, deposit: TokenAmount, ) -> None: """Tests that batch unlock is properly called. This test will start a single incomplete transfer, the secret will be revealed *on-chain*. The node that receives the tokens has to call unlock, the node that doesn't gain anything does nothing. """ alice_app, bob_app = raiden_network alice_address = alice_app.raiden.address bob_address = bob_app.raiden.address token_network_registry_address = alice_app.raiden.default_registry.address token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(alice_app), token_network_registry_address, token_address ) assert token_network_address hold_event_handler = bob_app.raiden.raiden_event_handler assert isinstance(hold_event_handler, HoldRaidenEventHandler) # Take a snapshot early on alice_app.raiden.snapshot() canonical_identifier = get_channelstate( alice_app, bob_app, token_network_address ).canonical_identifier assert is_channel_registered(alice_app, bob_app, canonical_identifier) assert is_channel_registered(bob_app, alice_app, canonical_identifier) token_proxy = alice_app.raiden.proxy_manager.token(token_address, BLOCK_ID_LATEST) alice_initial_balance = token_proxy.balance_of(alice_app.raiden.address) bob_initial_balance = token_proxy.balance_of(bob_app.raiden.address) # Take snapshot before transfer alice_app.raiden.snapshot() alice_to_bob_amount = 10 identifier = 1 secret = Secret(keccak(bob_address)) secrethash = sha256_secrethash(secret) secret_request_event = hold_event_handler.hold_secretrequest_for(secrethash=secrethash) alice_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=PaymentAmount(alice_to_bob_amount), target=TargetAddress(bob_address), identifier=PaymentID(identifier), secret=secret, ) secret_request_event.get() # wait for the messages to be exchanged alice_bob_channel_state = get_channelstate(alice_app, bob_app, token_network_address) lock = channel.get_lock(alice_bob_channel_state.our_state, secrethash) assert lock # This is the current state of the protocol: # # A -> B LockedTransfer # B -> A SecretRequest # - protocol didn't continue assert_synced_channel_state( token_network_address, alice_app, Balance(deposit), [lock], bob_app, Balance(deposit), [] ) # Test WAL restore to return the latest channel state alice_app.raiden.snapshot() our_balance_proof = alice_bob_channel_state.our_state.balance_proof restored_channel_state = channel_state_until_state_change( raiden=alice_app.raiden, canonical_identifier=alice_bob_channel_state.canonical_identifier, state_change_identifier=HIGH_STATECHANGE_ULID, ) assert restored_channel_state our_restored_balance_proof = restored_channel_state.our_state.balance_proof assert our_balance_proof == our_restored_balance_proof # Close the channel before revealing the secret off-chain. This will leave # a pending lock in the channel which has to be unlocked on-chain. # # The token network will emit a ChannelClose event, this will be polled by # both apps and each must start a task for calling settle. RaidenAPI(bob_app.raiden).channel_close( token_network_registry_address, token_address, alice_app.raiden.address ) # The secret has to be registered manually because Bob never learned the # secret. The test is holding the SecretRequest to ensure the off-chain # unlock will not happen and the channel is closed with a pending lock. # # Alternatives would be to hold the unlock messages, or to stop and restart # the apps after the channel is closed. secret_registry_proxy = alice_app.raiden.proxy_manager.secret_registry( secret_registry_address, block_identifier=BLOCK_ID_LATEST ) secret_registry_proxy.register_secret(secret=secret) msg = ( "The lock must still be part of the node state for the test to proceed, " "otherwise there is not unlock to be done." ) assert lock, msg msg = ( "The secret must be registered before the lock expires, in order for " "the unlock to happen on-chain. Otherwise the test will fail on the " "expected balances." ) assert lock.expiration > alice_app.raiden.get_block_number(), msg assert lock.secrethash == sha256_secrethash(secret) waiting.wait_for_settle( alice_app.raiden, token_network_registry_address, token_address, [alice_bob_channel_state.identifier], alice_app.raiden.alarm.sleep_time, ) msg = "The channel_state must not have been cleared, one of the ends has pending locks to do." assert is_channel_registered(alice_app, bob_app, canonical_identifier), msg assert is_channel_registered(bob_app, alice_app, canonical_identifier), msg msg = ( "Timeout while waiting for the unlock to be mined. This may happen if " "transaction is rejected, not mined, or the node's alarm task is " "not running." ) with gevent.Timeout(seconds=30, exception=AssertionError(msg)): # Wait for both nodes (Bob and Alice) to see the on-chain unlock wait_for_batch_unlock( app=alice_app, token_network_address=token_network_address, receiver=bob_address, sender=alice_address, ) wait_for_batch_unlock( app=bob_app, token_network_address=token_network_address, receiver=bob_address, sender=alice_address, ) msg = ( "The nodes have done the unlock, and both ends have seen it, now the " "channel must be cleared" ) assert not is_channel_registered(alice_app, bob_app, canonical_identifier), msg assert not is_channel_registered(bob_app, alice_app, canonical_identifier), msg alice_new_balance = alice_initial_balance + deposit - alice_to_bob_amount bob_new_balance = bob_initial_balance + deposit + alice_to_bob_amount msg = "Unexpected end balance after channel settlement with batch unlock." assert token_proxy.balance_of(alice_app.raiden.address) == alice_new_balance, msg assert token_proxy.balance_of(bob_app.raiden.address) == bob_new_balance, msg
def test_pfs_send_capacity_updates_during_mediated_transfer( raiden_network, number_of_nodes, deposit, token_addresses, network_wait): """ Tests that PFSCapacityUpdates and PFSFeeUpdates are being sent during a mediated transfer. """ 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) # There have been two PFSCapacityUpdates and two PFSFeeUpdates per channel per node assert len(get_messages(app0)) == 4 # The mediator has two channels assert len(get_messages(app1)) == 8 assert len(get_messages(app2)) == 4 # Reset message lists for more understandable assertions reset_messages(app0) reset_messages(app1) reset_messages(app2) amount = PaymentAmount(10) secrethash = transfer( initiator_app=app0, target_app=app2, token_address=token_address, amount=amount, identifier=PaymentID(1), timeout=network_wait * number_of_nodes, ) with block_timeout_for_transfer_by_secrethash(app1.raiden, secrethash): wait_assert( assert_succeeding_transfer_invariants, token_network_address, app0, deposit - amount, [], app1, deposit + amount, [], ) with block_timeout_for_transfer_by_secrethash(app1.raiden, secrethash): wait_assert( assert_succeeding_transfer_invariants, token_network_address, app1, deposit - amount, [], app2, deposit + amount, [], ) # Initiator: we expect one PFSCapacityUpdate when locking and one when unlocking messages0 = get_messages(app0) assert len(messages0) == 2 assert len([x for x in messages0 if isinstance(x, PFSCapacityUpdate)]) == 2 assert len([x for x in messages0 if isinstance(x, PFSFeeUpdate)]) == 0 # Mediator: # incoming channel: we expect one PFSCapacityUpdate when locking and one when unlocking # outgoing channel: we expect one PFSCapacityUpdate when funds are unlocked messages1 = get_messages(app1) assert len(messages1) == 3 assert len([x for x in messages1 if isinstance(x, PFSCapacityUpdate)]) == 3 assert len([x for x in messages1 if isinstance(x, PFSFeeUpdate)]) == 0 # Target: we expect one PFSCapacityUpdate when funds are unlocked messages2 = get_messages(app2) assert len(messages2) == 1 assert len([x for x in messages2 if isinstance(x, PFSCapacityUpdate)]) == 1 assert len([x for x in messages2 if isinstance(x, PFSFeeUpdate)]) == 0
def test_settle_is_automatically_called( raiden_network: List[App], token_addresses: List[TokenAddress] ) -> None: """Settle is automatically called by one of the nodes.""" app0, app1 = raiden_network registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token_address ) assert token_network_address token_network = views.get_token_network_by_address( views.state_from_app(app0), token_network_address ) assert token_network channel_identifier = get_channelstate(app0, app1, token_network_address).identifier assert ( channel_identifier in token_network.partneraddresses_to_channelidentifiers[app1.raiden.address] ) # A ChannelClose event will be generated, this will be polled by both apps # and each must start a task for calling settle RaidenAPI(app1.raiden).channel_close(registry_address, token_address, app0.raiden.address) waiting.wait_for_close( app0.raiden, registry_address, token_address, [channel_identifier], app0.raiden.alarm.sleep_time, ) channel_state = views.get_channelstate_for( views.state_from_raiden(app0.raiden), registry_address, token_address, app1.raiden.address ) assert channel_state assert channel_state.close_transaction assert channel_state.close_transaction.finished_block_number waiting.wait_for_settle( app0.raiden, registry_address, token_address, [channel_identifier], app0.raiden.alarm.sleep_time, ) token_network = views.get_token_network_by_address( views.state_from_app(app0), token_network_address ) assert token_network assert ( channel_identifier not in token_network.partneraddresses_to_channelidentifiers[app1.raiden.address] ) assert app0.raiden.wal, MSG_BLOCKCHAIN_EVENTS assert app0.raiden.alarm, MSG_BLOCKCHAIN_EVENTS state_changes = app0.raiden.wal.storage.get_statechanges_by_range(RANGE_ALL_STATE_CHANGES) assert search_for_item( state_changes, ContractReceiveChannelClosed, { "token_network_address": token_network_address, "channel_identifier": channel_identifier, "transaction_from": app1.raiden.address, "block_number": channel_state.close_transaction.finished_block_number, }, ) assert search_for_item( state_changes, ContractReceiveChannelSettled, {"token_network_address": token_network_address, "channel_identifier": channel_identifier}, )
def test_automatic_dispute( raiden_network: List[App], deposit: TokenAmount, token_addresses: List[TokenAddress] ) -> None: app0, app1 = raiden_network registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token_address ) assert token_network_address channel0 = get_channelstate(app0, app1, token_network_address) token_proxy = app0.raiden.proxy_manager.token(channel0.token_address, BLOCK_ID_LATEST) initial_balance0 = token_proxy.balance_of(app0.raiden.address) initial_balance1 = token_proxy.balance_of(app1.raiden.address) amount0_1 = PaymentAmount(10) transfer( initiator_app=app0, target_app=app1, token_address=token_address, amount=amount0_1, identifier=PaymentID(1), ) amount1_1 = PaymentAmount(50) transfer( initiator_app=app1, target_app=app0, token_address=token_address, amount=amount1_1, identifier=PaymentID(2), ) amount0_2 = PaymentAmount(60) transfer( initiator_app=app0, target_app=app1, token_address=token_address, amount=amount0_2, identifier=PaymentID(3), ) # Alice can only provide one of Bob's transfer, so she is incentivized to # use the one with the largest transferred_amount. RaidenAPI(app0.raiden).channel_close(registry_address, token_address, app1.raiden.address) # Bob needs to provide a transfer otherwise its netted balance will be # wrong, so he is incentivised to use Alice's transfer with the largest # transferred_amount. # # This is done automatically # channel1.external_state.update_transfer( # alice_second_transfer, # ) waiting.wait_for_settle( app0.raiden, registry_address, token_address, [channel0.identifier], app0.raiden.alarm.sleep_time, ) # check that the channel is properly settled and that Bob's client # automatically called updateTransfer() to reflect the actual transactions assert token_proxy.balance_of(Address(token_network_address)) == 0 total0 = amount0_1 + amount0_2 total1 = amount1_1 expected_balance0 = initial_balance0 + deposit - total0 + total1 expected_balance1 = initial_balance1 + deposit + total0 - total1 assert token_proxy.balance_of(app0.raiden.address) == expected_balance0 assert token_proxy.balance_of(app1.raiden.address) == expected_balance1
def test_secret_revealed_on_chain(raiden_chain, deposit, settle_timeout, token_addresses, retry_interval): """ A node must reveal the secret on-chain if it's known and the channel is closed. """ app0, app1, app2 = raiden_chain token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token_address) assert token_network_address amount = 10 identifier = 1 target = app2.raiden.address secret, secrethash = factories.make_secret_with_hash() # Reveal the secret, but do not unlock it off-chain app1_hold_event_handler = app1.raiden.raiden_event_handler app1_hold_event_handler.hold_unlock_for(secrethash=secrethash) app0.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=amount, target=target, identifier=identifier, secret=secret, ) with watch_for_unlock_failures(*raiden_chain), gevent.Timeout(10): wait_for_state_change(app2.raiden, ReceiveSecretReveal, {"secrethash": secrethash}, retry_interval) channel_state2_1 = get_channelstate(app2, app1, token_network_address) pending_lock = channel_state2_1.partner_state.secrethashes_to_unlockedlocks.get( secrethash) msg = "The lock must be registered in unlocked locks since the secret is known" assert pending_lock is not None, msg # The channels are out-of-sync. app1 has sent the unlock, however we are # intercepting it and app2 has not received the updated balance proof # Close the channel. This must register the secret on chain channel_close_event = ContractSendChannelClose( # type: ignore canonical_identifier=channel_state2_1.canonical_identifier, balance_proof=channel_state2_1.partner_state.balance_proof, triggered_by_block_hash=app0.raiden.rpc_client. blockhash_from_blocknumber("latest"), ) current_state = app2.raiden.wal.state_manager.current_state app2.raiden.raiden_event_handler.on_raiden_event(raiden=app2.raiden, chain_state=current_state, event=channel_close_event) settle_expiration = (app0.raiden.rpc_client.block_number() + settle_timeout + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS) app0.raiden.proxy_manager.client.wait_until_block( target_block_number=settle_expiration) # TODO: # - assert on the transferred amounts on-chain (for settle and unlock) # The channel app0-app1 should continue with the protocol off-chain, once # the secret is released on-chain by app2 assert_synced_channel_state(token_network_address, app0, deposit - amount, [], app1, deposit + amount, []) with watch_for_unlock_failures(*raiden_chain), gevent.Timeout(10): wait_for_state_change(app2.raiden, ContractReceiveSecretReveal, {"secrethash": secrethash}, retry_interval)
def test_raidenapi_channel_lifecycle(raiden_network, token_addresses, deposit, retry_timeout, settle_timeout_max): """Uses RaidenAPI to go through a complete channel lifecycle.""" node1, node2 = raiden_network token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(node1), node1.raiden.default_registry.address, token_address) assert token_network_address api1 = RaidenAPI(node1.raiden) api2 = RaidenAPI(node2.raiden) registry_address = node1.raiden.default_registry.address # nodes don't have a channel, so they are not healthchecking assert api1.get_node_network_state(api2.address) == NetworkState.UNKNOWN assert api2.get_node_network_state(api1.address) == NetworkState.UNKNOWN assert not api1.get_channel_list(registry_address, token_address, api2.address) # Make sure invalid arguments to get_channel_list are caught with pytest.raises(UnknownTokenAddress): api1.get_channel_list(registry_address=registry_address, token_address=None, partner_address=api2.address) address_for_lowest_settle_timeout = make_address() lowest_valid_settle_timeout = node1.raiden.config.reveal_timeout * 2 # Make sure a small settle timeout is not accepted when opening a channel with pytest.raises(InvalidSettleTimeout): api1.channel_open( registry_address=node1.raiden.default_registry.address, token_address=token_address, partner_address=address_for_lowest_settle_timeout, settle_timeout=lowest_valid_settle_timeout - 1, ) # Make sure the smallest settle timeout is accepted api1.channel_open( registry_address=node1.raiden.default_registry.address, token_address=token_address, partner_address=address_for_lowest_settle_timeout, settle_timeout=lowest_valid_settle_timeout, ) address_for_highest_settle_timeout = make_address() highest_valid_settle_timeout = settle_timeout_max # Make sure a large settle timeout is not accepted when opening a channel with pytest.raises(InvalidSettleTimeout): api1.channel_open( registry_address=node1.raiden.default_registry.address, token_address=token_address, partner_address=address_for_highest_settle_timeout, settle_timeout=highest_valid_settle_timeout + 1, ) # Make sure the highest settle timeout is accepted api1.channel_open( registry_address=node1.raiden.default_registry.address, token_address=token_address, partner_address=address_for_highest_settle_timeout, settle_timeout=highest_valid_settle_timeout, ) # open is a synchronous api api1.channel_open(node1.raiden.default_registry.address, token_address, api2.address) channels = api1.get_channel_list(registry_address, token_address, api2.address) assert len(channels) == 1 channel12 = get_channelstate(node1, node2, token_network_address) assert channel.get_status(channel12) == ChannelState.STATE_OPENED channel_event_list1 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address) assert must_have_event( channel_event_list1, { "event": ChannelEvent.OPENED, "args": { "participant1": to_checksum_address(api1.address), "participant2": to_checksum_address(api2.address), }, }, ) network_event_list1 = api1.get_blockchain_events_token_network( token_address) assert must_have_event(network_event_list1, {"event": ChannelEvent.OPENED}) registry_address = api1.raiden.default_registry.address # Check that giving a 0 total deposit is not accepted with pytest.raises(DepositMismatch): api1.set_total_channel_deposit( registry_address=registry_address, token_address=token_address, partner_address=api2.address, total_deposit=TokenAmount(0), ) # Load the new state with the deposit api1.set_total_channel_deposit( registry_address=registry_address, token_address=token_address, partner_address=api2.address, total_deposit=deposit, ) # let's make sure it's idempotent. Same deposit should raise deposit mismatch limit with pytest.raises(DepositMismatch): api1.set_total_channel_deposit(registry_address, token_address, api2.address, deposit) channel12 = get_channelstate(node1, node2, token_network_address) assert channel.get_status(channel12) == ChannelState.STATE_OPENED assert channel.get_balance(channel12.our_state, channel12.partner_state) == deposit assert channel12.our_state.contract_balance == deposit assert api1.get_channel_list(registry_address, token_address, api2.address) == [channel12] # there is a channel open, they must be healthchecking each other assert api1.get_node_network_state(api2.address) == NetworkState.REACHABLE assert api2.get_node_network_state(api1.address) == NetworkState.REACHABLE event_list2 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address) assert must_have_event( event_list2, { "event": ChannelEvent.DEPOSIT, "args": { "participant": to_checksum_address(api1.address), "total_deposit": deposit }, }, ) api1.channel_close(registry_address, token_address, api2.address) # Load the new state with the channel closed channel12 = get_channelstate(node1, node2, token_network_address) event_list3 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address) assert len(event_list3) > len(event_list2) assert must_have_event( event_list3, { "event": ChannelEvent.CLOSED, "args": { "closing_participant": to_checksum_address(api1.address) }, }, ) assert channel.get_status(channel12) == ChannelState.STATE_CLOSED with pytest.raises(UnexpectedChannelState): api1.set_total_channel_deposit(registry_address, token_address, api2.address, deposit + 100) assert wait_for_state_change( node1.raiden, ContractReceiveChannelSettled, { "canonical_identifier": { "token_network_address": token_network_address, "channel_identifier": channel12.identifier, } }, retry_timeout, )
def test_refund_transfer(raiden_chain: List[RaidenService], token_addresses, deposit, retry_timeout): """A failed transfer must send a refund back. TODO: - Unlock the token on refund #1091 """ # Topology: # # 0 -> 1 -> 2 # app0, app1, app2 = raiden_chain token_address = token_addresses[0] token_network_registry_address = app0.default_registry.address token_network_address = views.get_token_network_address_by_token_address( views.state_from_raiden(app0), token_network_registry_address, token_address) assert token_network_address, "token_address must be registered by the fixtures." # make a transfer to test the path app0 -> app1 -> app2 identifier_path = PaymentID(1) amount_path = PaymentAmount(1) with block_offset_timeout(app0): transfer( initiator_app=app0, target_app=app2, token_address=token_address, amount=amount_path, identifier=identifier_path, routes=[[app0.address, app1.address, app2.address]], ) # drain the channel app1 -> app2 identifier_drain = PaymentID(2) amount_drain = PaymentAmount(deposit * 8 // 10) transfer_timeout = block_offset_timeout(app1) with transfer_timeout: transfer( initiator_app=app1, target_app=app2, token_address=token_address, amount=amount_drain, identifier=identifier_drain, routes=[[app1.address, app2.address]], ) wait_assert( assert_synced_channel_state, token_network_address, app0, deposit - amount_path, [], app1, deposit + amount_path, [], ) wait_assert( assert_synced_channel_state, token_network_address, app1, deposit - amount_path - amount_drain, [], app2, deposit + amount_path + amount_drain, [], ) # app0 -> app1 -> app2 is the only available path, but the channel app1 -> # app2 doesn't have capacity, so a refund will be sent on app1 -> app0 identifier_refund = PaymentID(3) amount_refund = PaymentAmount(50) fee = calculate_fee_for_amount(amount_refund) fee_margin = calculate_fee_margin(amount_refund, fee) amount_refund_with_fees = amount_refund + fee + fee_margin payment_status = app0.mediated_transfer_async( token_network_address=token_network_address, amount=amount_refund, target=TargetAddress(app2.address), identifier=identifier_refund, route_states=[ create_route_state_for_route( apps=raiden_chain, token_address=token_address, fee_estimate=FeeAmount( round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount_refund)), ) ], ) msg = "there is no path with capacity, the transfer must fail" assert isinstance(payment_status.payment_done.wait(), EventPaymentSentFailed), msg # A lock structure with the correct amount send_locked = raiden_events_search_for_item( app0, SendLockedTransfer, {"transfer": { "lock": { "amount": amount_refund_with_fees } }}, ) assert send_locked secrethash = send_locked.transfer.lock.secrethash send_refund = raiden_events_search_for_item(app1, SendRefundTransfer, {}) assert send_refund lock = send_locked.transfer.lock refund_lock = send_refund.transfer.lock assert lock.amount == refund_lock.amount assert lock.secrethash assert lock.expiration assert lock.secrethash == refund_lock.secrethash # Both channels have the amount locked because of the refund message with transfer_timeout: wait_assert( assert_synced_channel_state, token_network_address, app0, deposit - amount_path, [lock], app1, deposit + amount_path, [refund_lock], ) wait_assert( assert_synced_channel_state, token_network_address, app1, deposit - amount_path - amount_drain, [], app2, deposit + amount_path + amount_drain, [], ) # Additional checks for LockExpired causing nonce mismatch after refund transfer: # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046 # At this point make sure that the initiator has not deleted the payment task assert secrethash in state_from_raiden( app0).payment_mapping.secrethashes_to_task # Wait for lock lock expiration but make sure app0 never processes LockExpired with dont_handle_lock_expired_mock(app0): wait_for_block( raiden=app0, block_number=BlockNumber( channel.get_sender_expiration_threshold(lock.expiration) + 1), retry_timeout=retry_timeout, ) # make sure that app0 still has the payment task for the secrethash # https://github.com/raiden-network/raiden/issues/3183 assert secrethash in state_from_raiden( app0).payment_mapping.secrethashes_to_task # make sure that app1 sent a lock expired message for the secrethash send_lock_expired = raiden_events_search_for_item( app1, SendLockExpired, {"secrethash": secrethash}) assert send_lock_expired assert app0.wal, "test apps must be started by the fixtures." state_changes = app0.wal.storage.get_statechanges_by_range( RANGE_ALL_STATE_CHANGES) # make sure that app0 never got it assert not search_for_item(state_changes, ReceiveLockExpired, {"secrethash": secrethash}) # Out of the handicapped app0 transport. # Now wait till app0 receives and processes LockExpired receive_lock_expired = wait_for_state_change(app0, ReceiveLockExpired, {"secrethash": secrethash}, retry_timeout) # And also till app1 received the processed wait_for_state_change( app1, ReceiveProcessed, {"message_identifier": receive_lock_expired.message_identifier}, retry_timeout, ) # make sure app1 queue has cleared the SendLockExpired chain_state1 = views.state_from_raiden(app1) queues1 = views.get_all_messagequeues(chain_state=chain_state1) result = [(queue_id, queue) for queue_id, queue in queues1.items() if queue_id.recipient == app0.address and queue] assert not result # and now wait for 1 more block so that the payment task can be deleted wait_for_block( raiden=app0, block_number=BlockNumber(app0.get_block_number() + 1), retry_timeout=retry_timeout, ) # and since the lock expired message has been sent and processed then the # payment task should have been deleted from both nodes # https://github.com/raiden-network/raiden/issues/3183 assert secrethash not in state_from_raiden( app0).payment_mapping.secrethashes_to_task assert secrethash not in state_from_raiden( app1).payment_mapping.secrethashes_to_task
def test_connect_does_not_open_channels_with_offline_nodes( raiden_network, token_addresses): """ Test that using the connection manager to connect to a token network does not open channels with offline nodes Test for https://github.com/raiden-network/raiden/issues/5583 """ # pylint: disable=too-many-locals registry_address = raiden_network[0].raiden.default_registry.address token_address = token_addresses[0] app0, app1, _, _ = raiden_network offline_node = app0 target_channels_num = 1 # connect the first node - this will register the token and open the first channel # Since there is no other nodes available to connect to this call will do nothing more RaidenAPI(app0.raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(100), initial_channel_target=target_channels_num, ) # First node will now go offline offline_node.stop() offline_node.raiden.greenlet.get() assert not offline_node.raiden # Call the connect endpoint for the second node RaidenAPI(app1.raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(100), initial_channel_target=target_channels_num, ) token_network_registry_address = views.get_token_network_address_by_token_address( views.state_from_raiden(app1.raiden), token_network_registry_address=registry_address, token_address=token_address, ) # and wait until connections are done. This should connect to an offline node # and create the first online discoverable manager = app1.raiden.connection_manager_for_token_network( token_network_registry_address) exception = AssertionError("Unsaturated connection manager", manager) with gevent.Timeout(120, exception): if not is_manager_saturated(manager, registry_address, token_address): gevent.sleep(1) # Call the connect endpoint for all but the two first nodes connect_greenlets = set( gevent.spawn( RaidenAPI(app.raiden).token_network_connect, registry_address, token_address, 100, target_channels_num, ) for app in raiden_network[2:]) gevent.joinall(connect_greenlets, raise_error=True) connection_managers = [ app.raiden.connection_manager_for_token_network( token_network_registry_address) for app in raiden_network[2:] ] # Wait until channels are opened and connections are done unsaturated_connection_managers = connection_managers[2:] exception = AssertionError("Unsaturated connection managers", unsaturated_connection_managers) with gevent.Timeout(120, exception): while unsaturated_connection_managers: for manager in unsaturated_connection_managers: if is_manager_saturated(manager, registry_address, token_address): unsaturated_connection_managers.remove(manager) gevent.sleep(1) assert saturated_count(connection_managers, registry_address, token_address) == len(connection_managers) for app in raiden_network[2:]: # ensure that we did not open a channel with the offline node node_state = views.state_from_raiden(app.raiden) network_state = views.get_token_network_by_token_address( node_state, registry_address, token_address) assert network_state is not None msg = "Each of the last 2 nodes should connect to 1 address" assert len(network_state.channelidentifiers_to_channels) == 1, msg for _, netchannel in network_state.channelidentifiers_to_channels.items( ): assert netchannel.partner_state.address != offline_node.raiden.address # Call the connect endpoint for all apps again to see this is handled fine. # This essentially checks that connecting to bootstrap/offline address again is not a problem for app in raiden_network[1:]: RaidenAPI(app.raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(100), initial_channel_target=target_channels_num, )
def test_participant_selection(raiden_network, token_addresses): # pylint: disable=too-many-locals registry_address = raiden_network[0].raiden.default_registry.address one_to_n_address = raiden_network[0].raiden.default_one_to_n_address token_address = token_addresses[0] # connect the first node - this will register the token and open the first channel # Since there is no other nodes available to connect to this call will do nothing more RaidenAPI(raiden_network[0].raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(100)) # Test invalid argument values with pytest.raises(InvalidAmount): RaidenAPI(raiden_network[0].raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(-1)) with pytest.raises(InvalidAmount): RaidenAPI(raiden_network[0].raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(100), joinable_funds_target=2, ) with pytest.raises(InvalidAmount): RaidenAPI(raiden_network[0].raiden).token_network_connect( registry_address=registry_address, token_address=token_address, funds=TokenAmount(100), joinable_funds_target=-1, ) # Call the connect endpoint for all but the first node connect_greenlets = set( gevent.spawn( RaidenAPI(app.raiden).token_network_connect, registry_address, token_address, 100) for app in raiden_network[1:]) gevent.joinall(connect_greenlets, raise_error=True) token_network_registry_address = views.get_token_network_address_by_token_address( views.state_from_raiden(raiden_network[0].raiden), token_network_registry_address=registry_address, token_address=token_address, ) connection_managers = [ app.raiden.connection_manager_for_token_network( token_network_registry_address) for app in raiden_network ] unsaturated_connection_managers = connection_managers[:] exception = AssertionError("Unsaturated connection managers", unsaturated_connection_managers) with gevent.Timeout(120, exception): while unsaturated_connection_managers: for manager in unsaturated_connection_managers: if is_manager_saturated(manager, registry_address, token_address): unsaturated_connection_managers.remove(manager) gevent.sleep(1) assert saturated_count(connection_managers, registry_address, token_address) == len(connection_managers) # ensure unpartitioned network for app in raiden_network: node_state = views.state_from_raiden(app.raiden) network_state = views.get_token_network_by_token_address( node_state, registry_address, token_address) assert network_state is not None for target in raiden_network: if target.raiden.address == app.raiden.address: continue _, routes, _ = routing.get_best_routes( chain_state=node_state, token_network_address=network_state.address, one_to_n_address=one_to_n_address, from_address=app.raiden.address, to_address=target.raiden.address, amount=PaymentAmount(1), previous_address=None, pfs_config=None, privkey=b"", # not used if pfs is not configured ) assert routes is not None # create a transfer to the leaving node, so we have a channel to settle for app in raiden_network: sender = app.raiden sender_channel = next( (channel_state for channel_state in RaidenAPI( sender).get_channel_list(registry_address=registry_address, token_address=token_address) if channel_state.our_state.contract_balance > 0 and channel_state.partner_state.contract_balance > 0), None, ) # choose a fully funded channel from sender if sender_channel: break assert sender_channel registry_address = sender.default_registry.address receiver = next( app.raiden for app in raiden_network if app.raiden.address == sender_channel.partner_state.address) # assert there is a direct channel receiver -> sender (vv) receiver_channel = RaidenAPI(receiver).get_channel_list( registry_address=registry_address, token_address=token_address, partner_address=sender.address, ) assert len(receiver_channel) == 1 with gevent.Timeout(30, exception=ValueError("partner not reachable")): waiting.wait_for_healthy(sender, receiver.address, PaymentAmount(1)) with watch_for_unlock_failures(*raiden_network): amount = PaymentAmount(1) RaidenAPI(sender).transfer_and_wait(registry_address, token_address, amount, receiver.address, transfer_timeout=10) with gevent.Timeout( 30, exception=ValueError( "timeout while waiting for incoming transaction")): wait_for_transaction(receiver, registry_address, token_address, sender.address) # test `leave()` method connection_manager = connection_managers[0] raiden = connection_manager.raiden blocks = BlockOffset(sender_channel.settle_timeout * 10) channels = views.list_channelstate_for_tokennetwork( chain_state=views.state_from_raiden(connection_manager.raiden), token_network_registry_address=registry_address, token_address=token_address, ) channel_identifiers = [channel.identifier for channel in channels] timeout = block_offset_timeout(raiden, "Timeout while waiting for leave", blocks) with timeout: RaidenAPI(sender).token_network_leave(registry_address, token_address) timeout.exception_to_throw = ValueError("Channels not settled in time") waiting.wait_for_settle( raiden=connection_manager.raiden, token_network_registry_address=registry_address, token_address=token_address, channel_ids=channel_identifiers, retry_timeout=0.1, )
def test_raidenapi_channel_lifecycle( raiden_network: List[RaidenService], token_addresses, deposit, retry_timeout, settle_timeout_max, ): """Uses RaidenAPI to go through a complete channel lifecycle.""" app1, app2 = raiden_network token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_raiden(app1), app1.default_registry.address, token_address) assert token_network_address api1 = RaidenAPI(app1) api2 = RaidenAPI(app2) registry_address = app1.default_registry.address # nodes don't have a channel, so they are not healthchecking assert api1.get_node_network_state(api2.address) == NetworkState.UNKNOWN assert api2.get_node_network_state(api1.address) == NetworkState.UNKNOWN assert not api1.get_channel_list(registry_address, token_address, api2.address) # Make sure invalid arguments to get_channel_list are caught with pytest.raises(UnknownTokenAddress): api1.get_channel_list(registry_address=registry_address, token_address=None, partner_address=api2.address) address_for_lowest_settle_timeout = make_address() lowest_valid_settle_timeout = app1.config.reveal_timeout * 2 # Make sure a small settle timeout is not accepted when opening a channel with pytest.raises(InvalidSettleTimeout): api1.channel_open( registry_address=app1.default_registry.address, token_address=token_address, partner_address=address_for_lowest_settle_timeout, settle_timeout=BlockTimeout(lowest_valid_settle_timeout - 1), ) # Make sure the smallest settle timeout is accepted api1.channel_open( registry_address=app1.default_registry.address, token_address=token_address, partner_address=address_for_lowest_settle_timeout, settle_timeout=BlockTimeout(lowest_valid_settle_timeout), ) address_for_highest_settle_timeout = make_address() highest_valid_settle_timeout = settle_timeout_max # Make sure a large settle timeout is not accepted when opening a channel with pytest.raises(InvalidSettleTimeout): api1.channel_open( registry_address=app1.default_registry.address, token_address=token_address, partner_address=address_for_highest_settle_timeout, settle_timeout=highest_valid_settle_timeout + 1, ) # Make sure the highest settle timeout is accepted api1.channel_open( registry_address=app1.default_registry.address, token_address=token_address, partner_address=address_for_highest_settle_timeout, settle_timeout=highest_valid_settle_timeout, ) # open is a synchronous api api1.channel_open(app1.default_registry.address, token_address, api2.address) channels = api1.get_channel_list(registry_address, token_address, api2.address) assert len(channels) == 1 channel12 = get_channelstate(app1, app2, token_network_address) assert channel.get_status(channel12) == ChannelState.STATE_OPENED registry_address = api1.raiden.default_registry.address # Check that giving a 0 total deposit is not accepted with pytest.raises(DepositMismatch): api1.set_total_channel_deposit( registry_address=registry_address, token_address=token_address, partner_address=api2.address, total_deposit=TokenAmount(0), ) # Load the new state with the deposit api1.set_total_channel_deposit( registry_address=registry_address, token_address=token_address, partner_address=api2.address, total_deposit=deposit, ) # let's make sure it's idempotent. Same deposit should raise deposit mismatch limit with pytest.raises(DepositMismatch): api1.set_total_channel_deposit(registry_address, token_address, api2.address, deposit) channel12 = get_channelstate(app1, app2, token_network_address) assert channel.get_status(channel12) == ChannelState.STATE_OPENED assert channel.get_balance(channel12.our_state, channel12.partner_state) == deposit assert channel12.our_state.contract_balance == deposit assert api1.get_channel_list(registry_address, token_address, api2.address) == [channel12] # there is a channel open, they must be healthchecking each other assert api1.get_node_network_state(api2.address) == NetworkState.REACHABLE assert api2.get_node_network_state(api1.address) == NetworkState.REACHABLE api1.channel_close(registry_address, token_address, api2.address) # Load the new state with the channel closed channel12 = get_channelstate(app1, app2, token_network_address) assert channel.get_status(channel12) == ChannelState.STATE_CLOSED with pytest.raises(UnexpectedChannelState): api1.set_total_channel_deposit(registry_address, token_address, api2.address, deposit + 100) assert wait_for_state_change( app1, ContractReceiveChannelSettled, { "canonical_identifier": { "token_network_address": token_network_address, "channel_identifier": channel12.identifier, } }, retry_timeout, )
def test_channel_withdraw( raiden_network: List[RaidenService], token_addresses: List[TokenAddress], deposit: TokenAmount, retry_timeout: float, ) -> None: """ Withdraw funds after a mediated transfer.""" alice_app, bob_app = raiden_network token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_raiden(alice_app), alice_app.default_registry.address, token_address, ) assert token_network_address token_proxy = bob_app.proxy_manager.token(token_address, BLOCK_ID_LATEST) bob_initial_balance = token_proxy.balance_of(bob_app.address) message_handler = WaitForMessage() bob_app.message_handler = message_handler alice_to_bob_amount = PaymentAmount(10) identifier = PaymentID(1) target = TargetAddress(bob_app.address) secret = factories.make_secret() payment_status = alice_app.mediated_transfer_async( token_network_address=token_network_address, amount=alice_to_bob_amount, target=target, identifier=identifier, secret=secret, ) wait_for_unlock = bob_app.message_handler.wait_for_message( Unlock, {"payment_identifier": identifier} ) with block_offset_timeout(alice_app): wait_for_unlock.get() msg = ( f"transfer from {to_checksum_address(alice_app.address)} " f"to {to_checksum_address(bob_app.address)} failed." ) assert payment_status.payment_done.get(), msg total_withdraw = WithdrawAmount(deposit + alice_to_bob_amount) bob_alice_channel_state = get_channelstate(bob_app, alice_app, token_network_address) bob_app.withdraw( canonical_identifier=bob_alice_channel_state.canonical_identifier, total_withdraw=total_withdraw, ) waiting.wait_for_withdraw_complete( raiden=bob_app, canonical_identifier=bob_alice_channel_state.canonical_identifier, total_withdraw=total_withdraw, retry_timeout=retry_timeout, ) bob_balance_after_withdraw = token_proxy.balance_of(bob_app.address) assert bob_initial_balance + total_withdraw == bob_balance_after_withdraw
def transfer_and_assert_path( path: List[App], token_address: TokenAddress, amount: PaymentAmount, identifier: PaymentID, timeout: float = 10, ) -> SecretHash: """ Nice to read shortcut to make successful LockedTransfer. Note: This utility *does not enforce the path*, however it does check the provided path is used in totality. It's the responsability of the caller to ensure the path will be used. All nodes in `path` are synched. """ assert identifier is not None, "The identifier must be provided" secret, secrethash = make_secret_with_hash() first_app = path[0] token_network_registry_address = first_app.raiden.default_registry.address token_network_address = views.get_token_network_address_by_token_address( chain_state=views.state_from_app(first_app), token_network_registry_address=token_network_registry_address, token_address=token_address, ) assert token_network_address for app in path: assert isinstance(app.raiden.message_handler, WaitForMessage) msg = "The apps must be on the same token network registry" assert app.raiden.default_registry.address == token_network_registry_address, msg app_token_network_address = views.get_token_network_address_by_token_address( chain_state=views.state_from_app(app), token_network_registry_address=token_network_registry_address, token_address=token_address, ) msg = "The apps must be synchronized with the blockchain" assert token_network_address == app_token_network_address, msg pairs = zip(path[:-1], path[1:]) receiving = list() for from_app, to_app in pairs: from_channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=views.state_from_app(from_app), token_network_address=token_network_address, partner_address=to_app.raiden.address, ) to_channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=views.state_from_app(to_app), token_network_address=token_network_address, partner_address=from_app.raiden.address, ) msg = ( f"{to_checksum_address(from_app.raiden.address)} does not have a channel with " f"{to_checksum_address(to_app.raiden.address)} needed to transfer through the " f"path {[to_checksum_address(app.raiden.address) for app in path]}." ) assert from_channel_state, msg assert to_channel_state, msg msg = ( f"channel among {to_checksum_address(from_app.raiden.address)} and " f"{to_checksum_address(to_app.raiden.address)} must be open to be used for a " f"transfer" ) assert channel.get_status(from_channel_state) == ChannelState.STATE_OPENED, msg assert channel.get_status(to_channel_state) == ChannelState.STATE_OPENED, msg receiving.append((to_app, to_channel_state.identifier)) assert isinstance(app.raiden.message_handler, WaitForMessage) results = set( app.raiden.message_handler.wait_for_message( Unlock, { "channel_identifier": channel_identifier, "token_network_address": token_network_address, "payment_identifier": identifier, "secret": secret, }, ) for app, channel_identifier in receiving ) last_app = path[-1] payment_status = first_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=amount, target=TargetAddress(last_app.raiden.address), identifier=identifier, secret=secret, ) msg = ( f"transfer from {to_checksum_address(first_app.raiden.address)} " f"to {to_checksum_address(last_app.raiden.address)} for amount " f"{amount} failed" ) exception = RuntimeError(msg + " due to Timeout") with watch_for_unlock_failures(*path): with Timeout(seconds=timeout, exception=exception): gevent.joinall(results, raise_error=True) assert payment_status.payment_done.get(), msg return secrethash
def test_settled_lock( token_addresses: List[TokenAddress], raiden_network: List[App], deposit: TokenAmount ) -> None: """ Any transfer following a secret reveal must update the locksroot, so that an attacker cannot reuse a secret to double claim a lock. """ app0, app1 = raiden_network registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] amount = PaymentAmount(30) token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token_address ) assert token_network_address hold_event_handler = app1.raiden.raiden_event_handler msg = "hold event handler necessary to control messages" assert isinstance(hold_event_handler, HoldRaidenEventHandler), msg address0 = app0.raiden.address address1 = app1.raiden.address deposit0 = deposit deposit1 = deposit token_proxy = app0.raiden.proxy_manager.token(token_address, BLOCK_ID_LATEST) initial_balance0 = token_proxy.balance_of(address0) initial_balance1 = token_proxy.balance_of(address1) identifier = factories.make_payment_id() target = TargetAddress(app1.raiden.address) secret = factories.make_secret() secrethash = sha256_secrethash(secret) secret_available = hold_event_handler.hold_secretrequest_for(secrethash=secrethash) app0.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=amount, target=target, identifier=identifier, secret=secret, ) secret_available.wait() # wait for the messages to be exchanged # Save the pending locks from the pending transfer, used to test the unlock channelstate_0_1 = get_channelstate(app0, app1, token_network_address) batch_unlock = channel.get_batch_unlock(channelstate_0_1.our_state) assert batch_unlock hold_event_handler.release_secretrequest_for(app1.raiden, secrethash) transfer( initiator_app=app0, target_app=app1, token_address=token_address, amount=amount, identifier=PaymentID(2), ) # The channel state has to be recovered before the settlement, otherwise # the object is cleared from the node's state. channelstate_1_0 = get_channelstate(app1, app0, token_network_address) RaidenAPI(app1.raiden).channel_close(registry_address, token_address, app0.raiden.address) waiting.wait_for_settle( app1.raiden, app1.raiden.default_registry.address, token_address, [channelstate_0_1.identifier], app1.raiden.alarm.sleep_time, ) current_block = app0.raiden.rpc_client.block_number() netting_channel = app1.raiden.proxy_manager.payment_channel( channel_state=channelstate_1_0, block_identifier=BLOCK_ID_LATEST ) # The transfer locksroot must not contain the unlocked lock, the # unlock must fail. with pytest.raises(RaidenUnrecoverableError): netting_channel.unlock( sender=channelstate_0_1.our_state.address, receiver=channelstate_0_1.partner_state.address, pending_locks=batch_unlock, given_block_identifier=current_block, ) expected_balance0 = initial_balance0 + deposit0 - amount * 2 expected_balance1 = initial_balance1 + deposit1 + amount * 2 assert token_proxy.balance_of(address0) == expected_balance0 assert token_proxy.balance_of(address1) == expected_balance1
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
def test_start_end_attack( token_addresses: List[TokenAddress], raiden_chain: List[App], deposit: List[App] ) -> None: """ An attacker can try to steal tokens from a hub or the last node in a path. The attacker needs to use two addresses (A1 and A2) and connect both to the hub H. Once connected a mediated transfer is initialized from A1 to A2 through H. Once the node A2 receives the mediated transfer the attacker uses the known secret and reveal to close and settle the channel H-A2, without revealing the secret to H's raiden node. The intention is to make the hub transfer the token but for him to be unable to require the token A1.""" token = token_addresses[0] app0, app1, app2 = raiden_chain # pylint: disable=unbalanced-tuple-unpacking token_network_address = views.get_token_network_address_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token ) assert token_network_address hold_event_handler = app2.raiden.raiden_event_handler msg = "hold event handler necessary to control messages" assert isinstance(hold_event_handler, HoldRaidenEventHandler), msg # the attacker owns app0 and app2 and creates a transfer through app1 amount = PaymentAmount(30) identifier = PaymentID(1) target = TargetAddress(app2.raiden.address) secret, secrethash = factories.make_secret_with_hash() hold_event_handler.hold_secretrequest_for(secrethash=secrethash) app0.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=amount, target=target, identifier=identifier, secret=secret, ) attack_channel = get_channelstate(app2, app1, token_network_address) attack_transfer = None # TODO attack_contract = attack_channel.external_state.netting_channel.address # type: ignore hub_contract = get_channelstate( # type: ignore app1, app0, token_network_address ).external_state.netting_channel.address # start the settle counter attack_balance_proof = attack_transfer.to_balanceproof() # type: ignore attack_channel.netting_channel.channel_close(attack_balance_proof) # type: ignore # wait until the last block to reveal the secret, hopefully we are not # missing a block during the test assert attack_transfer app2.raiden.rpc_client.wait_until_block( target_block_number=attack_transfer.lock.expiration - 1 ) # since the attacker knows the secret he can net the lock # <the commented code below is left for documentation purposes> # attack_channel.netting_channel.unlock( # UnlockProofState(unlock_proof, attack_transfer.lock, secret) # ) # XXX: verify that the secret was publicized # at this point the hub might not know the secret yet, and won't be able to # claim the token from the channel A1 - H # the attacker settles the contract app2.raiden.rpc_client.wait_until_block( target_block_number=app2.raiden.rpc_client.block_number() + 1 ) attack_channel.netting_channel.settle(token, attack_contract) # at this point the attacker has the "stolen" funds attack_contract = app2.raiden.proxy_manager.token_hashchannel[token][attack_contract] assert attack_contract.participants[app2.raiden.address]["netted"] == deposit + amount assert attack_contract.participants[app1.raiden.address]["netted"] == deposit - amount # and the hub's channel A1-H doesn't hub_contract = app1.raiden.proxy_manager.token_hashchannel[token][hub_contract] assert hub_contract.participants[app0.raiden.address]["netted"] == deposit assert hub_contract.participants[app1.raiden.address]["netted"] == deposit # to mitigate the attack the Hub _needs_ to use a lower expiration for the # locked transfer between H-A2 than A1-H. For A2 to acquire the token # it needs to make the secret public in the blockchain so it publishes the # secret through an event and the Hub is able to require its funds app1.raiden.rpc_client.wait_until_block( target_block_number=app1.raiden.rpc_client.block_number() + 1 ) # XXX: verify that the Hub has found the secret, close and settle the channel # the hub has acquired its token hub_contract = app1.raiden.proxy_manager.token_hashchannel[token][hub_contract] assert hub_contract.participants[app0.raiden.address]["netted"] == deposit + amount assert hub_contract.participants[app1.raiden.address]["netted"] == deposit - amount
def test_refund_messages(raiden_chain: List[RaidenService], token_addresses, deposit): # The network has the following topology: # # App0 <---> App1 <---> App2 app0, app1, app2 = raiden_chain # pylint: disable=unbalanced-tuple-unpacking token_address = token_addresses[0] token_network_registry_address = app0.default_registry.address token_network_address = views.get_token_network_address_by_token_address( views.state_from_raiden(app0), token_network_registry_address, token_address) assert token_network_address, "token_address must be registered by the fixtures." identifier = PaymentID(1) # Exhaust the channel App1 <-> App2 (to force the refund transfer) # Here we make a single-hop transfer, no fees are charged so we should # send the whole deposit amount to drain the channel. transfer( initiator_app=app1, target_app=app2, token_address=token_address, amount=deposit, identifier=identifier, routes=[[app1.address, app2.address]], ) refund_amount = deposit // 2 refund_fees = calculate_fee_for_amount(refund_amount) fee_margin = calculate_fee_margin(refund_amount, refund_fees) refund_amount_with_fees = refund_amount + refund_fees + fee_margin payment_status = app0.mediated_transfer_async( token_network_address=token_network_address, amount=refund_amount, target=TargetAddress(app2.address), identifier=identifier, route_states=[ create_route_state_for_route( apps=raiden_chain, token_address=token_address, fee_estimate=FeeAmount( round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * refund_amount)), ) ], ) msg = "Must fail, there are no routes available" assert isinstance(payment_status.payment_done.wait(), EventPaymentSentFailed), msg # The transfer from app0 to app2 failed, so the balances did change. # Since the refund is not unlocked both channels have the corresponding # amount locked (issue #1091) send_lockedtransfer = raiden_events_search_for_item( app0, SendLockedTransfer, {"transfer": { "lock": { "amount": refund_amount_with_fees } }}, ) assert send_lockedtransfer send_refundtransfer = raiden_events_search_for_item( app1, SendRefundTransfer, {}) assert send_refundtransfer with block_offset_timeout(app1): wait_assert( func=assert_synced_channel_state, token_network_address=token_network_address, app0=app0, balance0=deposit, pending_locks0=[send_lockedtransfer.transfer.lock], app1=app1, balance1=deposit, pending_locks1=[send_refundtransfer.transfer.lock], ) # This channel was exhausted to force the refund transfer except for the fees wait_assert( func=assert_succeeding_transfer_invariants, token_network_address=token_network_address, app0=app1, balance0=0, pending_locks0=[], app1=app2, balance1=deposit * 2, pending_locks1=[], )
def test_batch_unlock_after_restart(raiden_network, restart_node, token_addresses, deposit): """Simulate the case where: - A sends B a transfer - B sends A a transfer - Secrets were never revealed - B closes channel - A crashes - Wait for settle - Wait for unlock from B - Restart A At this point, the current unlock logic will try to unlock iff the node gains from unlocking. Which means that the node will try to unlock either side. In the above scenario, each node will unlock its side. This test makes sure that we do NOT invalidate A's unlock transaction based on the ContractReceiveChannelBatchUnlock caused by B's unlock. """ alice_app, bob_app = raiden_network registry_address = alice_app.raiden.default_registry.address token_address = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( chain_state=views.state_from_app(alice_app), token_network_registry_address=alice_app.raiden.default_registry.address, token_address=token_address, ) assert token_network_address timeout = 10 token_network = views.get_token_network_by_address( chain_state=views.state_from_app(alice_app), token_network_address=token_network_address ) assert token_network channel_identifier = get_channelstate(alice_app, bob_app, token_network_address).identifier assert ( channel_identifier in token_network.partneraddresses_to_channelidentifiers[bob_app.raiden.address] ) alice_to_bob_amount = 10 identifier = 1 alice_transfer_secret = Secret(keccak(alice_app.raiden.address)) alice_transfer_secrethash = sha256_secrethash(alice_transfer_secret) bob_transfer_secret = Secret(keccak(bob_app.raiden.address)) bob_transfer_secrethash = sha256_secrethash(bob_transfer_secret) alice_transfer_hold = bob_app.raiden.raiden_event_handler.hold_secretrequest_for( secrethash=alice_transfer_secrethash ) bob_transfer_hold = alice_app.raiden.raiden_event_handler.hold_secretrequest_for( secrethash=bob_transfer_secrethash ) alice_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=alice_to_bob_amount, target=bob_app.raiden.address, identifier=identifier, secret=alice_transfer_secret, ) bob_app.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=alice_to_bob_amount, target=alice_app.raiden.address, identifier=identifier + 1, secret=bob_transfer_secret, ) alice_transfer_hold.wait(timeout=timeout) bob_transfer_hold.wait(timeout=timeout) alice_bob_channel_state = get_channelstate(alice_app, bob_app, token_network_address) alice_lock = channel.get_lock(alice_bob_channel_state.our_state, alice_transfer_secrethash) bob_lock = channel.get_lock(alice_bob_channel_state.partner_state, bob_transfer_secrethash) assert alice_lock assert bob_lock # This is the current state of protocol: # # A -> B LockedTransfer # - protocol didn't continue assert_synced_channel_state( token_network_address=token_network_address, app0=alice_app, balance0=deposit, pending_locks0=[alice_lock], app1=bob_app, balance1=deposit, pending_locks1=[bob_lock], ) # A ChannelClose event will be generated, this will be polled by both apps # and each must start a task for calling settle RaidenAPI(bob_app.raiden).channel_close( registry_address=registry_address, token_address=token_address, partner_address=alice_app.raiden.address, ) # wait for the close transaction to be mined, this is necessary to compute # the timeout for the settle with block_offset_timeout(alice_app.raiden): waiting.wait_for_close( raiden=alice_app.raiden, token_network_registry_address=registry_address, token_address=token_address, channel_ids=[alice_bob_channel_state.identifier], retry_timeout=alice_app.raiden.alarm.sleep_time, ) channel_closed = raiden_state_changes_search_for_item( bob_app.raiden, ContractReceiveChannelClosed, { "canonical_identifier": { "token_network_address": token_network_address, "channel_identifier": alice_bob_channel_state.identifier, } }, ) assert isinstance(channel_closed, ContractReceiveChannelClosed) offset = BlockOffset(alice_bob_channel_state.settle_timeout * 2) with block_offset_timeout(bob_app.raiden, "Settle did not happen", offset): waiting.wait_for_settle( raiden=alice_app.raiden, token_network_registry_address=registry_address, token_address=token_address, channel_ids=[alice_bob_channel_state.identifier], retry_timeout=alice_app.raiden.alarm.sleep_time, ) with gevent.Timeout(timeout): wait_for_batch_unlock( app=bob_app, token_network_address=token_network_address, receiver=alice_bob_channel_state.partner_state.address, sender=alice_bob_channel_state.our_state.address, ) restart_node(alice_app) with gevent.Timeout(timeout): wait_for_batch_unlock( app=alice_app, token_network_address=token_network_address, receiver=alice_bob_channel_state.partner_state.address, sender=alice_bob_channel_state.our_state.address, )
def test_refund_transfer_after_2nd_hop(raiden_chain: List[RaidenService], number_of_nodes, token_addresses, deposit, network_wait): """Test the refund transfer sent due to failure after 2nd hop""" # Topology: # # 0 -> 1 -> 2 -> 3 # app0, app1, app2, app3 = raiden_chain token_address = token_addresses[0] token_network_registry_address = app0.default_registry.address token_network_address = views.get_token_network_address_by_token_address( views.state_from_raiden(app0), token_network_registry_address, token_address) assert token_network_address, "token_address must be registered by the fixtures." # make a transfer to test the path app0 -> app1 -> app2 -> app3 identifier_path = PaymentID(1) amount_path = PaymentAmount(1) with block_offset_timeout(app0): transfer( initiator_app=app0, target_app=app3, token_address=token_address, amount=amount_path, identifier=identifier_path, timeout=network_wait * number_of_nodes, routes=[[app0.address, app1.address, app2.address, app3.address]], ) # drain the channel app2 -> app3 identifier_drain = PaymentID(2) amount_drain = PaymentAmount(deposit * 8 // 10) with block_offset_timeout(app2): transfer( initiator_app=app2, target_app=app3, token_address=token_address, amount=amount_drain, identifier=identifier_drain, timeout=network_wait, routes=[[app2.address, app3.address]], ) wait_assert( assert_synced_channel_state, token_network_address, app0, deposit - amount_path, [], app1, deposit + amount_path, [], ) wait_assert( assert_synced_channel_state, token_network_address, app1, deposit - amount_path, [], app2, deposit + amount_path, [], ) wait_assert( assert_synced_channel_state, token_network_address, app2, deposit - amount_path - amount_drain, [], app3, deposit + amount_path + amount_drain, [], ) # app0 -> app1 -> app2 > app3 is the only available path, but the channel # app2 -> app3 doesn't have capacity, so a refund will be sent on # app2 -> app1 -> app0 identifier_refund = PaymentID(3) amount_refund = PaymentAmount(50) fee = calculate_fee_for_amount(amount_refund) fee_margin = calculate_fee_margin(amount_refund, fee) amount_refund_with_fees = amount_refund + fee + fee_margin payment_status = app0.mediated_transfer_async( token_network_address=token_network_address, amount=amount_refund, target=TargetAddress(app3.address), identifier=identifier_refund, route_states=[ create_route_state_for_route( apps=[app0, app1, app2], token_address=token_address, fee_estimate=FeeAmount( round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount_refund)), ) ], ) msg = "there is no path with capacity, the transfer must fail" assert isinstance(payment_status.payment_done.wait(), EventPaymentSentFailed), msg # Lock structures with the correct amount send_locked1 = raiden_events_search_for_item( app0, SendLockedTransfer, {"transfer": { "lock": { "amount": amount_refund_with_fees } }}, ) assert send_locked1 send_refund1 = raiden_events_search_for_item(app1, SendRefundTransfer, {}) assert send_refund1 lock1 = send_locked1.transfer.lock refund_lock1 = send_refund1.transfer.lock assert lock1.amount == refund_lock1.amount assert lock1.secrethash == refund_lock1.secrethash send_locked2 = raiden_events_search_for_item( app1, SendLockedTransfer, {"transfer": { "lock": { "amount": amount_refund_with_fees } }}, ) assert send_locked2 send_refund2 = raiden_events_search_for_item(app2, SendRefundTransfer, {}) assert send_refund2 lock2 = send_locked2.transfer.lock refund_lock2 = send_refund2.transfer.lock assert lock2.amount == refund_lock2.amount assert lock2.secrethash assert lock2.expiration # channels have the amount locked because of the refund message with block_offset_timeout(app0): wait_assert( assert_synced_channel_state, token_network_address, app0, deposit - amount_path, [lock1], app1, deposit + amount_path, [refund_lock1], ) wait_assert( assert_synced_channel_state, token_network_address, app1, deposit - amount_path, [lock2], app2, deposit + amount_path, [refund_lock2], ) wait_assert( assert_synced_channel_state, token_network_address, app2, deposit - amount_path - amount_drain, [], app3, deposit + amount_path + amount_drain, [], )
def test_clear_closed_queue(raiden_network: List[App], token_addresses, network_wait): """ Closing a channel clears the respective message queue. """ app0, app1 = raiden_network hold_event_handler = app1.raiden.raiden_event_handler assert isinstance(hold_event_handler, HoldRaidenEventHandler) registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] chain_state0 = views.state_from_app(app0) token_network_address = views.get_token_network_address_by_token_address( chain_state0, app0.raiden.default_registry.address, token_address) assert token_network_address token_network = views.get_token_network_by_address(chain_state0, token_network_address) assert token_network channel_identifier = get_channelstate(app0, app1, token_network_address).identifier assert (channel_identifier in token_network.partneraddresses_to_channelidentifiers[ app1.raiden.address]) target = app1.raiden.address secret = Secret(sha3(target)) secrethash = sha256_secrethash(secret) hold_event_handler.hold_secretrequest_for(secrethash=secrethash) # make an unconfirmed transfer to ensure the nodes have communicated amount = PaymentAmount(10) payment_identifier = PaymentID(1337) app0.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=amount, target=TargetAddress(target), identifier=payment_identifier, secret=secret, ) app1.raiden.transport.stop() app1.raiden.transport.greenlet.get() # make sure to wait until the queue is created def has_initiator_events(): assert app0.raiden.wal, "raiden server must have been started" initiator_events = app0.raiden.wal.storage.get_events() return search_for_item(initiator_events, SendLockedTransfer, {}) assert wait_until(has_initiator_events, network_wait) # assert the specific queue is present chain_state0 = views.state_from_app(app0) queues0 = views.get_all_messagequeues(chain_state=chain_state0) assert [ (queue_id, queue) for queue_id, queue in queues0.items() if queue_id.recipient == app1.raiden.address and queue_id. canonical_identifier.channel_identifier == channel_identifier and queue ] # A ChannelClose event will be generated, this will be polled by both apps RaidenAPI(app0.raiden).channel_close(registry_address, token_address, app1.raiden.address) exception = ValueError("Could not get close event") with gevent.Timeout(seconds=30, exception=exception): waiting.wait_for_close( app0.raiden, registry_address, token_address, [channel_identifier], app0.raiden.alarm.sleep_time, ) # assert all queues with this partner are gone or empty chain_state0 = views.state_from_app(app0) queues0 = views.get_all_messagequeues(chain_state=chain_state0) assert not [(queue_id, queue) for queue_id, queue in queues0.items() if queue_id.recipient == app1.raiden.address and queue] chain_state1 = views.state_from_app(app1) queues1 = views.get_all_messagequeues(chain_state=chain_state1) assert not [(queue_id, queue) for queue_id, queue in queues1.items() if queue_id.recipient == app0.raiden.address and queue]
def test_regression_multiple_revealsecret( raiden_network: List[RaidenService], token_addresses: List[TokenAddress]) -> None: """Multiple RevealSecret messages arriving at the same time must be handled properly. Unlock handling followed these steps: The Unlock message arrives The secret is registered The channel is updated and the correspoding lock is removed * A balance proof for the new channel state is created and sent to the payer The channel is unregistered for the given secrethash The step marked with an asterisk above introduced a context-switch. This allowed a second Reveal Unlock message to be handled before the channel was unregistered. And because the channel was already updated an exception was raised for an unknown secret. """ app0, app1 = raiden_network token = token_addresses[0] token_network_address = views.get_token_network_address_by_token_address( views.state_from_raiden(app0), app0.default_registry.address, token) assert token_network_address channelstate_0_1 = get_channelstate(app0, app1, token_network_address) payment_identifier = PaymentID(1) secret, secrethash = make_secret_with_hash() expiration = BlockExpiration(app0.get_block_number() + 100) lock_amount = PaymentWithFeeAmount(10) lock = Lock(amount=lock_amount, expiration=expiration, secrethash=secrethash) nonce = Nonce(1) transferred_amount = TokenAmount(0) mediated_transfer = LockedTransfer( chain_id=UNIT_CHAIN_ID, message_identifier=make_message_identifier(), payment_identifier=payment_identifier, nonce=nonce, token_network_address=token_network_address, token=token, channel_identifier=channelstate_0_1.identifier, transferred_amount=transferred_amount, locked_amount=LockedAmount(lock_amount), recipient=app1.address, locksroot=Locksroot(lock.lockhash), lock=lock, target=TargetAddress(app1.address), initiator=InitiatorAddress(app0.address), signature=EMPTY_SIGNATURE, metadata=Metadata( routes=[RouteMetadata(route=[app0.address, app1.address])]), ) app0.sign(mediated_transfer) app1.on_messages([mediated_transfer]) reveal_secret = RevealSecret(message_identifier=make_message_identifier(), secret=secret, signature=EMPTY_SIGNATURE) app0.sign(reveal_secret) token_network_address = channelstate_0_1.token_network_address unlock = Unlock( chain_id=UNIT_CHAIN_ID, message_identifier=make_message_identifier(), payment_identifier=payment_identifier, nonce=Nonce(mediated_transfer.nonce + 1), token_network_address=token_network_address, channel_identifier=channelstate_0_1.identifier, transferred_amount=TokenAmount(lock_amount), locked_amount=LockedAmount(0), locksroot=LOCKSROOT_OF_NO_LOCKS, secret=secret, signature=EMPTY_SIGNATURE, ) app0.sign(unlock) messages = [unlock, reveal_secret] receive_method = app1.on_messages wait = set( gevent.spawn_later(0.1, receive_method, [data]) for data in messages) gevent.joinall(wait, raise_error=True)