Пример #1
0
    def set_url(self, url: str) -> None:
        """Sets the url needed to access the service via HTTP for the caller"""
        log_details: Dict[str, Any] = {
            "node": to_checksum_address(self.node_address),
            "contract": to_checksum_address(self.address),
            "url": url,
        }

        if not url.strip():
            msg = "Invalid empty URL"
            raise BrokenPreconditionError(msg)

        parsed_url = urlparse(url)
        if parsed_url.scheme not in ("http", "https"):
            msg = "URL provided to service registry must be a valid HTTP(S) endpoint."
            raise BrokenPreconditionError(msg)

        with log_transaction(log, "set_url", log_details):
            gas_limit = self.proxy.estimate_gas("latest", "setURL", url)
            if not gas_limit:
                msg = f"URL {url} is invalid"
                raise RaidenUnrecoverableError(msg)

            log_details["gas_limit"] = gas_limit
            transaction_hash = self.proxy.transact("setURL", gas_limit, url)
            receipt = self.client.poll(transaction_hash)
            failed_receipt = check_transaction_threw(receipt=receipt)
            if failed_receipt:
                msg = f"URL {url} is invalid"
                raise RaidenUnrecoverableError(msg)
Пример #2
0
    def init(
        self,
        monitoring_service_address: MonitoringServiceAddress,
        one_to_n_address: OneToNAddress,
        given_block_identifier: BlockSpecification,
    ) -> None:
        """ Initialize the UserDeposit contract with MS and OneToN addresses """
        log_details = {
            "monitoring_service_address":
            to_checksum_address(monitoring_service_address),
            "one_to_n_address":
            to_checksum_address(one_to_n_address),
        }

        check_address_has_code(
            client=self.client,
            address=Address(monitoring_service_address),
            contract_name=CONTRACT_MONITORING_SERVICE,
            expected_code=decode_hex(
                self.contract_manager.get_runtime_hexcode(
                    CONTRACT_MONITORING_SERVICE)),
        )
        check_address_has_code(
            client=self.client,
            address=Address(one_to_n_address),
            contract_name=CONTRACT_ONE_TO_N,
            expected_code=decode_hex(
                self.contract_manager.get_runtime_hexcode(CONTRACT_ONE_TO_N)),
        )
        try:
            existing_monitoring_service_address = self.monitoring_service_address(
                block_identifier=given_block_identifier)
            existing_one_to_n_address = self.one_to_n_address(
                block_identifier=given_block_identifier)
        except ValueError:
            pass
        except BadFunctionCallOutput:
            raise_on_call_returned_empty(given_block_identifier)
        else:
            if existing_monitoring_service_address != EMPTY_ADDRESS:
                msg = (
                    f"MonitoringService contract address is already set to "
                    f"{to_checksum_address(existing_monitoring_service_address)}"
                )
                raise BrokenPreconditionError(msg)

            if existing_one_to_n_address != EMPTY_ADDRESS:
                msg = (f"OneToN contract address is already set to "
                       f"{to_checksum_address(existing_one_to_n_address)}")
                raise BrokenPreconditionError(msg)

        with log_transaction(log, "init", log_details):
            self._init(
                monitoring_service_address=monitoring_service_address,
                one_to_n_address=one_to_n_address,
                log_details=log_details,
            )
Пример #3
0
    def deposit(
        self,
        beneficiary: Address,
        total_deposit: TokenAmount,
        given_block_identifier: BlockSpecification,
    ) -> None:
        """ Deposit provided amount into the user-deposit contract
        to the beneficiary's account. """

        token_address = self.token_address(given_block_identifier)
        token = self.proxy_manager.token(token_address=token_address)

        log_details = {
            "beneficiary": to_checksum_address(beneficiary),
            "contract": to_checksum_address(self.address),
            "node": to_checksum_address(self.node_address),
            "total_deposit": total_deposit,
        }

        # To prevent concurrent transactions for token transfers where it is unknown if
        # we have enough capacity for both, we acquire the lock
        # for the token proxy. Example: A user deposit and a channel deposit
        # for the same token.
        with self.deposit_lock, token.token_lock:
            # check preconditions
            try:
                previous_total_deposit = self.get_total_deposit(
                    address=beneficiary,
                    block_identifier=given_block_identifier)
                current_balance = token.balance_of(
                    address=self.node_address,
                    block_identifier=given_block_identifier)
                whole_balance = self.whole_balance(
                    block_identifier=given_block_identifier)
                whole_balance_limit = self.whole_balance_limit(
                    block_identifier=given_block_identifier)
            except ValueError:
                # If 'given_block_identifier' has been pruned, we cannot perform the
                # precondition checks but must still set the amount_to_deposit to a
                # reasonable value.
                previous_total_deposit = self.get_total_deposit(
                    address=beneficiary,
                    block_identifier=self.client.get_checking_block())
                amount_to_deposit = TokenAmount(total_deposit -
                                                previous_total_deposit)
            except BadFunctionCallOutput:
                raise_on_call_returned_empty(given_block_identifier)
            else:
                log_details["previous_total_deposit"] = previous_total_deposit
                amount_to_deposit = TokenAmount(total_deposit -
                                                previous_total_deposit)

                if whole_balance + amount_to_deposit > UINT256_MAX:
                    msg = (
                        f"Current whole balance is {whole_balance}. "
                        f"The new deposit of {amount_to_deposit} would lead to an overflow."
                    )
                    raise BrokenPreconditionError(msg)

                if whole_balance + amount_to_deposit > whole_balance_limit:
                    msg = (
                        f"Current whole balance is {whole_balance}. "
                        f"With the new deposit of {amount_to_deposit}, the deposit "
                        f"limit of {whole_balance_limit} would be exceeded.")
                    raise BrokenPreconditionError(msg)

                if total_deposit <= previous_total_deposit:
                    msg = (
                        f"Current total deposit {previous_total_deposit} is already larger "
                        f"than the requested total deposit amount {total_deposit}"
                    )
                    raise BrokenPreconditionError(msg)

                if current_balance < amount_to_deposit:
                    msg = (
                        f"new_total_deposit - previous_total_deposit = {amount_to_deposit} "
                        f"can not be larger than the available balance {current_balance}, "
                        f"for token at address {to_checksum_address(token.address)}"
                    )
                    raise BrokenPreconditionError(msg)

            with log_transaction(log, "deposit", log_details):
                self._deposit(
                    beneficiary=beneficiary,
                    token=token,
                    total_deposit=total_deposit,
                    amount_to_deposit=amount_to_deposit,
                    log_details=log_details,
                )
Пример #4
0
    def register_secret_batch(self, secrets: List[Secret]) -> None:
        """Register a batch of secrets. Check if they are already registered at
        the given block identifier."""
        secrets_to_register = list()
        secrethashes_to_register = list()
        secrethashes_not_sent = list()
        transaction_result = AsyncResult()
        wait_for = set()

        # secret registration has no preconditions:
        #
        # - The action does not depend on any state, it's always valid to call
        #   it.
        # - This action is always susceptible to race conditions.
        #
        # Therefore this proxy only needs to detect if the secret is already
        # registered, to avoid sending obviously unecessary transactions, and
        # it has to handle race conditions.

        with self._open_secret_transactions_lock:
            verification_block_hash = self.client.get_confirmed_blockhash()

            for secret in secrets:
                secrethash = sha256_secrethash(secret)
                secrethash_hex = encode_hex(secrethash)

                # Do the local test on `open_secret_transactions` first, then
                # if necessary do an RPC call.
                #
                # The call to `is_secret_registered` has two conflicting
                # requirements:
                #
                # - Avoid sending duplicated transactions for the same lock
                # - Operating on a consistent/confirmed view of the blockchain
                #   (if a secret has been registered in a block that is not
                #   confirmed it doesn't count yet, an optimization would be to
                #   *not* send the transaction and wait for the confirmation)
                #
                # The code below respects the consistent blockchain view,
                # meaning that if this proxy method is called with an old
                # blockhash an unecessary transaction will be sent, and the
                # error will be treated as a race-condition.
                other_result = self.open_secret_transactions.get(secret)

                if other_result is not None:
                    wait_for.add(other_result)
                    secrethashes_not_sent.append(secrethash_hex)
                elif not self.is_secret_registered(secrethash,
                                                   verification_block_hash):
                    secrets_to_register.append(secret)
                    secrethashes_to_register.append(secrethash_hex)
                    self.open_secret_transactions[secret] = transaction_result

        # From here on the lock is not required. Context-switches will happen
        # for the gas estimation and the transaction, however the
        # synchronization data is limited to the open_secret_transactions
        log_details = {
            "node": to_checksum_address(self.node_address),
            "contract": to_checksum_address(self.address),
            "secrethashes": secrethashes_to_register,
            "secrethashes_not_sent": secrethashes_not_sent,
        }

        with log_transaction(log, "register_secret_batch", log_details):
            if secrets_to_register:
                self._register_secret_batch(secrets_to_register,
                                            transaction_result, log_details)

            gevent.joinall(wait_for, raise_error=True)
Пример #5
0
    def add_token(
        self,
        token_address: TokenAddress,
        channel_participant_deposit_limit: TokenAmount,
        token_network_deposit_limit: TokenAmount,
        block_identifier: BlockSpecification,
    ) -> TokenNetworkAddress:
        """
        Register token of `token_address` with the token network.
        The limits apply for version 0.13.0 and above of raiden-contracts,
        since instantiation also takes the limits as constructor arguments.
        """
        if block_identifier == "latest":
            raise ValueError(
                'Calling a proxy with "latest" is usually wrong because '
                "the result of the precondition check is not precisely predictable."
            )

        if token_address == NULL_ADDRESS_BYTES:
            raise InvalidTokenAddress(
                "The call to register a token at 0x00..00 will fail.")

        if token_network_deposit_limit <= 0:
            raise InvalidTokenNetworkDepositLimit(
                f"Token network deposit limit of {token_network_deposit_limit} is invalid"
            )

        if channel_participant_deposit_limit > token_network_deposit_limit:
            raise InvalidChannelParticipantDepositLimit(
                f"Channel participant deposit limit of "
                f"{channel_participant_deposit_limit} is invalid")

        token_proxy = self.proxy_manager.token(token_address)
        try:
            token_supply = token_proxy.total_supply(
                block_identifier=block_identifier)
            already_registered = self.get_token_network(
                token_address=token_address, block_identifier=block_identifier)
            deprecation_executor = self.get_deprecation_executor(
                block_identifier=block_identifier)
            settlement_timeout_min = self.settlement_timeout_min(
                block_identifier=block_identifier)
            settlement_timeout_max = self.settlement_timeout_max(
                block_identifier=block_identifier)
            chain_id = self.get_chain_id(block_identifier=block_identifier)
            secret_registry_address = self.get_secret_registry_address(
                block_identifier=block_identifier)
            max_token_networks = self.get_max_token_networks(
                block_identifier=block_identifier)
            token_networks_created = self.get_token_network_created(
                block_identifier=block_identifier)
        except ValueError:
            # If `block_identifier` has been pruned the checks cannot be performed
            pass
        except BadFunctionCallOutput:
            raise_on_call_returned_empty(block_identifier)
        else:
            if token_networks_created >= max_token_networks:
                raise BrokenPreconditionError(
                    f"Number of token networks will exceed the max of {max_token_networks}"
                )

            if token_supply == "":
                raise InvalidToken("Given token address does not follow the "
                                   "ERC20 standard (missing `totalSupply()`)")
            if already_registered:
                raise BrokenPreconditionError(
                    "The token is already registered in the TokenNetworkRegistry."
                )

            if deprecation_executor == NULL_ADDRESS_BYTES:
                raise BrokenPreconditionError(
                    "The deprecation executor property for the TokenNetworkRegistry is invalid."
                )

            if chain_id == 0:
                raise BrokenPreconditionError(
                    "The chain ID property for the TokenNetworkRegistry is invalid."
                )

            if secret_registry_address == NULL_ADDRESS_BYTES:
                raise BrokenPreconditionError(
                    "The secret registry address for the token network is invalid."
                )

            if settlement_timeout_min == 0:
                raise BrokenPreconditionError(
                    "The minimum settlement timeout for the token network "
                    "should be larger than zero.")

            if settlement_timeout_min == 0:
                raise BrokenPreconditionError(
                    "The minimum settlement timeout for the token network "
                    "should be larger than zero.")

            if settlement_timeout_max <= settlement_timeout_min:
                raise BrokenPreconditionError(
                    "The maximum settlement timeout for the token network "
                    "should be larger than the minimum settlement timeout.")

        log_details = {
            "node": to_checksum_address(self.node_address),
            "contract": to_checksum_address(self.address),
            "token_address": to_checksum_address(token_address),
        }
        with log_transaction(log, "add_token", log_details):
            return self._add_token(
                token_address=token_address,
                channel_participant_deposit_limit=
                channel_participant_deposit_limit,
                token_network_deposit_limit=token_network_deposit_limit,
                log_details=log_details,
            )
Пример #6
0
    def approve(self, allowed_address: Address,
                allowance: TokenAmount) -> None:
        """ Approve `allowed_address` to transfer up to `deposit` amount of token.

        Note:

            For channel deposit please use the channel proxy, since it does
            additional validations.
            We assume there to be sufficient balance as a precondition if this
            is called, so it is not checked as a precondition here.
        """
        # Note that given_block_identifier is not used here as there
        # are no preconditions to check before sending the transaction
        # There are no direct calls to this method in any event handler,
        # so a precondition check would make no sense.
        with self.token_lock:
            log_details = {
                "node": to_checksum_address(self.node_address),
                "contract": to_checksum_address(self.address),
                "allowed_address": to_checksum_address(allowed_address),
                "allowance": allowance,
            }

            with log_transaction(log, "approve", log_details):
                checking_block = self.client.get_checking_block()
                error_prefix = "Call to approve will fail"
                gas_limit = self.proxy.estimate_gas(
                    checking_block, "approve",
                    to_checksum_address(allowed_address), allowance)

                if gas_limit:
                    error_prefix = "Call to approve failed"
                    gas_limit = safe_gas_limit(gas_limit)
                    log_details["gas_limit"] = gas_limit
                    transaction_hash = self.proxy.transact(
                        "approve", gas_limit,
                        to_checksum_address(allowed_address), allowance)

                    receipt = self.client.poll(transaction_hash)
                    failed_receipt = check_transaction_threw(receipt=receipt)

                    if failed_receipt:
                        failed_at_blockhash = encode_hex(
                            failed_receipt["blockHash"])

                        if failed_receipt["cumulativeGasUsed"] == gas_limit:
                            msg = (
                                f"approve failed and all gas was used ({gas_limit}). "
                                f"Estimate gas may have underestimated approve, or "
                                f"succeeded even though an assert is triggered, or "
                                f"the smart contract code has a conditional assert."
                            )
                            raise RaidenRecoverableError(msg)

                        balance = self.balance_of(self.client.address,
                                                  failed_at_blockhash)
                        if balance < allowance:
                            msg = (
                                f"{error_prefix} Your balance of {balance} is "
                                "below the required amount of {allowance}.")
                            if balance == 0:
                                msg += (
                                    " Note: The balance was 0, which may also happen "
                                    "if the contract is not a valid ERC20 token "
                                    "(balanceOf method missing).")
                            raise RaidenRecoverableError(msg)

                        raise RaidenRecoverableError(
                            f"{error_prefix}. The reason is unknown, you have enough tokens for "
                            f"the requested allowance and enough eth to pay the gas. There may "
                            f"be a problem with the token contract.")

                else:
                    failed_at = self.proxy.rpc_client.get_block("latest")
                    failed_at_blockhash = encode_hex(failed_at["hash"])
                    failed_at_blocknumber = failed_at["number"]

                    self.proxy.rpc_client.check_for_insufficient_eth(
                        transaction_name="approve",
                        transaction_executed=False,
                        required_gas=GAS_REQUIRED_FOR_APPROVE,
                        block_identifier=failed_at_blocknumber,
                    )

                    balance = self.balance_of(self.client.address,
                                              failed_at_blockhash)
                    if balance < allowance:
                        msg = (f"{error_prefix} Your balance of {balance} is "
                               "below the required amount of {allowance}.")
                        if balance == 0:
                            msg += (
                                " Note: The balance was 0, which may also happen if the contract "
                                "is not a valid ERC20 token (balanceOf method missing)."
                            )
                        raise RaidenRecoverableError(msg)

                    raise RaidenRecoverableError(
                        f"{error_prefix} Gas estimation failed for unknown reason. "
                        f"Please make sure the contract is a valid ERC20 token."
                    )
Пример #7
0
    def transfer(self, to_address: Address, amount: TokenAmount) -> None:
        """ Transfer `amount` tokens to `to_address`.

        Note:

            We assume there to be sufficient balance as a precondition if
            this is called, so that is not checked as a precondition here.
        """
        # Note that given_block_identifier is not used here as there
        # are no preconditions to check before sending the transaction
        # There are no direct calls to this method in any event handler,
        # so a precondition check would make no sense.
        with self.token_lock:
            log_details = {
                "node": to_checksum_address(self.node_address),
                "contract": to_checksum_address(self.address),
                "to_address": to_checksum_address(to_address),
                "amount": amount,
            }

            with log_transaction(log, "transfer", log_details):
                checking_block = self.client.get_checking_block()
                gas_limit = self.proxy.estimate_gas(
                    checking_block, "transfer",
                    to_checksum_address(to_address), amount)
                failed_receipt = None

                if gas_limit is not None:
                    gas_limit = safe_gas_limit(gas_limit)
                    log_details["gas_limit"] = gas_limit

                    transaction_hash = self.proxy.transact(
                        "transfer", gas_limit, to_checksum_address(to_address),
                        amount)

                    receipt = self.client.poll(transaction_hash)
                    # TODO: check Transfer event (issue: #2598)
                    failed_receipt = check_transaction_threw(receipt=receipt)

                if gas_limit is None or failed_receipt is not None:
                    if failed_receipt:
                        failed_at_number = failed_receipt["blockNumber"]
                    else:
                        failed_at_number = checking_block
                    failed_at_hash = encode_hex(
                        self.client.blockhash_from_blocknumber(
                            failed_at_number))

                    self.proxy.rpc_client.check_for_insufficient_eth(
                        transaction_name="transfer",
                        transaction_executed=False,
                        required_gas=GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL,
                        block_identifier=failed_at_number,
                    )

                    balance = self.balance_of(self.client.address,
                                              failed_at_hash)
                    if balance < amount:
                        msg = (
                            f"Call to transfer will fail. Your balance of {balance} is "
                            f"below the required amount of {amount}.")
                        if balance == 0:
                            msg += (
                                " Note: The balance was 0, which may also happen if the contract "
                                "is not a valid ERC20 token (balanceOf method missing)."
                            )
                        raise RaidenRecoverableError(msg)

                    if gas_limit is None:
                        raise RaidenRecoverableError(
                            "Call to transfer will fail. Gas estimation failed for unknown "
                            "reason. Please make sure the contract is a valid ERC20 token."
                        )
                    else:
                        raise RaidenRecoverableError(
                            "Call to transfer failed for unknown reason. Please make sure the "
                            "contract is a valid ERC20 token.")