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
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
def checksum_address(address: int) -> ChecksumAddress: return ChecksumAddress( to_checksum_address( address.to_bytes(ETH_ADDRESS_BYTE_LENGTH, 'big')))
# 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"]
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, )