def test_snapshotting(raiden_network, token_addresses): app0, app1, app2 = raiden_network api0 = RaidenAPI(app0.raiden) api1 = RaidenAPI(app1.raiden) channel_0_1 = api0.get_channel_list(token_addresses[0], app1.raiden.address) channel_0_2 = api0.get_channel_list(token_addresses[0], app2.raiden.address) assert not api1.get_channel_list(token_addresses[0], app2.raiden.address) assert len(channel_0_1) == 1 assert len(channel_0_2) == 1 api1.transfer_and_wait(token_addresses[0], 5, app2.raiden.address) app0.stop() app1.stop() app2.stop() for app in [app0, app1, app2]: data = load_snapshot(app.raiden.serialization_file) for serialized_channel in data['channels']: network = app.raiden.token_to_channelgraph[serialized_channel.token_address] running_channel = network.address_to_channel[serialized_channel.channel_address] assert running_channel.serialize() == serialized_channel for queue in data['queues']: key = (queue['receiver_address'], queue['token_address']) assert app.raiden.protocol.channel_queue[key].copy() == queue['messages'] assert data['receivedhashes_to_acks'] == app.raiden.protocol.receivedhashes_to_acks assert data['nodeaddresses_to_nonces'] == app.raiden.protocol.nodeaddresses_to_nonces assert data['transfers'] == app.raiden.identifier_to_statemanagers
def test_get_channel_list(raiden_network, token_addresses): app0, app1, app2 = raiden_network # pylint: disable=unbalanced-tuple-unpacking channel0 = channel(app0, app1, token_addresses[0]) channel1 = channel(app1, app0, token_addresses[0]) channel2 = channel(app0, app2, token_addresses[0]) api0 = RaidenAPI(app0.raiden) api1 = RaidenAPI(app1.raiden) api2 = RaidenAPI(app2.raiden) assert channel0, channel2 in api0.get_channel_list() assert channel0 in api0.get_channel_list( partner_address=app1.raiden.address) assert channel1 in api1.get_channel_list(token_address=token_addresses[0]) assert channel1 in api1.get_channel_list(token_addresses[0], app0.raiden.address) assert not api1.get_channel_list(partner_address=app2.raiden.address) with pytest.raises(KeyError): api1.get_channel_list( token_address=token_addresses[0], partner_address=app2.raiden.address, ) with pytest.raises(KeyError): api2.get_channel_list(token_address=app2.raiden.address, )
def test_close_regression(raiden_network, deposit, token_addresses): """ The python api was using the wrong balance proof to close the channel, thus the close was failing if a transfer was made. """ app0, app1 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(app0.raiden) api2 = RaidenAPI(app1.raiden) channel_list = api1.get_channel_list(token_address, app1.raiden.address) channel12 = channel_list[0] token_proxy = app0.raiden.chain.token(token_address) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) # Initialize app2 balance proof and close the channel amount = 10 assert api1.transfer(token_address, amount, api2.address) api2.channel_close(token_address, api1.address) waiting.wait_for_settle( app0.raiden, app0.raiden.default_registry.address, token_address, [channel12.identifier], app0.raiden.alarm.wait_time, ) node1_expected_balance = node1_balance_before + deposit - amount node2_expected_balance = node2_balance_before + deposit + amount assert token_proxy.balance_of(api1.address) == node1_expected_balance assert token_proxy.balance_of(api2.address) == node2_expected_balance
def test_close_regression(raiden_network, deposit, token_addresses): """ The python api was using the wrong balance proof to close the channel, thus the close was failing if a transfer was made. """ app0, app1 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(app0.raiden) api2 = RaidenAPI(app1.raiden) registry_address = app0.raiden.default_registry.address channel_list = api1.get_channel_list(registry_address, token_address, app1.raiden.address) channel12 = channel_list[0] token_proxy = app0.raiden.proxy_manager.token(token_address, BLOCK_ID_LATEST) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) # Initialize app2 balance proof and close the channel amount = PaymentAmount(10) identifier = PaymentID(42) secret, secrethash = factories.make_secret_with_hash() timeout = block_offset_timeout(app1.raiden, "Transfer timed out.") with watch_for_unlock_failures(*raiden_network), timeout: assert api1.transfer_and_wait( registry_address=registry_address, token_address=token_address, amount=amount, target=TargetAddress(api2.address), identifier=identifier, secret=secret, ) timeout.exception_to_throw = ValueError( "Waiting for transfer received success in the WAL timed out.") result = waiting.wait_for_received_transfer_result( raiden=app1.raiden, payment_identifier=identifier, amount=amount, retry_timeout=app1.raiden.alarm.sleep_time, secrethash=secrethash, ) msg = f"Unexpected transfer result: {str(result)}" assert result == waiting.TransferWaitResult.UNLOCKED, msg api2.channel_close(registry_address, token_address, api1.address) waiting.wait_for_settle( app0.raiden, app0.raiden.default_registry.address, token_address, [channel12.identifier], app0.raiden.alarm.sleep_time, ) node1_expected_balance = node1_balance_before + deposit - amount node2_expected_balance = node2_balance_before + deposit + amount assert token_proxy.balance_of(api1.address) == node1_expected_balance assert token_proxy.balance_of(api2.address) == node2_expected_balance
def test_token_addresses(raiden_network, token_addresses): node1, node2 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(node1.raiden) api2 = RaidenAPI(node2.raiden) assert api1.address == node1.raiden.address assert set(api1.tokens) == set(token_addresses) assert set(api1.get_tokens_list()) == set(token_addresses) channels = api1.get_channel_list(token_address, api2.address) assert api1.get_channel_list(token_address) == channels assert len(api1.get_channel_list()) == 2 assert api1.get_node_network_state(api2.address) == NODE_NETWORK_REACHABLE
def test_close_regression(raiden_network, deposit, token_addresses): """ The python api was using the wrong balance proof to close the channel, thus the close was failing if a transfer was made. """ app0, app1 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(app0.raiden) api2 = RaidenAPI(app1.raiden) registry_address = app0.raiden.default_registry.address channel_list = api1.get_channel_list(registry_address, token_address, app1.raiden.address) channel12 = channel_list[0] token_proxy = app0.raiden.proxy_manager.token(token_address) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) # Initialize app2 balance proof and close the channel amount = 10 identifier = 42 secret, secrethash = factories.make_secret_with_hash() assert api1.transfer_and_wait( registry_address=registry_address, token_address=token_address, amount=amount, target=api2.address, identifier=identifier, secret=secret, transfer_timeout=10, ) exception = ValueError( "Waiting for transfer received success in the WAL timed out") with gevent.Timeout(seconds=5, exception=exception): result = waiting.wait_for_received_transfer_result( raiden=app1.raiden, payment_identifier=identifier, amount=amount, retry_timeout=app1.raiden.alarm.sleep_time, secrethash=secrethash, ) msg = f"Unexpected transfer result: {str(result)}" assert result == waiting.TransferWaitResult.UNLOCKED, msg api2.channel_close(registry_address, token_address, api1.address) waiting.wait_for_settle( app0.raiden, app0.raiden.default_registry.address, token_address, [channel12.identifier], app0.raiden.alarm.sleep_time, ) node1_expected_balance = node1_balance_before + deposit - amount node2_expected_balance = node2_balance_before + deposit + amount assert token_proxy.balance_of(api1.address) == node1_expected_balance assert token_proxy.balance_of(api2.address) == node2_expected_balance
def test_get_channel_list(raiden_network, token_addresses): app0, app1, app2 = raiden_network # pylint: disable=unbalanced-tuple-unpacking channel0 = channel(app0, app1, token_addresses[0]) channel1 = channel(app1, app0, token_addresses[0]) channel2 = channel(app0, app2, token_addresses[0]) api0 = RaidenAPI(app0.raiden) api1 = RaidenAPI(app1.raiden) api2 = RaidenAPI(app2.raiden) assert channel0, channel2 in api0.get_channel_list() assert channel0 in api0.get_channel_list(partner_address=app1.raiden.address) assert channel1 in api1.get_channel_list(token_address=token_addresses[0]) assert channel1 in api1.get_channel_list(token_addresses[0], app0.raiden.address) assert not api1.get_channel_list(partner_address=app2.raiden.address) assert not api1.get_channel_list( token_address=token_addresses[0], partner_address=app2.raiden.address, ) assert not api2.get_channel_list( token_address=app2.raiden.address, )
def get_channel_events_for_token(app, token_address, start_block=0): """ Collect all events from all channels for a given `token_address` and `app` """ result = list() api = RaidenAPI(app.raiden) channels = api.get_channel_list(token_address=token_address) for channel in channels: events = api.get_channel_events(channel.identifier, start_block) result.extend(events) return result
def get_channel_events_for_token(app, token_address, start_block=0): """ Collect all events from all channels for a given `token_address` and `app` """ result = list() api = RaidenAPI(app.raiden) channels = api.get_channel_list(token_address=token_address) for channel in channels: events = api.get_channel_events(channel.channel_address, start_block) result.extend(events) return result
def test_close_regression(raiden_network, deposit, token_addresses): """ The python api was using the wrong balance proof to close the channel, thus the close was failing if a transfer was made. """ app0, app1 = raiden_network registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] api1 = RaidenAPI(app0.raiden) api2 = RaidenAPI(app1.raiden) registry_address = app0.raiden.default_registry.address channel_list = api1.get_channel_list(registry_address, token_address, app1.raiden.address) channel12 = channel_list[0] token_proxy = app0.raiden.chain.token(token_address) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) # Initialize app2 balance proof and close the channel amount = 10 identifier = 42 assert api1.transfer( registry_address, token_address, amount, api2.address, identifier=identifier, ) exception = ValueError( 'Waiting for transfer received success in the WAL timed out') with gevent.Timeout(seconds=5, exception=exception): waiting.wait_for_transfer_success( app1.raiden, identifier, amount, app1.raiden.alarm.sleep_time, ) api2.channel_close(registry_address, token_address, api1.address) waiting.wait_for_settle( app0.raiden, app0.raiden.default_registry.address, token_address, [channel12.identifier], app0.raiden.alarm.sleep_time, ) node1_expected_balance = node1_balance_before + deposit - amount node2_expected_balance = node2_balance_before + deposit + amount assert token_proxy.balance_of(api1.address) == node1_expected_balance assert token_proxy.balance_of(api2.address) == node2_expected_balance
def test_close_regression(raiden_network, deposit, token_addresses): """ The python api was using the wrong balance proof to close the channel, thus the close was failing if a transfer was made. """ app0, app1 = raiden_network registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] api1 = RaidenAPI(app0.raiden) api2 = RaidenAPI(app1.raiden) registry_address = app0.raiden.default_registry.address channel_list = api1.get_channel_list(registry_address, token_address, app1.raiden.address) channel12 = channel_list[0] token_proxy = app0.raiden.chain.token(token_address) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) # Initialize app2 balance proof and close the channel amount = 10 identifier = 42 assert api1.transfer( registry_address, token_address, amount, api2.address, identifier=identifier, ) exception = ValueError('Waiting for transfer received success in the WAL timed out') with gevent.Timeout(seconds=5, exception=exception): waiting.wait_for_transfer_success( app1.raiden, identifier, amount, app1.raiden.alarm.sleep_time, ) api2.channel_close(registry_address, token_address, api1.address) waiting.wait_for_settle( app0.raiden, app0.raiden.default_registry.address, token_address, [channel12.identifier], app0.raiden.alarm.sleep_time, ) node1_expected_balance = node1_balance_before + deposit - amount node2_expected_balance = node2_balance_before + deposit + amount assert token_proxy.balance_of(api1.address) == node1_expected_balance assert token_proxy.balance_of(api2.address) == node2_expected_balance
def get_channel_events_for_token(app, registry_address, token_address, start_block=0): """ Collect all events from all channels for a given `token_address` and `app` """ result = list() api = RaidenAPI(app.raiden) channels = api.get_channel_list( registry_address=registry_address, token_address=token_address, ) token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(app), app.raiden.default_registry.address, token_address, ) for channel in channels: events = api.get_channel_events(token_network_identifier, channel.identifier, start_block) result.extend(events) return result
def test_close_regression(blockchain_type, raiden_network, token_addresses): """ The python api was using the wrong balance proof to close the channel, thus the close was failling if a transfer was made. """ if blockchain_type == 'tester': pytest.skip('event polling is not reliable') node1, node2 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(node1.raiden) api2 = RaidenAPI(node2.raiden) channel_list = api1.get_channel_list(token_address, node2.raiden.address) channel12 = channel_list[0] token_proxy = node1.raiden.chain.token(token_address) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) channel_balance = token_proxy.balance_of(channel12.channel_address) amount = 10 assert api1.transfer(token_address, amount, api2.address) api1.close(token_address, api2.address) node1.raiden.poll_blockchain_events() assert channel12.state == CHANNEL_STATE_CLOSED settlement_block = ( channel12.external_state.closed_block + channel12.settle_timeout + 5 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block) node1.raiden.poll_blockchain_events() assert channel12.state == CHANNEL_STATE_SETTLED node1_withdraw_amount = channel12.balance node2_withdraw_amount = channel_balance - node1_withdraw_amount assert token_proxy.balance_of( api1.address) == node1_balance_before + node1_withdraw_amount assert token_proxy.balance_of( api2.address) == node2_balance_before + node2_withdraw_amount
def run_test_close_regression(raiden_network, deposit, token_addresses): app0, app1 = raiden_network registry_address = app0.raiden.default_registry.address token_address = token_addresses[0] api1 = RaidenAPI(app0.raiden) api2 = RaidenAPI(app1.raiden) registry_address = app0.raiden.default_registry.address channel_list = api1.get_channel_list(registry_address, token_address, app1.raiden.address) channel12 = channel_list[0] token_proxy = app0.raiden.chain.token(token_address) node1_balance_before = token_proxy.balance_of(api1.address) node2_balance_before = token_proxy.balance_of(api2.address) # Initialize app2 balance proof and close the channel amount = 10 identifier = 42 assert api1.transfer(registry_address, token_address, amount, api2.address, identifier=identifier, payment_hash_invoice=EMPTY_PAYMENT_HASH_INVOICE) exception = ValueError( "Waiting for transfer received success in the WAL timed out") with gevent.Timeout(seconds=5, exception=exception): waiting.wait_for_transfer_success(app1.raiden, identifier, amount, app1.raiden.alarm.sleep_time) api2.channel_close(registry_address, token_address, api1.address) waiting.wait_for_settle( app0.raiden, app0.raiden.default_registry.address, token_address, [channel12.identifier], app0.raiden.alarm.sleep_time, ) node1_expected_balance = node1_balance_before + deposit - amount node2_expected_balance = node2_balance_before + deposit + amount assert token_proxy.balance_of(api1.address) == node1_expected_balance assert token_proxy.balance_of(api2.address) == node2_expected_balance
def test_channel_lifecycle(raiden_network, token_addresses, deposit, transport_config): node1, node2 = raiden_network token_address = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(node1), node1.raiden.default_registry.address, token_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) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state(api1.address) == NODE_NETWORK_UNKNOWN assert not api1.get_channel_list(registry_address, token_address, api2.address) # 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_identifier) assert channel.get_status(channel12) == CHANNEL_STATE_OPENED event_list1 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address, ) assert any((event['event'] == ChannelEvent.OPENED and is_same_address( event['args']['participant1'], to_normalized_address(api1.address), ) and is_same_address( event['args']['participant2'], to_normalized_address(api2.address), )) for event in event_list1) token_events = api1.get_blockchain_events_token_network(token_address, ) assert token_events[0]['event'] == ChannelEvent.OPENED registry_address = api1.raiden.default_registry.address # Load the new state with the deposit api1.set_total_channel_deposit( registry_address, token_address, api2.address, 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_identifier) assert channel.get_status(channel12) == CHANNEL_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) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_REACHABLE event_list2 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address, ) assert any((event['event'] == ChannelEvent.DEPOSIT and is_same_address( event['args']['participant'], to_normalized_address(api1.address), ) and event['args']['total_deposit'] == deposit) for event in event_list2) api1.channel_close(registry_address, token_address, api2.address) # Load the new state with the channel closed channel12 = get_channelstate(node1, node2, token_network_identifier) event_list3 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address, ) assert len(event_list3) > len(event_list2) assert any((event['event'] == ChannelEvent.CLOSED and is_same_address( event['args']['closing_participant'], to_normalized_address(api1.address), )) for event in event_list3) assert channel.get_status(channel12) == CHANNEL_STATE_CLOSED settlement_block = ( channel12.close_transaction.finished_block_number + channel12.settle_timeout + 10 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block) state_changes = node1.raiden.wal.storage.get_statechanges_by_identifier( from_identifier=0, to_identifier='latest', ) assert must_contain_entry( state_changes, ContractReceiveChannelSettled, { 'token_network_identifier': token_network_identifier, 'channel_identifier': channel12.identifier, })
def test_channel_lifecycle(raiden_network, token_addresses, deposit, transport_config): node1, node2 = raiden_network token_address = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(node1), node1.raiden.default_registry.address, token_address, ) api1 = RaidenAPI(node1.raiden) api2 = RaidenAPI(node2.raiden) registry_address = node1.raiden.default_registry.address if transport_config.protocol == TransportProtocol.UDP: # nodes don't have a channel, so they are not healthchecking assert api1.get_node_network_state( api2.address) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state( api1.address) == NODE_NETWORK_UNKNOWN elif transport_config.protocol == TransportProtocol.MATRIX: # with Matrix nodes do not need a health check to know each others reachability assert api1.get_node_network_state( api2.address) == NODE_NETWORK_UNREACHABLE assert api2.get_node_network_state( api1.address) == NODE_NETWORK_UNREACHABLE assert not api1.get_channel_list(registry_address, token_address, api2.address) # 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_identifier) assert channel.get_status(channel12) == CHANNEL_STATE_OPENED event_list1 = api1.get_channel_events( channel12.identifier, channel12.open_transaction.finished_block_number, ) assert event_list1 == [] token_events = api1.get_token_network_events( token_address, channel12.open_transaction.finished_block_number, ) assert token_events[0]['event'] == EVENT_CHANNEL_NEW registry_address = api1.raiden.default_registry.address # Load the new state with the deposit api1.channel_deposit( registry_address, token_address, api2.address, deposit, ) channel12 = get_channelstate(node1, node2, token_network_identifier) assert channel.get_status(channel12) == CHANNEL_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) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_REACHABLE event_list2 = api1.get_channel_events( channel12.identifier, channel12.open_transaction.finished_block_number, ) assert any( (event['event'] == EVENT_CHANNEL_NEW_BALANCE and is_same_address( event['args']['registry_address'], to_normalized_address(registry_address), ) and is_same_address( event['args']['participant'], to_normalized_address(api1.address), )) for event in event_list2) api1.channel_close(registry_address, token_address, api2.address) # Load the new state with the channel closed channel12 = get_channelstate(node1, node2, token_network_identifier) event_list3 = api1.get_channel_events( channel12.identifier, channel12.open_transaction.finished_block_number, ) assert len(event_list3) > len(event_list2) assert any((event['event'] == EVENT_CHANNEL_CLOSED and is_same_address( event['args']['registry_address'], to_normalized_address(registry_address), ) and is_same_address( event['args']['closing_address'], to_normalized_address(api1.address), )) for event in event_list3) assert channel.get_status(channel12) == CHANNEL_STATE_CLOSED settlement_block = ( channel12.close_transaction.finished_block_number + channel12.settle_timeout + 10 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block) # Load the new state with the channel settled channel12 = get_channelstate(node1, node2, token_network_identifier) assert channel.get_status(channel12) == CHANNEL_STATE_SETTLED
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_channel_lifecycle(blockchain_type, raiden_network, token_addresses, deposit): if blockchain_type == 'tester': pytest.skip( 'there is not support ATM for retrieving events from tester') node1, node2 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(node1.raiden) api2 = RaidenAPI(node2.raiden) # nodes don't have a channel, so they are not healthchecking assert api1.get_node_network_state(api2.address) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state(api1.address) == NODE_NETWORK_UNKNOWN assert api1.get_channel_list(token_address, api2.address) == [] # this is a synchronous api api1.open(token_address, api2.address) channels = api1.get_channel_list(token_address, api2.address) assert len(channels) == 1 channel12 = channels[0] event_list1 = api1.get_channel_events( channel12.channel_address, channel12.external_state.opened_block, ) assert event_list1 == [] # the channel has no deposit yet assert channel12.state == CHANNEL_STATE_OPENED api1.deposit(token_address, api2.address, deposit) assert channel12.state == CHANNEL_STATE_OPENED assert channel12.balance == deposit assert channel12.contract_balance == deposit assert api1.get_channel_list(token_address, api2.address) == [channel12] # there is a channel open, they must be healthchecking each other assert api1.get_node_network_state(api2.address) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_REACHABLE event_list2 = api1.get_channel_events( channel12.channel_address, channel12.external_state.opened_block, ) assert any((event['_event_type'] == 'ChannelNewBalance' and event['participant'] == hexlify(api1.address)) for event in event_list2) with pytest.raises(InvalidState): api1.settle(token_address, api2.address) api1.close(token_address, api2.address) node1.raiden.poll_blockchain_events() event_list3 = api1.get_channel_events( channel12.channel_address, channel12.external_state.opened_block, ) assert len(event_list3) > len(event_list2) assert any((event['_event_type'] == 'ChannelClosed' and event['closing_address'] == hexlify(api1.address)) for event in event_list3) assert channel12.state == CHANNEL_STATE_CLOSED settlement_block = ( channel12.external_state.closed_block + channel12.settle_timeout + 5 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block) node1.raiden.poll_blockchain_events() assert channel12.state == CHANNEL_STATE_SETTLED
class ConnectionManager(object): """The ConnectionManager provides a high level abstraction for connecting to a Token network. Note: It is initialized with 0 funds; a connection to the token network will be only established _after_ calling `connect(funds)` """ # XXX Hack: for bootstrapping the first node on a network opens a channel # with this address to become visible. BOOTSTRAP_ADDR_HEX = '2' * 40 BOOTSTRAP_ADDR = BOOTSTRAP_ADDR_HEX.decode('hex') def __init__( self, raiden, token_address, channelgraph, ): self.lock = Semaphore() self.raiden = raiden self.api = RaidenAPI(raiden) self.channelgraph = channelgraph self.token_address = token_address self.funds = 0 self.initial_channel_target = 0 self.joinable_funds_target = 0 def connect(self, funds, initial_channel_target=3, joinable_funds_target=.4): """Connect to the network. Use this to establish a connection with the token network. Subsequent calls to `connect` are allowed, but will only affect the spendable funds and the connection strategy parameters for the future. `connect` will not close any channels. Note: the ConnectionManager does not discriminate manually opened channels from automatically opened ones. If the user manually opened channels, those deposit amounts will affect the funding per channel and the number of new channels opened. Args: funds (int): the amount of tokens spendable for this ConnectionManager. initial_channel_target (int): number of channels to open immediately joinable_funds_target (float): amount of funds not initially assigned """ if funds <= 0: raise ValueError('connecting needs a positive value for `funds`') self.initial_channel_target = initial_channel_target self.joinable_funds_target = joinable_funds_target open_channels = self.open_channels # there are already channels open if len(open_channels): log.debug( 'connect() called on an already joined token network', token_address=pex(self.token_address), open_channels=len(open_channels), sum_deposits=sum(channel.deposit for channel in open_channels), funds=funds, ) if len(self.channelgraph.graph.nodes()) == 0: with self.lock: log.debug('bootstrapping token network.') # make ourselves visible self.api.open(self.token_address, ConnectionManager.BOOTSTRAP_ADDR) with self.lock: self.funds = funds funding = self.initial_funding_per_partner # this could be a subsequent call, or some channels already open new_partner_count = max( 0, self.initial_channel_target - len(self.open_channels)) for partner in self.find_new_partners(new_partner_count): self.api.open( self.token_address, partner, ) self.api.deposit(self.token_address, partner, funding) def leave(self, wait_for_settle=True, timeout=30): """ Leave the token network. This implies closing all open channels and optionally waiting for settlement. Args: wait_for_settle (bool): block until successful settlement? timeout (float): maximum time to wait """ with self.lock: self.initial_channel_target = 0 open_channels = self.open_channels channel_specs = [(self.token_address, c.partner_address) for c in open_channels] for channel in channel_specs: try: self.api.close(*channel), except RuntimeError: # if the error wasn't that the channel was already closed: raise if channel[1] in [ c.partner_address for c in self.open_channels ]: raise # wait for events to propagate gevent.sleep(self.raiden.alarm.wait_time) if wait_for_settle: try: with gevent.timeout.Timeout(timeout): while any(c.state != CHANNEL_STATE_SETTLED for c in open_channels): # wait for events to propagate gevent.sleep(self.raiden.alarm.wait_time) except gevent.timeout.Timeout: log.debug('timeout while waiting for settlement', unsettled=sum( 1 for channel in open_channels if channel.state != CHANNEL_STATE_SETTLED), settled=sum( 1 for channel in open_channels if channel.state == CHANNEL_STATE_SETTLED)) def join_channel(self, partner_address, partner_deposit): """Will be called, when we were selected as channel partner by another node. It will fund the channel with up to the partner's deposit, but not more than remaining funds or the initial funding per channel. If the connection manager has no funds, this is a noop. """ # not initialized if self.funds <= 0: return # in leaving state if self.initial_channel_target < 1: return with self.lock: remaining = self.funds_remaining initial = self.initial_funding_per_partner joining_funds = min(partner_deposit, remaining, initial) if joining_funds <= 0: return self.api.deposit(self.token_address, partner_address, joining_funds) log.debug('joined a channel!', funds=joining_funds, me=pex(self.raiden.address), partner=pex(partner_address)) def retry_connect(self): """Will be called when new channels in the token network are detected. If the minimum number of channels was not yet established, it will try to open new channels. If the connection manager has no funds, this is a noop. """ # not initialized if self.funds <= 0: return # in leaving state if self.initial_channel_target == 0: return with self.lock: if self.funds_remaining <= 0: return if len(self.open_channels) >= self.initial_channel_target: return for partner in self.find_new_partners(self.initial_channel_target - len(self.open_channels)): try: self.api.open(self.token_address, partner) self.api.deposit(self.token_address, partner, self.initial_funding_per_partner) # this can fail because of a race condition, where the channel partner opens first except Exception as e: log.error('could not open a channel', exc_info=e) def find_new_partners(self, number): """Search the token network for potential channel partners. Args: number (int): number of partners to return """ known = set(c.partner_address for c in self.open_channels) known = known.union({self.__class__.BOOTSTRAP_ADDR}) known = known.union({self.raiden.address}) available = set(self.channelgraph.graph.nodes()) - known available = self._select_best_partners(available) log.debug('found {} partners'.format(len(available))) return available[:number] def _select_best_partners(self, partners): # FIXME: use a proper selection strategy # https://github.com/raiden-network/raiden/issues/576 return list(partners) @property def initial_funding_per_partner(self): """The calculated funding per partner depending on configuration and overall funding of the ConnectionManager. """ if self.initial_channel_target: return int(self.funds * (1 - self.joinable_funds_target) / self.initial_channel_target) else: return 0 @property def wants_more_channels(self): """True, if funds available and the `initial_channel_target` was not yet reached. """ return (self.funds_remaining > 0 and len(self.open_channels) < self.initial_channel_target) @property def funds_remaining(self): """The remaining funds after subtracting the already deposited amounts. """ if self.funds > 0: remaining = self.funds - sum(channel.deposit for channel in self.open_channels) assert isinstance(remaining, int) return remaining return 0 @property def open_channels(self): """Shorthand for getting our open channels in this token network. """ return [ channel for channel in self.api.get_channel_list( token_address=self.token_address) if channel.state == CHANNEL_STATE_OPENED ]
def run_test_raidenapi_channel_lifecycle(raiden_network, token_addresses, deposit, retry_timeout): node1, node2 = raiden_network token_address = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(node1), node1.raiden.default_registry.address, token_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) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state(api1.address) == NODE_NETWORK_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) # 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_identifier) assert channel.get_status(channel12) == CHANNEL_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=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_identifier) assert channel.get_status(channel12) == CHANNEL_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) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_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_identifier) 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) == CHANNEL_STATE_CLOSED assert wait_for_state_change( node1.raiden, ContractReceiveChannelSettled, { "token_network_identifier": token_network_identifier, "channel_identifier": channel12.identifier, }, retry_timeout, )
def test_channel_lifecycle(raiden_network, token_addresses, deposit, transport_protocol): node1, node2 = raiden_network token_address = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(node1), node1.raiden.default_registry.address, token_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) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state(api1.address) == NODE_NETWORK_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, ) # 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_identifier) assert channel.get_status(channel12) == CHANNEL_STATE_OPENED event_list1 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address, ) assert any( ( event['event'] == ChannelEvent.OPENED and is_same_address( event['args']['participant1'], to_normalized_address(api1.address), ) and is_same_address( event['args']['participant2'], to_normalized_address(api2.address), ) ) for event in event_list1 ) token_events = api1.get_blockchain_events_token_network( token_address, ) assert token_events[0]['event'] == ChannelEvent.OPENED registry_address = api1.raiden.default_registry.address # Load the new state with the deposit api1.set_total_channel_deposit( registry_address, token_address, api2.address, 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_identifier) assert channel.get_status(channel12) == CHANNEL_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) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_REACHABLE event_list2 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address, ) assert any( ( event['event'] == ChannelEvent.DEPOSIT and is_same_address( event['args']['participant'], to_normalized_address(api1.address), ) and event['args']['total_deposit'] == deposit ) for event in event_list2 ) api1.channel_close(registry_address, token_address, api2.address) # Load the new state with the channel closed channel12 = get_channelstate(node1, node2, token_network_identifier) event_list3 = api1.get_blockchain_events_channel( token_address, channel12.partner_state.address, ) assert len(event_list3) > len(event_list2) assert any( ( event['event'] == ChannelEvent.CLOSED and is_same_address( event['args']['closing_participant'], to_normalized_address(api1.address), ) ) for event in event_list3 ) assert channel.get_status(channel12) == CHANNEL_STATE_CLOSED settlement_block = ( channel12.close_transaction.finished_block_number + channel12.settle_timeout + 10 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS) state_changes = node1.raiden.wal.storage.get_statechanges_by_identifier( from_identifier=0, to_identifier='latest', ) assert must_contain_entry(state_changes, ContractReceiveChannelSettled, { 'token_network_identifier': token_network_identifier, 'channel_identifier': channel12.identifier, })
def test_channel_lifecycle(raiden_network, token_addresses, deposit, transport_config): node1, node2 = raiden_network token_address = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(node1), node1.raiden.default_registry.address, token_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) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state(api1.address) == NODE_NETWORK_UNKNOWN assert not api1.get_channel_list(registry_address, token_address, api2.address) # 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_identifier) assert channel.get_status(channel12) == CHANNEL_STATE_OPENED event_list1 = api1.get_channel_events( token_address, channel12.partner_state.address, channel12.open_transaction.finished_block_number, ) assert any( ( event['event'] == EVENT_CHANNEL_OPENED and is_same_address( event['args']['participant1'], to_normalized_address(api1.address), ) and is_same_address( event['args']['participant2'], to_normalized_address(api2.address), ) ) for event in event_list1 ) token_events = api1.get_token_network_events( token_address, channel12.open_transaction.finished_block_number, ) assert token_events[0]['event'] == EVENT_CHANNEL_OPENED registry_address = api1.raiden.default_registry.address # Load the new state with the deposit api1.set_total_channel_deposit( registry_address, token_address, api2.address, deposit, ) # let's make sure it's idempotent api1.set_total_channel_deposit( registry_address, token_address, api2.address, deposit, ) channel12 = get_channelstate(node1, node2, token_network_identifier) assert channel.get_status(channel12) == CHANNEL_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) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_REACHABLE event_list2 = api1.get_channel_events( token_address, channel12.partner_state.address, channel12.open_transaction.finished_block_number, ) assert any( ( event['event'] == EVENT_CHANNEL_DEPOSIT and is_same_address( event['args']['participant'], to_normalized_address(api1.address), ) and event['args']['total_deposit'] == deposit ) for event in event_list2 ) api1.channel_close(registry_address, token_address, api2.address) # Load the new state with the channel closed channel12 = get_channelstate(node1, node2, token_network_identifier) event_list3 = api1.get_channel_events( token_address, channel12.partner_state.address, channel12.open_transaction.finished_block_number, ) assert len(event_list3) > len(event_list2) assert any( ( event['event'] == EVENT_CHANNEL_CLOSED and is_same_address( event['args']['closing_participant'], to_normalized_address(api1.address), ) ) for event in event_list3 ) assert channel.get_status(channel12) == CHANNEL_STATE_CLOSED settlement_block = ( channel12.close_transaction.finished_block_number + channel12.settle_timeout + 10 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block) # Load the new state with the channel settled channel12 = get_channelstate(node1, node2, token_network_identifier) assert channel.get_status(channel12) == CHANNEL_STATE_SETTLED
def test_channel_lifecycle(raiden_network, token_addresses, deposit): node1, node2 = raiden_network token_address = token_addresses[0] api1 = RaidenAPI(node1.raiden) api2 = RaidenAPI(node2.raiden) # nodes don't have a channel, so they are not healthchecking assert api1.get_node_network_state(api2.address) == NODE_NETWORK_UNKNOWN assert api2.get_node_network_state(api1.address) == NODE_NETWORK_UNKNOWN assert not api1.get_channel_list(token_address, api2.address) # open is a synchronous api api1.channel_open(token_address, api2.address) channels = api1.get_channel_list(token_address, api2.address) assert len(channels) == 1 channel12 = get_channelstate(node1, node2, token_address) assert channel.get_status(channel12) == CHANNEL_STATE_OPENED event_list1 = api1.get_channel_events( channel12.identifier, channel12.open_transaction.finished_block_number, ) assert event_list1 == [] # Load the new state with the deposit api1.channel_deposit(token_address, api2.address, deposit) channel12 = get_channelstate(node1, node2, token_address) assert channel.get_status(channel12) == CHANNEL_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(token_address, api2.address) == [channel12] # there is a channel open, they must be healthchecking each other assert api1.get_node_network_state(api2.address) == NODE_NETWORK_REACHABLE assert api2.get_node_network_state(api1.address) == NODE_NETWORK_REACHABLE event_list2 = api1.get_channel_events( channel12.identifier, channel12.open_transaction.finished_block_number, ) assert any((event['_event_type'] == b'ChannelNewBalance' and event['participant'] == address_encoder(api1.address)) for event in event_list2) api1.channel_close(token_address, api2.address) node1.raiden.poll_blockchain_events() # Load the new state with the channel closed channel12 = get_channelstate(node1, node2, token_address) event_list3 = api1.get_channel_events( channel12.identifier, channel12.open_transaction.finished_block_number, ) assert len(event_list3) > len(event_list2) assert any((event['_event_type'] == b'ChannelClosed' and event['closing_address'] == address_encoder(api1.address)) for event in event_list3) assert channel.get_status(channel12) == CHANNEL_STATE_CLOSED settlement_block = ( channel12.close_transaction.finished_block_number + channel12.settle_timeout + 10 # arbitrary number of additional blocks, used to wait for the settle() call ) wait_until_block(node1.raiden.chain, settlement_block) # Load the new state with the channel settled channel12 = get_channelstate(node1, node2, token_address) node1.raiden.poll_blockchain_events() assert channel.get_status(channel12) == CHANNEL_STATE_SETTLED
class ConnectionManager: """The ConnectionManager provides a high level abstraction for connecting to a Token network. Note: It is initialized with 0 funds; a connection to the token network will be only established _after_ calling `connect(funds)` """ # XXX Hack: for bootstrapping, the first node on a network opens a channel # with this address to become visible. BOOTSTRAP_ADDR_HEX = b'2' * 40 BOOTSTRAP_ADDR = unhexlify(BOOTSTRAP_ADDR_HEX) def __init__(self, raiden, token_address, channelgraph): self.lock = Semaphore() self.raiden = raiden self.api = RaidenAPI(raiden) self.channelgraph = channelgraph self.token_address = token_address self.funds = 0 self.initial_channel_target = 0 self.joinable_funds_target = 0 def connect(self, funds, initial_channel_target=3, joinable_funds_target=.4): """Connect to the network. Use this to establish a connection with the token network. Subsequent calls to `connect` are allowed, but will only affect the spendable funds and the connection strategy parameters for the future. `connect` will not close any channels. Note: the ConnectionManager does not discriminate manually opened channels from automatically opened ones. If the user manually opened channels, those deposit amounts will affect the funding per channel and the number of new channels opened. Args: funds (int): the amount of tokens spendable for this ConnectionManager. initial_channel_target (int): number of channels to open immediately joinable_funds_target (float): amount of funds not initially assigned """ if funds <= 0: raise ValueError('connecting needs a positive value for `funds`') if self.token_address in self.raiden.message_handler.blocked_tokens: self.raiden.message_handler.blocked_tokens.pop(self.token_address) self.initial_channel_target = initial_channel_target self.joinable_funds_target = joinable_funds_target open_channels = self.open_channels # there are already channels open if len(open_channels): log.debug( 'connect() called on an already joined token network', token_address=pex(self.token_address), open_channels=len(open_channels), sum_deposits=self.sum_deposits, funds=funds, ) if len(self.channelgraph.graph.nodes()) == 0: with self.lock: log.debug('bootstrapping token network.') # make ourselves visible self.api.open(self.token_address, ConnectionManager.BOOTSTRAP_ADDR) with self.lock: # set our available funds self.funds = funds # try to fullfill our connection goal self._add_new_partners() def leave_async(self): """ Async version of `leave()` """ leave_result = AsyncResult() gevent.spawn(self.leave).link(leave_result) return leave_result def leave(self, only_receiving=True): """ Leave the token network. This implies closing all channels and waiting for all channels to be settled. """ # set leaving state if self.token_address not in self.raiden.message_handler.blocked_tokens: self.raiden.message_handler.blocked_tokens.append( self.token_address) if self.initial_channel_target > 0: self.initial_channel_target = 0 closed_channels = self.close_all(only_receiving) self.wait_for_settle(closed_channels) return closed_channels def close_all(self, only_receiving=True): """ Close all channels in the token network. Note: By default we're just discarding all channels we haven't received anything. This potentially leaves deposits locked in channels after `closing`. This is "safe" from an accounting point of view (deposits can not be lost), but may still be undesirable from a liquidity point of view (deposits will only be freed after manually closing or after the partner closed the channel). If only_receiving is False then we close and settle all channels irrespective of them having received transfers or not. """ with self.lock: self.initial_channel_target = 0 channels_to_close = (self.receiving_channels[:] if only_receiving else self.open_channels[:]) for channel in channels_to_close: # FIXME: race condition, this can fail if channel was closed externally self.api.close(self.token_address, channel.partner_address) return channels_to_close def wait_for_settle(self, closed_channels): """Wait for all closed channels of the token network to settle. Note, that this does not time out. """ not_settled_channels = [ channel for channel in closed_channels if not channel.state != CHANNEL_STATE_SETTLED ] while any(c.state != CHANNEL_STATE_SETTLED for c in not_settled_channels): # wait for events to propagate gevent.sleep(self.raiden.alarm.wait_time) return True def join_channel(self, partner_address, partner_deposit): """Will be called, when we were selected as channel partner by another node. It will fund the channel with up to the partner's deposit, but not more than remaining funds or the initial funding per channel. If the connection manager has no funds, this is a noop. """ # not initialized if self.funds <= 0: return # in leaving state if self.leaving_state: return with self.lock: remaining = self.funds_remaining initial = self.initial_funding_per_partner joining_funds = min(partner_deposit, remaining, initial) if joining_funds <= 0: return self.api.deposit(self.token_address, partner_address, joining_funds) log.debug('joined a channel!', funds=joining_funds, me=pex(self.raiden.address), partner=pex(partner_address)) def retry_connect(self): """Will be called when new channels in the token network are detected. If the minimum number of channels was not yet established, it will try to open new channels. If the connection manager has no funds, this is a noop. """ # not initialized if self.funds <= 0: return # in leaving state if self.leaving_state: return with self.lock: if self.funds_remaining <= 0: return if len(self.open_channels) >= self.initial_channel_target: return # try to fullfill our connection goal self._add_new_partners() def _add_new_partners(self): """ This opens channels with a number of new partners according to the connection strategy parameter `self.initial_channel_target`. Each new channel will receive `self.initial_funding_per_partner` funding. """ # this could be a subsequent call, or some channels already open new_partner_count = max( 0, self.initial_channel_target - len(self.open_channels)) for partner in self.find_new_partners(new_partner_count): self._open_and_deposit(partner, self.initial_funding_per_partner) def _open_and_deposit(self, partner, funding_amount): """ Open a channel with `partner` and deposit `funding_amount` tokens. If the channel was already opened (a known race condition), this skips the opening and only deposits. """ try: self.api.open(self.token_address, partner) # this can fail because of a race condition, where the channel partner opens first except DuplicatedChannelError: log.info('partner opened channel first') channelgraph = self.raiden.token_to_channelgraph[self.token_address] if partner not in channelgraph.partneraddress_to_channel: self.raiden.poll_blockchain_events() if partner not in channelgraph.partneraddress_to_channel: log.error( 'Opening new channel failed; channel already opened, ' 'but partner not in channelgraph', partner=pex(partner), token_address=pex(self.token_address), ) else: try: self.api.deposit( self.token_address, partner, funding_amount, ) except AddressWithoutCode: log.warn( 'connection manager: channel closed just after it was created' ) except TransactionThrew: log.exception('connection manager: deposit failed') def find_new_partners(self, number): """Search the token network for potential channel partners. Args: number (int): number of partners to return """ known = set(c.partner_address for c in self.open_channels) known = known.union({self.__class__.BOOTSTRAP_ADDR}) known = known.union({self.raiden.address}) available = set(self.channelgraph.graph.nodes()) - known available = self._select_best_partners(available) log.debug('found {} partners'.format(len(available))) return available[:number] def _select_best_partners(self, partners): # FIXME: use a proper selection strategy # https://github.com/raiden-network/raiden/issues/576 return list(partners) @property def initial_funding_per_partner(self): """The calculated funding per partner depending on configuration and overall funding of the ConnectionManager. """ if self.initial_channel_target: return int(self.funds * (1 - self.joinable_funds_target) / self.initial_channel_target) else: return 0 @property def wants_more_channels(self): """True, if funds available and the `initial_channel_target` was not yet reached. """ if self.token_address in self.raiden.message_handler.blocked_tokens: return False return (self.funds_remaining > 0 and len(self.open_channels) < self.initial_channel_target) @property def funds_remaining(self): """The remaining funds after subtracting the already deposited amounts. """ if self.funds > 0: remaining = self.funds - self.sum_deposits assert isinstance(remaining, int) return remaining return 0 @property def open_channels(self): """Shorthand for getting our open channels in this token network. """ return [ channel for channel in self.api.get_channel_list( token_address=self.token_address) if channel.state == CHANNEL_STATE_OPENED ] @property def sum_deposits(self): """Shorthand for getting sum of all open channels deposited funds""" return sum(channel.contract_balance for channel in self.open_channels) @property def receiving_channels(self): """Shorthand for getting channels that had received any transfers in this token network. """ return [ channel for channel in self.open_channels if len(channel.received_transfers) ] @property def min_settle_blocks(self): """Returns the minimum necessary waiting time to settle all channels. """ channels = self.receiving_channels timeouts = [0] current_block = self.raiden.get_block_number() for channel in channels: if channel.state == CHANNEL_STATE_CLOSED: since_closed = current_block - channel.external_state._closed_block elif channel.state == CHANNEL_STATE_OPENED: # it will at least take one more block to call close since_closed = -1 else: since_closed = 0 timeouts.append(channel.settle_timeout - since_closed) return max(timeouts) @property def leaving_state(self): return (self.token_address in self.raiden.message_handler.blocked_tokens or self.initial_channel_target < 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, )