Пример #1
0
def call_minting_method(client: JSONRPCClient, proxy: ContractProxy,
                        contract_method: MintingMethod,
                        args: List[Any]) -> TransactionHash:
    """ Try to mint tokens by calling `contract_method` on `proxy`.

    Raises:
        MintFailed if anything goes wrong.
    """
    method = contract_method.value

    gas_limit = proxy.estimate_gas("latest", method, *args)
    if gas_limit is None:
        raise MintFailed(f"Gas estimation failed. Make sure the token has a "
                         f"method named {method} with the expected signature.")

    try:
        tx_hash = proxy.transact(method, gas_limit, *args)
    except (RaidenError, ValueError) as e:
        # Re-raise TransactionAlreadyPending etc. as MintFailed.
        # Since the token minting api is not standardized, we also catch ValueErrors
        # that might fall through ContractProxy.transact()'s exception handling.
        raise MintFailed(str(e))

    receipt = client.poll(tx_hash)
    if check_transaction_threw(receipt=receipt):
        raise MintFailed(
            f"Call to contract method {method}: Transaction failed.")

    return tx_hash
Пример #2
0
def mint_token_if_balance_low(
    token_contract: ContractProxy,
    target_address: str,
    min_balance: int,
    fund_amount: int,
    gas_limit: int,
    mint_msg: str,
    no_action_msg: str = None,
) -> Optional[TransactionHash]:
    """ Check token balance and mint if below minimum """
    balance = token_contract.contract.functions.balanceOf(target_address).call()
    if balance < min_balance:
        mint_amount = fund_amount - balance
        log.debug(mint_msg, address=target_address, amount=mint_amount)
        return token_contract.transact("mintFor", gas_limit, mint_amount, target_address)
    else:
        if no_action_msg:
            log.debug(no_action_msg, balance=balance)

    return None
Пример #3
0
class NettingChannel:
    def __init__(
        self,
        jsonrpc_client,
        channel_address,
        poll_timeout=DEFAULT_POLL_TIMEOUT,
    ):
        contract = jsonrpc_client.new_contract(
            CONTRACT_MANAGER.get_contract_abi(CONTRACT_NETTING_CHANNEL),
            to_normalized_address(channel_address),
        )
        self.proxy = ContractProxy(jsonrpc_client, contract)

        self.address = channel_address
        self.client = jsonrpc_client
        self.poll_timeout = poll_timeout
        # Prevents concurrent deposit, close, or settle operations on the same channel
        self.channel_operations_lock = RLock()
        self.client = jsonrpc_client
        self.node_address = privatekey_to_address(self.client.privkey)
        CONTRACT_MANAGER.check_contract_version(
            self.proxy.contract.functions.contract_version().call(),
            CONTRACT_NETTING_CHANNEL,
        )

        # check we are a participant of the given channel
        self.detail()
        self._check_exists()

    def _check_exists(self):
        check_address_has_code(self.client, self.address, 'Netting Channel')

    def _call_and_check_result(self, function_name: str):
        fn = getattr(self.proxy.contract.functions, function_name)
        try:
            call_result = fn().call()
        except BadFunctionCallOutput as e:
            raise AddressWithoutCode(str(e))

        if call_result == b'':
            self._check_exists()
            raise RuntimeError(
                "Call to '{}' returned nothing".format(function_name), )

        return call_result

    def token_address(self):
        """ Returns the type of token that can be transferred by the channel.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        address = self._call_and_check_result('tokenAddress')
        return to_canonical_address(address)

    def detail(self):
        """ Returns a dictionary with the details of the netting channel.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        data = self._call_and_check_result('addressAndBalance')

        settle_timeout = self.settle_timeout()
        our_address = privatekey_to_address(self.client.privkey)

        if to_canonical_address(data[0]) == our_address:
            return {
                'our_address': to_canonical_address(data[0]),
                'our_balance': data[1],
                'partner_address': to_canonical_address(data[2]),
                'partner_balance': data[3],
                'settle_timeout': settle_timeout,
            }

        if to_canonical_address(data[2]) == our_address:
            return {
                'our_address': to_canonical_address(data[2]),
                'our_balance': data[3],
                'partner_address': to_canonical_address(data[0]),
                'partner_balance': data[1],
                'settle_timeout': settle_timeout,
            }

        raise ValueError(
            'We [{}] are not a participant of the given channel ({}, {})'.
            format(
                pex(our_address),
                data[0],
                data[2],
            ))

    def settle_timeout(self):
        """ Returns the netting channel settle_timeout.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        return self._call_and_check_result('settleTimeout')

    def opened(self):
        """ Returns the block in which the channel was created.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        return self._call_and_check_result('opened')

    def closed(self):
        """ Returns the block in which the channel was closed or 0.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        return self._call_and_check_result('closed')

    def closing_address(self):
        """ Returns the address of the closer, if the channel is closed, None
        otherwise.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        closer = self.proxy.contract.functions.closingAddress().call()

        if closer:
            return to_canonical_address(closer)

        return None

    def can_transfer(self):
        """ Returns True if the channel is opened and the node has deposit in
        it.

        Note: Having a deposit does not imply having a balance for off-chain
        transfers.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
        """
        closed = self.closed()

        if closed != 0:
            return False

        return self.detail()['our_balance'] > 0

    def set_total_deposit(self, total_deposit):
        """ Set the total deposit of token in the channel.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
            ChannelBusyError: If the channel is busy with another operation
            RuntimeError: If the netting channel token address is empty.
        """
        if not isinstance(total_deposit, int):
            raise ValueError('total_deposit needs to be an integral number.')
        log.info(
            'set_total_deposit called',
            node=pex(self.node_address),
            contract=pex(self.address),
            amount=total_deposit,
        )

        if not self.channel_operations_lock.acquire(blocking=False):
            raise ChannelBusyError(
                f'Channel with address {self.address} is '
                f'busy with another ongoing operation.', )

        with releasing(self.channel_operations_lock):
            transaction_hash = self.proxy.transact(
                'setTotalDeposit',
                total_deposit,
            )

            self.client.poll(
                unhexlify(transaction_hash),
                timeout=self.poll_timeout,
            )

            receipt_or_none = check_transaction_threw(self.client,
                                                      transaction_hash)
            if receipt_or_none:
                log.critical(
                    'set_total_deposit failed',
                    node=pex(self.node_address),
                    contract=pex(self.address),
                )

                self._check_exists()
                raise TransactionThrew('Deposit', receipt_or_none)

            log.info(
                'set_total_deposit successful',
                node=pex(self.node_address),
                contract=pex(self.address),
                amount=total_deposit,
            )

    def close(self, nonce, transferred_amount, locked_amount, locksroot,
              extra_hash, signature):
        """ Close the channel using the provided balance proof.

        Raises:
            AddressWithoutCode: If the channel was settled prior to the call.
            ChannelBusyError: If the channel is busy with another operation.
        """

        log.info(
            'close called',
            node=pex(self.node_address),
            contract=pex(self.address),
            nonce=nonce,
            transferred_amount=transferred_amount,
            locked_amount=locked_amount,
            locksroot=encode_hex(locksroot),
            extra_hash=encode_hex(extra_hash),
            signature=encode_hex(signature),
        )

        if not self.channel_operations_lock.acquire(blocking=False):
            raise ChannelBusyError(
                f'Channel with address {self.address} is '
                f'busy with another ongoing operation.', )

        with releasing(self.channel_operations_lock):
            transaction_hash = self.proxy.transact(
                'close',
                nonce,
                transferred_amount,
                locked_amount,
                locksroot,
                extra_hash,
                signature,
            )
            self.client.poll(unhexlify(transaction_hash),
                             timeout=self.poll_timeout)

            receipt_or_none = check_transaction_threw(self.client,
                                                      transaction_hash)
            if receipt_or_none:
                log.critical(
                    'close failed',
                    node=pex(self.node_address),
                    contract=pex(self.address),
                    nonce=nonce,
                    transferred_amount=transferred_amount,
                    locked_amount=locked_amount,
                    locksroot=encode_hex(locksroot),
                    extra_hash=encode_hex(extra_hash),
                    signature=encode_hex(signature),
                )
                self._check_exists()
                raise TransactionThrew('Close', receipt_or_none)

            log.info(
                'close successful',
                node=pex(self.node_address),
                contract=pex(self.address),
                nonce=nonce,
                transferred_amount=transferred_amount,
                locked_amount=locked_amount,
                locksroot=encode_hex(locksroot),
                extra_hash=encode_hex(extra_hash),
                signature=encode_hex(signature),
            )

    def update_transfer(
        self,
        nonce,
        transferred_amount,
        locked_amount,
        locksroot,
        extra_hash,
        signature,
    ):
        if signature:
            log.info(
                'updateTransfer called',
                node=pex(self.node_address),
                contract=pex(self.address),
                nonce=nonce,
                transferred_amount=transferred_amount,
                locked_amount=locked_amount,
                locksroot=encode_hex(locksroot),
                extra_hash=encode_hex(extra_hash),
                signature=encode_hex(signature),
            )

            transaction_hash = self.proxy.transact(
                'updateTransfer',
                nonce,
                transferred_amount,
                locked_amount,
                locksroot,
                extra_hash,
                signature,
            )

            self.client.poll(
                unhexlify(transaction_hash),
                timeout=self.poll_timeout,
            )

            receipt_or_none = check_transaction_threw(self.client,
                                                      transaction_hash)
            if receipt_or_none:
                log.critical(
                    'updateTransfer failed',
                    node=pex(self.node_address),
                    contract=pex(self.address),
                    nonce=nonce,
                    transferred_amount=transferred_amount,
                    locked_amount=locked_amount,
                    locksroot=encode_hex(locksroot),
                    extra_hash=encode_hex(extra_hash),
                    signature=encode_hex(signature),
                )
                self._check_exists()
                raise TransactionThrew('Update Transfer', receipt_or_none)

            log.info(
                'updateTransfer successful',
                node=pex(self.node_address),
                contract=pex(self.address),
                nonce=nonce,
                transferred_amount=transferred_amount,
                locked_amount=locked_amount,
                locksroot=encode_hex(locksroot),
                extra_hash=encode_hex(extra_hash),
                signature=encode_hex(signature),
            )

    def unlock(self, unlock_proof):
        log.info(
            'unlock called',
            node=pex(self.node_address),
            contract=pex(self.address),
        )

        if isinstance(unlock_proof.lock_encoded, messages.Lock):
            raise ValueError(
                'unlock must be called with a lock encoded `.as_bytes`')

        merkleproof_encoded = b''.join(unlock_proof.merkle_proof)

        transaction_hash = self.proxy.transact(
            'unlock',
            unlock_proof.lock_encoded,
            merkleproof_encoded,
            unlock_proof.secret,
        )

        self.client.poll(unhexlify(transaction_hash),
                         timeout=self.poll_timeout)
        receipt_or_none = check_transaction_threw(self.client,
                                                  transaction_hash)

        if receipt_or_none:
            log.critical(
                'unlock failed',
                node=pex(self.node_address),
                contract=pex(self.address),
                lock=unlock_proof,
            )
            self._check_exists()
            raise TransactionThrew('unlock', receipt_or_none)

        log.info(
            'unlock successful',
            node=pex(self.node_address),
            contract=pex(self.address),
            lock=unlock_proof,
        )

    def settle(self):
        """ Settle the channel.

        Raises:
            ChannelBusyError: If the channel is busy with another operation
        """
        log.info(
            'settle called',
            node=pex(self.node_address),
        )

        if not self.channel_operations_lock.acquire(blocking=False):
            raise ChannelBusyError(
                f'Channel with address {self.address} is '
                f'busy with another ongoing operation', )

        with releasing(self.channel_operations_lock):
            transaction_hash = self.proxy.transact('settle', )

            self.client.poll(unhexlify(transaction_hash),
                             timeout=self.poll_timeout)
            receipt_or_none = check_transaction_threw(self.client,
                                                      transaction_hash)
            if receipt_or_none:
                log.info(
                    'settle failed',
                    node=pex(self.node_address),
                    contract=pex(self.address),
                )
                self._check_exists()
                raise TransactionThrew('Settle', receipt_or_none)

            log.info(
                'settle successful',
                node=pex(self.node_address),
                contract=pex(self.address),
            )

    def all_events_filter(
        self,
        from_block: typing.BlockSpecification = None,
        to_block: typing.BlockSpecification = None,
    ) -> Filter:
        """ Install a new filter for all the events emitted by the current netting channel contract

        Return:
            Filter: The filter instance.
        """
        return self.client.new_filter(
            contract_address=self.proxy.contract_address,
            topics=None,
            from_block=from_block,
            to_block=to_block,
        )
Пример #4
0
class Registry:
    def __init__(
            self,
            jsonrpc_client,
            registry_address,
            poll_timeout=DEFAULT_POLL_TIMEOUT,
    ):
        # pylint: disable=too-many-arguments
        contract = jsonrpc_client.new_contract(
            CONTRACT_MANAGER.get_contract_abi(CONTRACT_REGISTRY),
            address_encoder(registry_address),
        )
        self.proxy = ContractProxy(jsonrpc_client, contract)

        if not is_binary_address(registry_address):
            raise ValueError('registry_address must be a valid address')

        check_address_has_code(jsonrpc_client, registry_address, 'Registry')

        CONTRACT_MANAGER.check_contract_version(
            self.proxy.contract.functions.contract_version().call(),
            CONTRACT_REGISTRY
        )

        self.address = registry_address
        self.client = jsonrpc_client
        self.poll_timeout = poll_timeout
        self.node_address = privatekey_to_address(self.client.privkey)

        self.address_to_channelmanager = dict()
        self.token_to_channelmanager = dict()

    def manager_address_by_token(self, token_address):
        """ Return the channel manager address for the given token or None if
        there is no correspoding address.
        """
        address = self.proxy.contract.functions.channelManagerByToken(
            to_checksum_address(token_address),
        ).call()

        if address == b'':
            check_address_has_code(self.client, self.address)
            return None

        return address_decoder(address)

    def add_token(self, token_address):
        if not is_binary_address(token_address):
            raise ValueError('token_address must be a valid address')

        log.info(
            'add_token called',
            node=pex(self.node_address),
            token_address=pex(token_address),
            registry_address=pex(self.address),
        )

        transaction_hash = self.proxy.transact(
            'addToken',
            self.address,
            token_address,
        )

        self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout)
        receipt_or_none = check_transaction_threw(self.client, transaction_hash)
        if receipt_or_none:
            log.info(
                'add_token failed',
                node=pex(self.node_address),
                token_address=pex(token_address),
                registry_address=pex(self.address),
            )
            raise TransactionThrew('AddToken', receipt_or_none)

        manager_address = self.manager_address_by_token(token_address)

        if manager_address is None:
            log.info(
                'add_token failed and check_transaction_threw didnt detect it',
                node=pex(self.node_address),
                token_address=pex(token_address),
                registry_address=pex(self.address),
            )

            raise RuntimeError('channelManagerByToken failed')

        log.info(
            'add_token sucessful',
            node=pex(self.node_address),
            token_address=pex(token_address),
            registry_address=pex(self.address),
            manager_address=pex(manager_address),
        )

        return manager_address

    def token_addresses(self):
        addresses = self.proxy.contract.functions.tokenAddresses().call()
        return [
            address_decoder(address)
            for address in addresses
        ]

    def manager_addresses(self):
        addresses = self.proxy.contract.functions.channelManagerAddresses().call()
        return [
            address_decoder(address)
            for address in addresses
        ]

    def tokenadded_filter(self, from_block=None, to_block=None) -> Filter:
        topics = [CONTRACT_MANAGER.get_event_id(EVENT_TOKEN_ADDED)]

        registry_address_bin = self.proxy.contract_address
        return self.client.new_filter(
            registry_address_bin,
            topics=topics,
            from_block=from_block,
            to_block=to_block,
        )

    def manager(self, manager_address):
        """ Return a proxy to interact with a ChannelManagerContract. """
        if not is_binary_address(manager_address):
            raise ValueError('manager_address must be a valid address')

        if manager_address not in self.address_to_channelmanager:
            manager = ChannelManager(
                self.client,
                manager_address,
                self.poll_timeout,
            )

            token_address = manager.token_address()

            self.token_to_channelmanager[token_address] = manager
            self.address_to_channelmanager[manager_address] = manager

        return self.address_to_channelmanager[manager_address]

    def manager_by_token(self, token_address):
        """ Find the channel manager for `token_address` and return a proxy to
        interact with it.

        If the token is not already registered it raises `EthNodeCommunicationError`,
        since we try to instantiate a Channel manager with an empty address.
        """
        if not is_binary_address(token_address):
            raise ValueError('token_address must be a valid address')

        if token_address not in self.token_to_channelmanager:
            check_address_has_code(self.client, token_address)  # check that the token exists
            manager_address = self.manager_address_by_token(token_address)

            if manager_address is None:
                raise NoTokenManager(
                    'Manager for token 0x{} does not exist'.format(hexlify(token_address))
                )

            manager = ChannelManager(
                self.client,
                manager_address,
                self.poll_timeout,
            )

            self.token_to_channelmanager[token_address] = manager
            self.address_to_channelmanager[manager_address] = manager

        return self.token_to_channelmanager[token_address]
Пример #5
0
class Discovery:
    """On chain smart contract raiden node discovery: allows registering
    endpoints (host, port) for your ethereum-/raiden-address and looking up
    endpoints for other ethereum-/raiden-addressess.
    """

    def __init__(
            self,
            jsonrpc_client,
            discovery_address,
            poll_timeout=DEFAULT_POLL_TIMEOUT,
    ):
        contract = jsonrpc_client.new_contract(
            CONTRACT_MANAGER.get_contract_abi(CONTRACT_ENDPOINT_REGISTRY),
            to_normalized_address(discovery_address),
        )
        self.proxy = ContractProxy(jsonrpc_client, contract)

        if not is_binary_address(discovery_address):
            raise ValueError('discovery_address must be a valid address')

        check_address_has_code(jsonrpc_client, discovery_address, 'Discovery')

        CONTRACT_MANAGER.check_contract_version(
            self.version(),
            CONTRACT_ENDPOINT_REGISTRY,
        )

        self.address = discovery_address
        self.client = jsonrpc_client
        self.poll_timeout = poll_timeout
        self.not_found_address = NULL_ADDRESS

    def register_endpoint(self, node_address, endpoint):
        if node_address != self.client.sender:
            raise ValueError("node_address doesnt match this node's address")

        transaction_hash = self.proxy.transact(
            'registerEndpoint',
            endpoint,
        )

        self.client.poll(
            unhexlify(transaction_hash),
            timeout=self.poll_timeout,
        )

        receipt_or_none = check_transaction_threw(self.client, transaction_hash)
        if receipt_or_none:
            raise TransactionThrew('Register Endpoint', receipt_or_none)

    def endpoint_by_address(self, node_address_bin):
        node_address_hex = to_checksum_address(node_address_bin)
        endpoint = self.proxy.contract.functions.findEndpointByAddress(
            node_address_hex,
        ).call()

        if endpoint == '':
            raise UnknownAddress('Unknown address {}'.format(pex(node_address_bin)))

        return endpoint

    def address_by_endpoint(self, endpoint):
        address = self.proxy.contract.functions.findAddressByEndpoint(endpoint).call()

        if address == self.not_found_address:  # the 0 address means nothing found
            return None

        return to_canonical_address(address)

    def version(self):
        return self.proxy.contract.functions.contract_version().call()
Пример #6
0
class Token:
    def __init__(
        self,
        jsonrpc_client,
        token_address,
    ):
        contract = jsonrpc_client.new_contract(
            CONTRACT_MANAGER.get_contract_abi(CONTRACT_HUMAN_STANDARD_TOKEN),
            to_normalized_address(token_address),
        )
        self.proxy = ContractProxy(jsonrpc_client, contract)

        if not is_binary_address(token_address):
            raise ValueError('token_address must be a valid address')

        check_address_has_code(jsonrpc_client, token_address, 'Token')

        self.address = token_address
        self.client = jsonrpc_client

    def approve(self, contract_address, allowance):
        """ Aprove `contract_address` to transfer up to `deposit` amount of token. """
        # TODO: check that `contract_address` is a netting channel and that
        # `self.address` is one of the participants (maybe add this logic into
        # `NettingChannel` and keep this straight forward)
        transaction_hash = self.proxy.transact(
            'approve',
            contract_address,
            allowance,
        )

        self.client.poll(unhexlify(transaction_hash))
        receipt_or_none = check_transaction_threw(self.client,
                                                  transaction_hash)

        if receipt_or_none:
            user_balance = self.balance_of(self.client.sender)

            # If the balance is zero, either the smart contract doesnt have a
            # balanceOf function or the actual balance is zero
            if user_balance == 0:
                msg = (
                    "Approve failed. \n"
                    "Your account balance is 0 (zero), either the smart "
                    "contract is not a valid ERC20 token or you don't have funds "
                    "to use for openning a channel. ")
                raise TransactionThrew(msg, receipt_or_none)

            # The approve call failed, check the user has enough balance
            # (assuming the token smart contract may check for the maximum
            # allowance, which is not necessarily the case)
            elif user_balance < allowance:
                msg = (
                    'Approve failed. \n'
                    'Your account balance is {}, nevertheless the call to '
                    'approve failed. Please make sure the corresponding smart '
                    'contract is a valid ERC20 token.').format(user_balance)
                raise TransactionThrew(msg, receipt_or_none)

            # If the user has enough balance, warn the user the smart contract
            # may not have the approve function.
            else:
                msg = (
                    'Approve failed. \n'
                    'Your account balance is {}, the request allowance is {}. '
                    'The smart contract may be rejecting your request for the '
                    'lack of balance.').format(user_balance, allowance)
                raise TransactionThrew(msg, receipt_or_none)

    def balance_of(self, address):
        """ Return the balance of `address`. """
        return self.proxy.contract.functions.balanceOf(
            to_checksum_address(address), ).call()

    def transfer(self, to_address, amount):
        transaction_hash = self.proxy.transact(
            'transfer',
            to_checksum_address(to_address),
            amount,
        )

        self.client.poll(unhexlify(transaction_hash))
        receipt_or_none = check_transaction_threw(self.client,
                                                  transaction_hash)
        if receipt_or_none:
            raise TransactionThrew('Transfer', receipt_or_none)
Пример #7
0
class ChannelManager:
    def __init__(
        self,
        jsonrpc_client,
        manager_address,
        poll_timeout=DEFAULT_POLL_TIMEOUT,
    ):
        # pylint: disable=too-many-arguments
        contract = jsonrpc_client.new_contract(
            CONTRACT_MANAGER.get_contract_abi(CONTRACT_CHANNEL_MANAGER),
            to_normalized_address(manager_address),
        )

        self.proxy = ContractProxy(jsonrpc_client, contract)

        if not is_binary_address(manager_address):
            raise ValueError('manager_address must be a valid address')

        check_address_has_code(jsonrpc_client, manager_address,
                               'Channel Manager')

        CONTRACT_MANAGER.check_contract_version(
            self.version(),
            CONTRACT_CHANNEL_MANAGER,
        )

        self.address = manager_address
        self.client = jsonrpc_client
        self.poll_timeout = poll_timeout
        self.open_channel_transactions = dict()

    def token_address(self) -> Address:
        """ Return the token of this manager. """
        token_address = self.proxy.contract.functions.tokenAddress().call()
        return to_canonical_address(token_address)

    def new_netting_channel(self, other_peer: Address,
                            settle_timeout: int) -> Address:
        """ Creates and deploys a new netting channel contract.

        Args:
            other_peer: The peer to open the channel with.
            settle_timeout: The settle timout to use for this channel.

        Returns:
            The address of the new netting channel.
        """
        if not is_binary_address(other_peer):
            raise ValueError('The other_peer must be a valid address')

        invalid_timeout = (settle_timeout < NETTINGCHANNEL_SETTLE_TIMEOUT_MIN
                           or
                           settle_timeout > NETTINGCHANNEL_SETTLE_TIMEOUT_MAX)
        if invalid_timeout:
            raise InvalidSettleTimeout(
                'settle_timeout must be in range [{}, {}]'.format(
                    NETTINGCHANNEL_SETTLE_TIMEOUT_MIN,
                    NETTINGCHANNEL_SETTLE_TIMEOUT_MAX,
                ))

        local_address = privatekey_to_address(self.client.privkey)
        if local_address == other_peer:
            raise SamePeerAddress(
                'The other peer must not have the same address as the client.')

        # Prevent concurrent attempts to open a channel with the same token and
        # partner address.
        if other_peer not in self.open_channel_transactions:
            new_open_channel_transaction = AsyncResult()
            self.open_channel_transactions[
                other_peer] = new_open_channel_transaction

            try:
                transaction_hash = self._new_netting_channel(
                    other_peer, settle_timeout)
            except Exception as e:
                new_open_channel_transaction.set_exception(e)
                raise
            else:
                new_open_channel_transaction.set(transaction_hash)
            finally:
                self.open_channel_transactions.pop(other_peer, None)
        else:
            # All other concurrent threads should block on the result of opening this channel
            self.open_channel_transactions[other_peer].get()

        netting_channel_results_encoded = self.proxy.contract.functions.getChannelWith(
            to_checksum_address(other_peer), ).call(
                {'from': to_checksum_address(self.client.sender)})

        # address is at index 0
        netting_channel_address_encoded = netting_channel_results_encoded

        if not netting_channel_address_encoded:
            log.error(
                'netting_channel_address failed',
                peer1=pex(local_address),
                peer2=pex(other_peer),
            )
            raise RuntimeError('netting_channel_address failed')

        netting_channel_address_bin = to_canonical_address(
            netting_channel_address_encoded)

        log.info(
            'new_netting_channel called',
            peer1=pex(local_address),
            peer2=pex(other_peer),
            netting_channel=pex(netting_channel_address_bin),
        )

        return netting_channel_address_bin

    def _new_netting_channel(self, other_peer, settle_timeout):
        if self.channel_exists(other_peer):
            raise DuplicatedChannelError(
                'Channel with given partner address already exists')

        transaction_hash = self.proxy.transact(
            'newChannel',
            other_peer,
            settle_timeout,
        )

        if not transaction_hash:
            raise RuntimeError('open channel transaction failed')

        self.client.poll(unhexlify(transaction_hash),
                         timeout=self.poll_timeout)

        if check_transaction_threw(self.client, transaction_hash):
            raise DuplicatedChannelError('Duplicated channel')

        return transaction_hash

    def channels_addresses(self) -> List[Tuple[Address, Address]]:
        # for simplicity the smart contract return a shallow list where every
        # second item forms a tuple
        channel_flat_encoded = self.proxy.contract.functions.getChannelsParticipants(
        ).call()

        channel_flat = [
            to_canonical_address(channel) for channel in channel_flat_encoded
        ]

        # [a,b,c,d] -> [(a,b),(c,d)]
        channel_iter = iter(channel_flat)
        return list(zip(channel_iter, channel_iter))

    def channels_by_participant(self,
                                participant_address: Address) -> List[Address]:
        """ Return a list of channel address that `participant_address` is a participant. """
        address_list = self.proxy.contract.functions.nettingContractsByAddress(
            to_checksum_address(participant_address), ).call(
                {'from': to_checksum_address(self.client.sender)})

        return [to_canonical_address(address) for address in address_list]

    def channel_exists(self, participant_address: Address) -> bool:
        existing_channel = self.proxy.contract.functions.getChannelWith(
            to_checksum_address(participant_address), ).call(
                {'from': to_checksum_address(self.client.sender)})

        exists = False

        if existing_channel != NULL_ADDRESS:
            exists = self.proxy.contract.functions.contractExists(
                to_checksum_address(existing_channel), ).call(
                    {'from': to_checksum_address(self.client.sender)})

        return exists

    def channelnew_filter(
        self,
        from_block: BlockSpecification = 0,
        to_block: BlockSpecification = 'latest',
    ) -> Filter:
        """ Install a new filter for ChannelNew events.

        Args:
            from_block: Create filter starting from this block number (default: 0).
            to_block: Create filter stopping at this block number (default: 'latest').

        Return:
            The filter instance.
        """
        topics = [CONTRACT_MANAGER.get_event_id(EVENT_CHANNEL_NEW)]

        channel_manager_address_bin = self.proxy.contract_address
        return self.client.new_filter(
            channel_manager_address_bin,
            topics=topics,
            from_block=from_block,
            to_block=to_block,
        )

    def version(self):
        return self.proxy.contract.functions.contract_version().call()