Пример #1
0
 def owner(self) -> ChecksumAddress:
     if self._upgradeable:
         # Get the address of the proxy
         contract = self.get_proxy_deployer()
     else:
         # Get the address of the implementation
         contract = self.blockchain.get_contract_by_name(
             contract_name=self.contract_name, registry=self.registry)
     owner_address = ChecksumAddress(
         contract.contract.functions.owner().call())  # blockchain read
     return owner_address
Пример #2
0
    def _sample(
        self,
        network_middleware: RestMiddleware,
        ursulas: Optional[Iterable['Ursula']] = None,
        timeout: int = 10,
    ) -> List['Ursula']:
        """Send concurrent requests to the /ping HTTP endpoint of nodes drawn from the reservoir."""

        ursulas = ursulas or []
        handpicked_addresses = [
            ChecksumAddress(ursula.checksum_address) for ursula in ursulas
        ]

        self.publisher.block_until_number_of_known_nodes_is(
            self.shares, learn_on_this_thread=True, eager=True)
        reservoir = self._make_reservoir(handpicked_addresses)
        value_factory = PrefetchStrategy(reservoir, self.shares)

        def worker(address) -> 'Ursula':
            return self._ping_node(address, network_middleware)

        worker_pool = WorkerPool(worker=worker,
                                 value_factory=value_factory,
                                 target_successes=self.shares,
                                 timeout=timeout,
                                 stagger_timeout=1,
                                 threadpool_size=self.shares)
        worker_pool.start()
        try:
            successes = worker_pool.block_until_target_successes()
        except (WorkerPool.OutOfValues, WorkerPool.TimedOut):
            # It's possible to raise some other exceptions here but we will use the logic below.
            successes = worker_pool.get_successes()
        finally:
            worker_pool.cancel()
            worker_pool.join()
        failures = worker_pool.get_failures()

        accepted_addresses = ", ".join(ursula.checksum_address
                                       for ursula in successes.values())
        if len(successes) < self.shares:
            rejections = "\n".join(
                f"{address}: {value}"
                for address, (type_, value, traceback) in failures.items())
            message = "Failed to contact enough sampled nodes.\n"\
                      f"Selected:\n{accepted_addresses}\n" \
                      f"Unavailable:\n{rejections}"
            self.log.debug(message)
            raise self.NotEnoughUrsulas(message)

        self.log.debug(f"Selected nodes for policy: {accepted_addresses}")
        ursulas = list(successes.values())
        return ursulas
Пример #3
0
 def checksum_address(address: int) -> ChecksumAddress:
     return ChecksumAddress(
         to_checksum_address(
             address.to_bytes(ETH_ADDRESS_BYTE_LENGTH, 'big')))
Пример #4
0
# gas price strategy
APPLY_GAS_PRICE_STRATEGY: bool = environ.get("APPLY_GAS_PRICE_STRATEGY",
                                             "False") in (
                                                 "true",
                                                 "True",
                                             )
MAX_TX_WAIT_SECONDS: int = int(environ.get("MAX_TX_WAIT_SECONDS", "120"))

# maximum gas spent on oracle vote
ORACLE_VOTE_GAS_LIMIT: Wei = Wei(int(environ["ORACLE_VOTE_GAS_LIMIT"]))

# how long to wait for transaction to mine
TRANSACTION_TIMEOUT: int = int(environ["TRANSACTION_TIMEOUT"])

# how long to wait before processing again
PROCESS_INTERVAL: int = int(environ["PROCESS_INTERVAL"])

# contracts
POOL_CONTRACT_ADDRESS: ChecksumAddress = ChecksumAddress(
    HexAddress(HexStr(environ["POOL_CONTRACT_ADDRESS"])))
ORACLES_CONTRACT_ADDRESS: ChecksumAddress = ChecksumAddress(
    HexAddress(HexStr(environ["ORACLES_CONTRACT_ADDRESS"])))
REWARD_ETH_CONTRACT_ADDRESS: ChecksumAddress = ChecksumAddress(
    HexAddress(HexStr(environ["REWARD_ETH_CONTRACT_ADDRESS"])))
STAKED_ETH_CONTRACT_ADDRESS: ChecksumAddress = ChecksumAddress(
    HexAddress(HexStr(environ["STAKED_ETH_CONTRACT_ADDRESS"])))

# credentials
# TODO: consider reading from file
ORACLE_PRIVATE_KEY: str = environ["ORACLE_PRIVATE_KEY"]
Пример #5
0
    def process(self) -> None:
        """Records new pool validators, submits off-chain data to `Oracles` contract."""
        sync_period = timedelta(seconds=get_oracles_sync_period(self.oracles))
        if sync_period != self.oracles_sync_period:
            # adjust next update time based on new period
            next_update_at = (
                self.next_update_at - self.oracles_sync_period + sync_period
            )
            self.oracles_sync_period = sync_period
            logger.info(
                f"Updated oracles update period: previous={self.oracles_sync_period}, new={sync_period}"
            )

            while next_update_at <= datetime.now(tz=timezone.utc):
                self.last_update_at = next_update_at
                next_update_at = self.last_update_at + self.oracles_sync_period

            self.next_update_at = self.last_update_at + self.oracles_sync_period
            logger.info(f"Scheduling oracles update at {self.next_update_at}")

        if self.next_update_at > datetime.now(tz=timezone.utc):
            # it's not the time to update yet
            return

        if check_oracles_paused(self.oracles):
            self.last_update_at = self.next_update_at
            self.next_update_at = self.last_update_at + self.oracles_sync_period
            logger.info(
                f"Skipping update as Oracles contract is paused:"
                f" next update at {self.next_update_at}"
            )
            return

        # fetch new pool validators
        public_keys: Set[BLSPubkey] = get_pool_validator_public_keys(self.pool)

        # calculate finalized epoch to fetch balance at
        epoch: int = (
            int(
                (self.next_update_at - self.genesis_time).total_seconds()
                / self.seconds_per_epoch
            )
            - 3
        )

        # fetch activated validators
        validator_statuses = get_pool_validator_statuses(
            stub=self.validator_stub, public_keys=public_keys
        )
        activated_public_keys: Set[BLSPubkey] = set()
        for i, public_key in enumerate(validator_statuses.public_keys):  # type: ignore
            status_response = validator_statuses.statuses[i]  # type: ignore
            status = ValidatorStatus(status_response.status)

            if (
                status in ACTIVATED_STATUSES
                and status_response.activation_epoch <= epoch
            ):
                activated_public_keys.add(public_key)

        if not activated_public_keys:
            self.last_update_at = self.next_update_at
            self.next_update_at = self.last_update_at + self.oracles_sync_period
            logger.info(
                f"No activated validators: next update at={str(self.next_update_at)}"
            )
            return

        activated_validators = len(activated_public_keys)
        logger.info(
            f"Retrieving balances for {activated_validators} / {len(public_keys)}"
            f" activated validators at epoch={epoch}"
        )
        activated_total_balance = get_validators_total_balance(
            stub=self.beacon_chain_stub, epoch=epoch, public_keys=activated_public_keys
        )

        # calculate new rewards
        total_rewards: Wei = Wei(
            activated_total_balance - (activated_validators * self.deposit_amount)
        )
        if total_rewards < 0:
            pretty_total_rewards = (
                f'-{self.w3.fromWei(abs(total_rewards), "ether")} ETH'
            )
        else:
            pretty_total_rewards = f'{self.w3.fromWei(total_rewards, "ether")} ETH'

        previous_total_rewards = get_reth_total_rewards(self.reward_eth_token)
        period_rewards: Wei = Wei(total_rewards - previous_total_rewards)
        if period_rewards < 0:
            pretty_period_rewards = (
                f'-{self.w3.fromWei(abs(period_rewards), "ether")} ETH'
            )
        else:
            pretty_period_rewards = f'{self.w3.fromWei(period_rewards, "ether")} ETH'
        logger.info(
            f"Retrieved pool validator rewards:"
            f" total={pretty_total_rewards}, period={pretty_period_rewards}"
        )

        # skip minting new rewards in case they are negative
        if period_rewards < 0:
            total_rewards = previous_total_rewards
            logger.info(
                f"Skipping updating total rewards: period rewards={pretty_period_rewards}"
            )

        if not check_oracle_has_vote(
            self.oracles,
            ChecksumAddress(self.w3.eth.default_account),  # type: ignore
            total_rewards,
            activated_validators,
        ):
            # submit vote
            logger.info(
                f"Submitting vote:"
                f" total rewards={pretty_total_rewards},"
                f" activated validators={activated_validators}"
            )
            submit_oracle_vote(
                self.oracles,
                total_rewards,
                activated_validators,
                TRANSACTION_TIMEOUT,
                ORACLE_VOTE_GAS_LIMIT,
            )
            logger.info("Vote has been successfully submitted")

        last_update_at = datetime.fromtimestamp(
            get_last_update_timestamp(self.reward_eth_token),
            tz=timezone.utc,
        )
        timeout = TRANSACTION_TIMEOUT  # wait for other voters
        while self.next_update_at > last_update_at:
            if timeout <= 0:
                raise RuntimeError("Timed out waiting for other oracles' votes")

            logger.info("Waiting for other oracles to vote...")
            time.sleep(1)
            last_update_at = datetime.fromtimestamp(
                get_last_update_timestamp(self.reward_eth_token),
                tz=timezone.utc,
            )
            timeout -= 1

        logger.info("Oracles have successfully voted for the same data")
        self.last_update_at = last_update_at

        self.next_update_at = self.last_update_at + self.oracles_sync_period
        logger.info(f"Re-scheduling rewards update: next at={self.next_update_at}")

        # check oracle balance
        if SEND_TELEGRAM_NOTIFICATIONS:
            check_default_account_balance(
                w3=self.w3,
                warning_amount=BALANCE_WARNING_THRESHOLD,
                error_amount=BALANCE_ERROR_THRESHOLD,
            )