示例#1
0
def test_deposit_updates_balance_immediately(raiden_chain, token_addresses):
    """ Test that the balance of a channel gets updated by the deposit() call
    immediately and without having to wait for the
    `ContractReceiveChannelNewBalance` message since the API needs to return
    the channel with the deposit balance updated.
    """
    app0, app1 = raiden_chain
    token_address = token_addresses[0]

    api0 = RaidenAPI(app0.raiden)

    old_state = get_channelstate(app0, app1, token_address)
    api0.channel_deposit(token_address, app1.raiden.address, 10)
    new_state = get_channelstate(app0, app1, token_address)

    assert new_state.our_state.contract_balance == old_state.our_state.contract_balance + 10
示例#2
0
def test_deposit_updates_balance_immediately(raiden_chain, token_addresses):
    """ Test that the balance of a channel gets updated by the deposit() call
    immediately and without having to wait for the
    `ContractReceiveChannelNewBalance` message since the API needs to return
    the channel with the deposit balance updated.
    """
    app0, app1 = raiden_chain
    registry_address = app0.raiden.default_registry.address
    token_address = token_addresses[0]
    token_network_identifier = views.get_token_network_identifier_by_token_address(
        views.state_from_app(app0),
        app0.raiden.default_registry.address,
        token_address,
    )

    api0 = RaidenAPI(app0.raiden)

    old_state = get_channelstate(app0, app1, token_network_identifier)
    api0.channel_deposit(registry_address, token_address, app1.raiden.address,
                         10)
    new_state = get_channelstate(app0, app1, token_network_identifier)

    assert new_state.our_state.contract_balance == old_state.our_state.contract_balance + 10
示例#3
0
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
示例#4
0
def run(
        privatekey,
        registry_contract_address,
        secret_registry_contract_address,
        discovery_contract_address,
        listen_address,
        structlog,
        logfile,
        scenario,
        stage_prefix,
):  # pylint: disable=unused-argument

    # TODO: only enabled structlog on "initiators"
    structlog.configure(structlog, log_file=logfile)

    (listen_host, listen_port) = split_endpoint(listen_address)

    config = App.DEFAULT_CONFIG.copy()
    config['host'] = listen_host
    config['port'] = listen_port
    config['privatekey_hex'] = privatekey

    privatekey_bin = decode_hex(privatekey)

    rpc_client = JSONRPCClient(
        '127.0.0.1',
        8545,
        privatekey_bin,
    )

    blockchain_service = BlockChainService(
        privatekey_bin,
        rpc_client,
        GAS_PRICE,
    )

    discovery = ContractDiscovery(
        blockchain_service,
        decode_hex(discovery_contract_address),
    )

    registry = blockchain_service.registry(
        registry_contract_address,
    )

    secret_registry = blockchain_service.secret_registry(
        secret_registry_contract_address,
    )

    throttle_policy = TokenBucket(
        config['protocol']['throttle_capacity'],
        config['protocol']['throttle_fill_rate'],
    )

    transport = UDPTransport(
        discovery,
        server._udp_socket((listen_host, listen_port)),
        throttle_policy,
        config['protocol'],
        dict(),
    )

    app = App(
        config,
        blockchain_service,
        registry,
        secret_registry,
        transport,
        discovery,
    )

    app.discovery.register(
        app.raiden.address,
        listen_host,
        listen_port,
    )

    app.raiden.install_payment_network_filters(app.raiden.default_registry.address)

    if scenario:
        script = json.load(scenario)

        tools = ConsoleTools(
            app.raiden,
            app.discovery,
            app.config['settle_timeout'],
            app.config['reveal_timeout'],
        )

        transfers_by_peer = {}

        tokens = script['tokens']
        token_address = None
        peer = None
        our_node = hexlify(app.raiden.address)
        log.warning('our address is {}'.format(our_node))
        for token in tokens:
            # skip tokens that we're not part of
            nodes = token['channels']
            if our_node not in nodes:
                continue

            partner_nodes = [
                node
                for node in nodes
                if node != our_node
            ]

            # allow for prefunded tokens
            if 'token_address' in token:
                token_address = token['token_address']
            else:
                token_address = tools.create_token(registry_contract_address)

            transfers_with_amount = token['transfers_with_amount']

            # FIXME: in order to do bidirectional channels, only one side
            # (i.e. only token['channels'][0]) should
            # open; others should join by calling
            # raiden.api.deposit, AFTER the channel came alive!

            # NOTE: leaving unidirectional for now because it most
            #       probably will get to higher throughput

            log.warning('Waiting for all nodes to come online')

            api = RaidenAPI(app.raiden)

            for node in partner_nodes:
                api.start_health_check_for(node)

            while True:
                all_reachable = all(
                    api.get_node_network_state(node) == NODE_NETWORK_REACHABLE
                    for node in partner_nodes
                )

                if all_reachable:
                    break

                gevent.sleep(5)

            log.warning('All nodes are online')

            if our_node != nodes[-1]:
                our_index = nodes.index(our_node)
                peer = nodes[our_index + 1]

                tools.token_network_register(app.raiden.default_registry.address, token_address)
                amount = transfers_with_amount[nodes[-1]]

                while True:
                    try:
                        app.discovery.get(peer.decode('hex'))
                        break
                    except KeyError:
                        log.warning('Error: peer {} not found in discovery'.format(peer))
                        time.sleep(random.randrange(30))

                while True:
                    try:
                        log.warning('Opening channel with {} for {}'.format(peer, token_address))
                        api.channel_open(app.raiden.default_registry.address, token_address, peer)
                        break
                    except KeyError:
                        log.warning('Error: could not open channel with {}'.format(peer))
                        time.sleep(random.randrange(30))

                while True:
                    try:
                        log.warning('Funding channel with {} for {}'.format(peer, token_address))
                        api.channel_deposit(
                            app.raiden.default_registry.address,
                            token_address,
                            peer,
                            amount,
                        )
                        break
                    except Exception:
                        log.warning('Error: could not deposit {} for {}'.format(amount, peer))
                        time.sleep(random.randrange(30))

                if our_index == 0:
                    last_node = nodes[-1]
                    transfers_by_peer[last_node] = int(amount)
            else:
                peer = nodes[-2]

        if stage_prefix is not None:
            open('{}.stage1'.format(stage_prefix), 'a').close()
            log.warning('Done with initialization, waiting to continue...')
            event = gevent.event.Event()
            gevent.signal(signal.SIGUSR2, event.set)
            event.wait()

        transfer_results = {'total_time': 0, 'timestamps': []}

        def transfer(token_address, amount_per_transfer, total_transfers, peer, is_async):
            def transfer_():
                log.warning('Making {} transfers to {}'.format(total_transfers, peer))
                initial_time = time.time()
                times = [0] * total_transfers
                for index in range(total_transfers):
                    RaidenAPI(app.raiden).transfer(
                        app.raiden.default_registry.address,
                        token_address.decode('hex'),
                        amount_per_transfer,
                        peer,
                    )
                    times[index] = time.time()

                transfer_results['total_time'] = time.time() - initial_time
                transfer_results['timestamps'] = times

                log.warning('Making {} transfers took {}'.format(
                    total_transfers, transfer_results['total_time']))
                log.warning('Times: {}'.format(times))

            if is_async:
                return gevent.spawn(transfer_)
            else:
                transfer_()

        # If sending to multiple targets, do it asynchronously, otherwise
        # keep it simple and just send to the single target on my thread.
        if len(transfers_by_peer) > 1:
            greenlets = []
            for peer_, amount in transfers_by_peer.items():
                greenlet = transfer(token_address, 1, amount, peer_, True)
                if greenlet is not None:
                    greenlets.append(greenlet)

            gevent.joinall(greenlets)

        elif len(transfers_by_peer) == 1:
            for peer_, amount in transfers_by_peer.items():
                transfer(token_address, 1, amount, peer_, False)

        log.warning('Waiting for termination')

        open('{}.stage2'.format(stage_prefix), 'a').close()
        log.warning('Waiting for transfers to finish, will write results...')
        event = gevent.event.Event()
        gevent.signal(signal.SIGUSR2, event.set)
        event.wait()

        open('{}.stage3'.format(stage_prefix), 'a').close()
        event = gevent.event.Event()
        gevent.signal(signal.SIGQUIT, event.set)
        gevent.signal(signal.SIGTERM, event.set)
        gevent.signal(signal.SIGINT, event.set)
        event.wait()

    else:
        log.warning('No scenario file supplied, doing nothing!')

        open('{}.stage2'.format(stage_prefix), 'a').close()
        event = gevent.event.Event()
        gevent.signal(signal.SIGQUIT, event.set)
        gevent.signal(signal.SIGTERM, event.set)
        gevent.signal(signal.SIGINT, event.set)
        event.wait()

    app.stop()
示例#5
0
class ConsoleTools:
    def __init__(self, raiden_service, discovery, settle_timeout,
                 reveal_timeout):
        self._chain = raiden_service.chain
        self._raiden = raiden_service
        self._api = RaidenAPI(raiden_service)
        self._discovery = discovery
        self.settle_timeout = settle_timeout
        self.reveal_timeout = reveal_timeout

    def create_token(self,
                     registry_address,
                     initial_alloc=10**6,
                     name='raidentester',
                     symbol='RDT',
                     decimals=2,
                     timeout=60,
                     auto_register=True):
        """ Create a proxy for a new HumanStandardToken (ERC20), that is
        initialized with Args(below).
        Per default it will be registered with 'raiden'.

        Args:
            initial_alloc (int): amount of initial tokens.
            name (str): human readable token name.
            symbol (str): token shorthand symbol.
            decimals (int): decimal places.
            timeout (int): timeout in seconds for creation.
            auto_register (boolean): if True(default), automatically register
                the token with raiden.

        Returns:
            token_address_hex: the hex encoded address of the new token/token.
        """
        contract_path = get_contract_path('HumanStandardToken.sol')
        # Deploy a new ERC20 token
        token_proxy = self._chain.client.deploy_solidity_contract(
            'HumanStandardToken',
            compile_file(contract_path),
            dict(),
            (initial_alloc, name, decimals, symbol),
            contract_path=contract_path,
            timeout=timeout,
        )
        token_address_hex = hexlify(token_proxy.contract_address)
        if auto_register:
            self.register_token(registry_address, token_address_hex)
        print("Successfully created {}the token '{}'.".format(
            'and registered ' if auto_register else ' ', name))
        return token_address_hex

    def register_token(self, registry_address_hex, token_address_hex):
        """ Register a token with the raiden token manager.

        Args:
            registry_address: registry address
            token_address_hex (string): a hex encoded token address.

        Returns:
            channel_manager: the channel_manager contract_proxy.
        """

        registry = self._raiden.chain.registry(registry_address_hex)

        # Add the ERC20 token to the raiden registry
        token_address = safe_address_decode(token_address_hex)
        registry.add_token(token_address)

        # Obtain the channel manager for the token
        channel_manager = registry.manager_by_token(token_address)

        # Register the channel manager with the raiden registry
        self._raiden.register_channel_manager(channel_manager.address)
        return channel_manager

    def open_channel_with_funding(self,
                                  registry_address_hex,
                                  token_address_hex,
                                  peer_address_hex,
                                  amount,
                                  settle_timeout=None,
                                  reveal_timeout=None):
        """ Convenience method to open a channel.

        Args:
            registry_address_hex (str): hex encoded address of the registry for the channel.
            token_address_hex (str): hex encoded address of the token for the channel.
            peer_address_hex (str): hex encoded address of the channel peer.
            amount (int): amount of initial funding of the channel.
            settle_timeout (int): amount of blocks for the settle time (if None use app defaults).
            reveal_timeout (int): amount of blocks for the reveal time (if None use app defaults).

        Return:
            netting_channel: the (newly opened) netting channel object.
        """
        # Check, if peer is discoverable
        registry_address = safe_address_decode(registry_address_hex)
        peer_address = safe_address_decode(peer_address_hex)
        token_address = safe_address_decode(token_address_hex)
        try:
            self._discovery.get(peer_address)
        except KeyError:
            print('Error: peer {} not found in discovery'.format(
                peer_address_hex))
            return

        self._api.channel_open(
            registry_address,
            token_address,
            peer_address,
            settle_timeout=settle_timeout,
            reveal_timeout=reveal_timeout,
        )

        return self._api.channel_deposit(
            registry_address,
            token_address,
            peer_address,
            amount,
        )

    def wait_for_contract(self, contract_address_hex, timeout=None):
        """ Wait until a contract is mined

        Args:
            contract_address_hex (string): hex encoded address of the contract
            timeout (int): time to wait for the contract to get mined

        Returns:
            True if the contract got mined, false otherwise
        """
        contract_address = safe_address_decode(contract_address_hex)
        start_time = time.time()
        result = self._raiden.chain.client.eth_getCode(contract_address)

        current_time = time.time()
        while len(result) == 0:
            if timeout and start_time + timeout > current_time:
                return False

            result = self._raiden.chain.client.eth_getCode(contract_address)
            gevent.sleep(0.5)

            current_time = time.time()

        return len(result) > 0
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, registry_address, token_address):
        # TODO:
        # - Add timeout for transaction polling, used to overwrite the RaidenAPI
        # defaults
        # - Add a proper selection strategy (#576)
        self.funds = 0
        self.initial_channel_target = 0
        self.joinable_funds_target = 0

        self.raiden = raiden
        self.registry_address = registry_address
        self.token_address = token_address

        self.lock = Semaphore()  #: protects self.funds and self.initial_channel_target
        self.api = RaidenAPI(raiden)

    def connect(
            self,
            funds: int,
            initial_channel_target: int = 3,
            joinable_funds_target: float = 0.4,
    ):
        """Connect to the 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: Target amount of tokens spendable to join the network.
            initial_channel_target: Target number of channels to open.
            joinable_funds_target: Amount of funds not initially assigned.
        """
        if funds <= 0:
            raise ValueError('connecting needs a positive value for `funds`')

        with self.lock:
            self.funds = funds
            self.initial_channel_target = initial_channel_target
            self.joinable_funds_target = joinable_funds_target

            log_open_channels(self.raiden, self.registry_address, self.token_address, funds)

            qty_network_channels = views.count_token_network_channels(
                views.state_from_raiden(self.raiden),
                self.registry_address,
                self.token_address,
            )

            if not qty_network_channels:
                log.debug('bootstrapping token network.')
                # make ourselves visible
                self.api.channel_open(
                    self.registry_address,
                    self.token_address,
                    self.BOOTSTRAP_ADDR,
                )
            else:
                self._open_channels(self.registry_address)

    def leave_async(self, only_receiving=True):
        """ Async version of `leave()` """
        leave_result = AsyncResult()
        gevent.spawn(self.leave, only_receiving).link(leave_result)
        return leave_result

    def leave(self, registry_address, only_receiving=True):
        """ Leave the token network.

        This implies closing all channels and waiting for all channels to be
        settled.

        Note: By default we're just discarding all channels for which 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

            if only_receiving:
                channels_to_close = views.get_channestate_for_receiving(
                    views.state_from_raiden(self.raiden),
                    registry_address,
                    self.token_address,
                )
            else:
                channels_to_close = views.get_channelstate_open(
                    views.state_from_raiden(self.raiden),
                    registry_address,
                    self.token_address,
                )

            partner_addresses = [
                channel_state.partner_state.address
                for channel_state in channels_to_close
            ]
            self.api.channel_batch_close(
                registry_address,
                self.token_address,
                partner_addresses,
            )

            channel_ids = [
                channel_state.identifier
                for channel_state in channels_to_close
            ]

            waiting.wait_for_settle(
                self.raiden,
                registry_address,
                self.token_address,
                channel_ids,
                self.raiden.alarm.wait_time,
            )

        return channels_to_close

    def join_channel(self, registry_address, 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 partners deposit, but
        not more than remaining funds or the initial funding per channel.

        If the connection manager has no funds, this is a noop.
        """
        with self.lock:
            joining_funds = min(
                partner_deposit,
                self._funds_remaining,
                self._initial_funding_per_partner,
            )
            if joining_funds <= 0 or self._leaving_state:
                return

            self.api.channel_deposit(
                registry_address,
                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, registry_address):
        """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.
        """
        with self.lock:
            if self._funds_remaining <= 0 or self._leaving_state:
                return

            open_channels = views.get_channelstate_open(
                views.state_from_raiden(self.raiden),
                registry_address,
                self.token_address,
            )
            if len(open_channels) >= self.initial_channel_target:
                return

            self._open_channels(registry_address)

    def find_new_partners(self, number: int):
        """Search the token network for potential channel partners.

        Args:
            number: number of partners to return
        """
        open_channels = views.get_channelstate_open(
            views.state_from_raiden(self.raiden),
            self.registry_address,
            self.token_address,
        )
        known = set(channel_state.partner_state.address for channel_state in open_channels)
        known.add(self.BOOTSTRAP_ADDR)
        known.add(self.raiden.address)

        participants_addresses = views.get_participants_addresses(
            views.state_from_raiden(self.raiden),
            self.registry_address,
            self.token_address,
        )

        available = participants_addresses - known
        new_partners = list(available)[:number]

        log.debug('found {} partners'.format(len(available)))

        return new_partners

    def _open_channels(self, registry_address):
        """ Open channels until there are `self.initial_channel_target`
        channels open. Do nothing if there are enough channels open already.

        Note:
            - This method must be called with the lock held.
        """
        open_channels = views.get_channelstate_open(
            views.state_from_raiden(self.raiden),
            registry_address,
            self.token_address,
        )

        qty_channels_to_open = self.initial_channel_target - len(open_channels)
        if qty_channels_to_open <= 0:
            return

        for partner in self.find_new_partners(qty_channels_to_open):
            try:
                self.api.channel_open(
                    registry_address,
                    self.token_address,
                    partner,
                )
            except DuplicatedChannelError:
                # This can fail because of a race condition, where the channel
                # partner opens first.
                log.info('partner opened channel first')

            try:
                self.api.channel_deposit(
                    registry_address,
                    self.token_address,
                    partner,
                    self._initial_funding_per_partner,
                )
            except AddressWithoutCode:
                log.warn('connection manager: channel closed just after it was created')
            except TransactionThrew:
                log.exception('connection manager: deposit failed')

    @property
    def _initial_funding_per_partner(self) -> int:
        """The calculated funding per partner depending on configuration and
        overall funding of the ConnectionManager.

        Note:
            - This attribute must be accessed with the lock held.
        """
        if self.initial_channel_target:
            return int(
                self.funds * (1 - self.joinable_funds_target) /
                self.initial_channel_target,
            )

        return 0

    @property
    def _funds_remaining(self) -> int:
        """The remaining funds after subtracting the already deposited amounts.

        Note:
            - This attribute must be accessed with the lock held.
        """
        if self.funds > 0:
            sum_deposits = views.get_our_capacity_for_token_network(
                views.state_from_raiden(self.raiden),
                self.registry_address,
                self.token_address,
            )

            remaining = self.funds - sum_deposits
            return remaining

        return 0

    @property
    def _leaving_state(self) -> bool:
        """True if the node is leaving the token network.

        Note:
            - This attribute must be accessed with the lock held.
        """
        return self.initial_channel_target < 1
示例#7
0
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
示例#8
0
def run(
        privatekey,
        registry_contract_address,
        secret_registry_contract_address,
        discovery_contract_address,
        listen_address,
        structlog,
        logfile,
        scenario,
        stage_prefix,
):  # pylint: disable=unused-argument

    # TODO: only enabled structlog on "initiators"
    structlog.configure(structlog, log_file=logfile)

    (listen_host, listen_port) = split_endpoint(listen_address)

    config = App.DEFAULT_CONFIG.copy()
    config['host'] = listen_host
    config['port'] = listen_port
    config['privatekey_hex'] = privatekey

    privatekey_bin = decode_hex(privatekey)

    rpc_client = JSONRPCClient(
        '127.0.0.1',
        8545,
        privatekey_bin,
    )

    blockchain_service = BlockChainService(privatekey_bin, rpc_client)

    discovery = ContractDiscovery(
        blockchain_service,
        decode_hex(discovery_contract_address),
    )

    registry = blockchain_service.token_network_registry(
        registry_contract_address,
    )

    secret_registry = blockchain_service.secret_registry(
        secret_registry_contract_address,
    )

    throttle_policy = TokenBucket(
        config['protocol']['throttle_capacity'],
        config['protocol']['throttle_fill_rate'],
    )

    transport = UDPTransport(
        discovery=discovery,
        udpsocket=gevent.server._udp_socket((listen_host, listen_port)),
        throttle_policy=throttle_policy,
        config=config['protocol'],
    )

    app = App(
        config=config,
        chain=blockchain_service,
        query_start_block=0,
        default_registry=registry,
        default_secret_registry=secret_registry,
        transport=transport,
        discovery=discovery,
    )

    app.discovery.register(
        app.raiden.address,
        listen_host,
        listen_port,
    )

    from_block = 0
    app.raiden.install_all_blockchain_filters(
        app.raiden.default_registry,
        app.raiden.default_secret_registry,
        from_block,
    )

    if scenario:
        script = json.load(scenario)

        tools = ConsoleTools(
            app.raiden,
            app.discovery,
            app.config['settle_timeout'],
            app.config['reveal_timeout'],
        )

        transfers_by_peer = {}

        tokens = script['tokens']
        token_address = None
        peer = None
        our_node = hexlify(app.raiden.address)
        log.warning('our address is {}'.format(our_node))
        for token in tokens:
            # skip tokens that we're not part of
            nodes = token['channels']
            if our_node not in nodes:
                continue

            partner_nodes = [
                node
                for node in nodes
                if node != our_node
            ]

            # allow for prefunded tokens
            if 'token_address' in token:
                token_address = token['token_address']
            else:
                token_address = tools.create_token(registry_contract_address)

            transfers_with_amount = token['transfers_with_amount']

            # FIXME: in order to do bidirectional channels, only one side
            # (i.e. only token['channels'][0]) should
            # open; others should join by calling
            # raiden.api.deposit, AFTER the channel came alive!

            # NOTE: leaving unidirectional for now because it most
            #       probably will get to higher throughput

            log.warning('Waiting for all nodes to come online')

            api = RaidenAPI(app.raiden)

            for node in partner_nodes:
                api.start_health_check_for(node)

            while True:
                all_reachable = all(
                    api.get_node_network_state(node) == NODE_NETWORK_REACHABLE
                    for node in partner_nodes
                )

                if all_reachable:
                    break

                gevent.sleep(5)

            log.warning('All nodes are online')

            if our_node != nodes[-1]:
                our_index = nodes.index(our_node)
                peer = nodes[our_index + 1]

                tools.token_network_register(app.raiden.default_registry.address, token_address)
                amount = transfers_with_amount[nodes[-1]]

                while True:
                    try:
                        app.discovery.get(peer.decode('hex'))
                        break
                    except KeyError:
                        log.warning('Error: peer {} not found in discovery'.format(peer))
                        time.sleep(random.randrange(30))

                while True:
                    try:
                        log.warning('Opening channel with {} for {}'.format(peer, token_address))
                        api.channel_open(app.raiden.default_registry.address, token_address, peer)
                        break
                    except KeyError:
                        log.warning('Error: could not open channel with {}'.format(peer))
                        time.sleep(random.randrange(30))

                while True:
                    try:
                        log.warning('Funding channel with {} for {}'.format(peer, token_address))
                        api.channel_deposit(
                            app.raiden.default_registry.address,
                            token_address,
                            peer,
                            amount,
                        )
                        break
                    except Exception:
                        log.warning('Error: could not deposit {} for {}'.format(amount, peer))
                        time.sleep(random.randrange(30))

                if our_index == 0:
                    last_node = nodes[-1]
                    transfers_by_peer[last_node] = int(amount)

        if stage_prefix is not None:
            open('{}.stage1'.format(stage_prefix), 'a').close()
            log.warning('Done with initialization, waiting to continue...')
            event = gevent.event.Event()
            gevent.signal(signal.SIGUSR2, event.set)
            event.wait()

        transfer_results = {'total_time': 0, 'timestamps': []}

        def transfer(token_address, amount_per_transfer, total_transfers, peer, is_async):
            def transfer_():
                log.warning('Making {} transfers to {}'.format(total_transfers, peer))
                initial_time = time.time()
                times = [0] * total_transfers
                for index in range(total_transfers):
                    RaidenAPI(app.raiden).transfer(
                        app.raiden.default_registry.address,
                        token_address.decode('hex'),
                        amount_per_transfer,
                        peer,
                    )
                    times[index] = time.time()

                transfer_results['total_time'] = time.time() - initial_time
                transfer_results['timestamps'] = times

                log.warning('Making {} transfers took {}'.format(
                    total_transfers, transfer_results['total_time']))
                log.warning('Times: {}'.format(times))

            if is_async:
                return gevent.spawn(transfer_)
            else:
                transfer_()

        # If sending to multiple targets, do it asynchronously, otherwise
        # keep it simple and just send to the single target on my thread.
        if len(transfers_by_peer) > 1:
            greenlets = []
            for peer_, amount in transfers_by_peer.items():
                greenlet = transfer(token_address, 1, amount, peer_, True)
                if greenlet is not None:
                    greenlets.append(greenlet)

            gevent.joinall(greenlets)

        elif len(transfers_by_peer) == 1:
            for peer_, amount in transfers_by_peer.items():
                transfer(token_address, 1, amount, peer_, False)

        log.warning('Waiting for termination')

        open('{}.stage2'.format(stage_prefix), 'a').close()
        log.warning('Waiting for transfers to finish, will write results...')
        event = gevent.event.Event()
        gevent.signal(signal.SIGUSR2, event.set)
        event.wait()

        open('{}.stage3'.format(stage_prefix), 'a').close()
        event = gevent.event.Event()
        gevent.signal(signal.SIGQUIT, event.set)
        gevent.signal(signal.SIGTERM, event.set)
        gevent.signal(signal.SIGINT, event.set)
        event.wait()

    else:
        log.warning('No scenario file supplied, doing nothing!')

        open('{}.stage2'.format(stage_prefix), 'a').close()
        event = gevent.event.Event()
        gevent.signal(signal.SIGQUIT, event.set)
        gevent.signal(signal.SIGTERM, event.set)
        gevent.signal(signal.SIGINT, event.set)
        event.wait()

    app.stop()