async def get_periodic_allocations(network: str, from_block: BlockNumber, to_block: BlockNumber) -> TokenAllocations: """Fetches periodic allocations.""" last_id = "" result: Dict = await execute_sw_gql_query( network=network, query=PERIODIC_DISTRIBUTIONS_QUERY, variables=dict(from_block=from_block, to_block=to_block, last_id=last_id), ) distributions_chunk = result.get("periodicDistributions", []) distributions = distributions_chunk # accumulate chunks of distributions while len(distributions_chunk) >= 1000: last_id = distributions_chunk[-1]["id"] result: Dict = await execute_sw_gql_query( network=network, query=PERIODIC_DISTRIBUTIONS_QUERY, variables=dict(from_block=from_block, to_block=to_block, last_id=last_id), ) distributions_chunk = result.get("periodicDistributions", []) distributions.extend(distributions_chunk) allocations: TokenAllocations = {} for dist in distributions: dist_start_block: BlockNumber = BlockNumber(int( dist["startedAtBlock"])) dist_end_block: BlockNumber = BlockNumber(int(dist["endedAtBlock"])) if dist_end_block <= from_block or dist_start_block >= to_block: # distributions are out of current range continue allocation = TokenAllocation( from_block=dist_start_block, to_block=dist_end_block, reward_token=Web3.toChecksumAddress(dist["token"]), reward=int(dist["amount"]), ) allocations.setdefault(Web3.toChecksumAddress(dist["beneficiary"]), []).append(allocation) return allocations
async def get_partners_rewards(network: str, from_block: BlockNumber, to_block: BlockNumber, total_reward: Wei) -> Tuple[Rewards, Wei]: """Fetches partners rewards.""" result: Dict = await execute_sw_gql_query( network=network, query=PARTNERS_QUERY, variables=dict(block_number=to_block, ), ) partners = result["partners"] reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"] # process partners points: Dict[ChecksumAddress, int] = {} total_points = 0 total_contributed = 0 for partner in partners: account = Web3.toChecksumAddress(partner["id"]) if account == EMPTY_ADDR_HEX: continue contributed_amount = Wei(int(partner["contributedAmount"])) total_contributed += contributed_amount revenue_share = int(partner["revenueShare"]) prev_account_points = int(partner["distributorPoints"]) updated_at_block = BlockNumber(int(partner["updatedAtBlock"])) if from_block > updated_at_block: updated_at_block = from_block prev_account_points = 0 account_points = prev_account_points + (contributed_amount * revenue_share * (to_block - updated_at_block)) if account_points <= 0: continue points[account] = account_points total_points += account_points if total_contributed <= 0: return {}, total_reward partners_reward = Wei( (total_reward * total_points) // (total_contributed * 10000 * (to_block - from_block))) if partners_reward <= 0: return {}, total_reward partners_reward = min(total_reward, partners_reward) rewards = calculate_points_based_rewards( total_reward=partners_reward, points=points, total_points=total_points, reward_token=reward_token_address, ) return rewards, Wei(total_reward - partners_reward)
async def has_synced_block(network: str, block_number: BlockNumber) -> bool: result: Dict = await execute_ethereum_gql_query( network=network, query=VALIDATOR_REGISTRATIONS_SYNC_BLOCK_QUERY, variables={}, ) meta = result["_meta"] return block_number <= BlockNumber(int(meta["block"]["number"]))
def _get_avg_block_time(w3: Web3, sample_size: int) -> float: latest = w3.eth.getBlock('latest') constrained_sample_size = min(sample_size, latest['number']) if constrained_sample_size == 0: raise ValidationError('Constrained sample size is 0') oldest = w3.eth.getBlock(BlockNumber(latest['number'] - constrained_sample_size)) return (latest['timestamp'] - oldest['timestamp']) / constrained_sample_size
def _get_weighted_avg_block_time(w3: Web3, sample_size: int) -> float: latest_block_number = w3.eth.getBlock('latest')['number'] constrained_sample_size = min(sample_size, latest_block_number) if constrained_sample_size == 0: raise ValidationError('Constrained sample size is 0') oldest_block = w3.eth.getBlock(BlockNumber(latest_block_number - constrained_sample_size)) oldest_block_number = oldest_block['number'] prev_timestamp = oldest_block['timestamp'] weighted_sum = 0.0 sum_of_weights = 0.0 for i in range(oldest_block_number + 1, latest_block_number + 1): curr_timestamp = w3.eth.getBlock(BlockNumber(i))['timestamp'] time = curr_timestamp - prev_timestamp weight = (i - oldest_block_number) / constrained_sample_size weighted_sum += (time * weight) sum_of_weights += weight prev_timestamp = curr_timestamp return weighted_sum / sum_of_weights
async def get_latest_block(network: str) -> Block: """Gets the latest block number and its timestamp.""" result: Dict = await execute_ethereum_gql_query( network=network, query=LATEST_BLOCK_QUERY, variables=dict(confirmation_blocks=CONFIRMATION_BLOCKS, ), ) return Block( block_number=BlockNumber(int(result["blocks"][0]["id"])), timestamp=Timestamp(int(result["blocks"][0]["timestamp"])), )
async def get_voting_parameters(network: str, block_number: BlockNumber) -> VotingParameters: """Fetches rewards voting parameters.""" result: Dict = await execute_sw_gql_query( network=network, query=VOTING_PARAMETERS_QUERY, variables=dict(block_number=block_number, ), ) network = result["networks"][0] reward_token = result["rewardEthTokens"][0] try: distributor = result["merkleDistributors"][0] except IndexError: distributor = { "rewardsUpdatedAtBlock": 0, "updatedAtBlock": 0, "merkleRoot": None, "merkleProofs": None, } rewards = RewardsVotingParameters( rewards_nonce=int(network["oraclesRewardsNonce"]), total_rewards=Wei(int(reward_token["totalRewards"])), rewards_updated_at_timestamp=Timestamp( int(reward_token["updatedAtTimestamp"])), ) distributor = DistributorVotingParameters( rewards_nonce=int(network["oraclesRewardsNonce"]), from_block=BlockNumber(int(distributor["rewardsUpdatedAtBlock"])), to_block=BlockNumber(int(reward_token["updatedAtBlock"])), last_updated_at_block=BlockNumber(int(distributor["updatedAtBlock"])), last_merkle_root=distributor["merkleRoot"], last_merkle_proofs=distributor["merkleProofs"], protocol_reward=Wei(int(reward_token["protocolPeriodReward"])), distributor_reward=Wei(int(reward_token["distributorPeriodReward"])), ) return VotingParameters(rewards=rewards, distributor=distributor)
def _update_block_info_cache() -> None: avg_block_time = block_info.get(AVG_BLOCK_TIME_KEY, default_average_block_time) avg_block_sample_size = block_info.get(AVG_BLOCK_SAMPLE_SIZE_KEY, 0) avg_block_time_updated_at = block_info.get( AVG_BLOCK_TIME_UPDATED_AT_KEY, 0) # compute age as counted by number of blocks since the avg_block_time if avg_block_time == 0: avg_block_time_age_in_blocks: float = avg_block_sample_size else: avg_block_time_age_in_blocks = ( (time.time() - avg_block_time_updated_at) / avg_block_time) if avg_block_time_age_in_blocks >= avg_block_sample_size: # If the length of time since the average block time as # measured by blocks is greater than or equal to the number of # blocks sampled then we need to recompute the average block # time. latest_block = web3.eth.getBlock('latest') ancestor_block_number = BlockNumber( max( 0, latest_block['number'] - average_block_time_sample_size, )) ancestor_block = web3.eth.getBlock(ancestor_block_number) sample_size = latest_block['number'] - ancestor_block_number block_info[AVG_BLOCK_SAMPLE_SIZE_KEY] = sample_size if sample_size != 0: block_info[AVG_BLOCK_TIME_KEY] = ( (latest_block['timestamp'] - ancestor_block['timestamp']) / sample_size) else: block_info[AVG_BLOCK_TIME_KEY] = avg_block_time block_info[AVG_BLOCK_TIME_UPDATED_AT_KEY] = time.time() if 'latest_block' in block_info: latest_block = block_info['latest_block'] time_since_latest_block = time.time( ) - latest_block['timestamp'] # latest block is too old so update cache if time_since_latest_block > avg_block_time: block_info['latest_block'] = web3.eth.getBlock('latest') else: # latest block has not been fetched so we fetch it. block_info['latest_block'] = web3.eth.getBlock('latest')
async def get_voting_parameters(network: str) -> ValidatorVotingParameters: """Fetches validator voting parameters.""" result: Dict = await execute_sw_gql_query( network=network, query=VALIDATOR_VOTING_PARAMETERS_QUERY, variables={}, ) network = result["networks"][0] pool = result["pools"][0] meta = result["_meta"] return ValidatorVotingParameters( validators_nonce=int(network["oraclesValidatorsNonce"]), pool_balance=Wei(int(pool["balance"])), latest_block_number=BlockNumber(int(meta["block"]["number"])), )
def create_latest_block_uri(w3: "Web3", from_blocks_ago: int = 3) -> URI: """ Creates a block uri for the given w3 instance. Defaults to 3 blocks prior to the "latest" block to accommodate for block reorgs. If using a testnet with less than 3 mined blocks, adjust :from_blocks_ago:. """ chain_id = to_hex(get_genesis_block_hash(w3)) latest_block_tx_receipt = w3.eth.get_block("latest") target_block_number = BlockNumber(latest_block_tx_receipt["number"] - from_blocks_ago) if target_block_number < 0: raise Exception( f"Only {latest_block_tx_receipt['number']} blocks avaible on provided w3, " f"cannot create latest block uri for {from_blocks_ago} blocks ago." ) recent_block = to_hex(w3.eth.get_block(target_block_number)["hash"]) return create_block_uri(chain_id, recent_block)
async def get_one_time_rewards(network: str, from_block: BlockNumber, to_block: BlockNumber) -> Rewards: """Fetches one time rewards.""" distributor_fallback_address = NETWORKS[network][ "DISTRIBUTOR_FALLBACK_ADDRESS"] last_id = "" result: Dict = await execute_sw_gql_query( network=network, query=ONE_TIME_DISTRIBUTIONS_QUERY, variables=dict(from_block=from_block, to_block=to_block, last_id=last_id), ) distributions_chunk = result.get("oneTimeDistributions", []) distributions = distributions_chunk # accumulate chunks of distributions while len(distributions_chunk) >= 1000: last_id = distributions_chunk[-1]["id"] result: Dict = await execute_sw_gql_query( network=network, query=ONE_TIME_DISTRIBUTIONS_QUERY, variables=dict(from_block=from_block, to_block=to_block, last_id=last_id), ) distributions_chunk = result.get("oneTimeDistributions", []) distributions.extend(distributions_chunk) # process one time distributions final_rewards: Rewards = {} for distribution in distributions: distributed_at_block = BlockNumber( int(distribution["distributedAtBlock"])) if not (from_block < distributed_at_block <= to_block): continue total_amount = int(distribution["amount"]) distributed_amount = 0 token = Web3.toChecksumAddress(distribution["token"]) rewards: Rewards = {} try: allocated_rewards = await get_one_time_rewards_allocations( distribution["rewardsLink"]) for beneficiary, amount in allocated_rewards.items(): if beneficiary == EMPTY_ADDR_HEX: continue rewards.setdefault(beneficiary, {})[token] = amount distributed_amount += int(amount) if total_amount != distributed_amount: logger.warning( f'[{network}] Failed to process one time distribution: {distribution["id"]}. Invalid rewards.' ) rewards: Rewards = { distributor_fallback_address: { token: str(total_amount) } } except Exception as e: logger.error(e) logger.warning( f'[{network}] Failed to process one time distribution: {distribution["id"]}. Exception occurred.' ) rewards: Rewards = { distributor_fallback_address: { token: str(total_amount) } } final_rewards = DistributorRewards.merge_rewards( final_rewards, rewards) return final_rewards
async def get_operators_rewards( network: str, from_block: BlockNumber, to_block: BlockNumber, total_reward: Wei, ) -> Tuple[Rewards, Wei]: """Fetches operators rewards.""" result: Dict = await execute_sw_gql_query( network=network, query=OPERATORS_REWARDS_QUERY, variables=dict(block_number=to_block, ), ) operators = result["operators"] reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"] # process operators points: Dict[ChecksumAddress, int] = {} total_points = 0 total_validators = 0 for operator in operators: account = Web3.toChecksumAddress(operator["id"]) if account == EMPTY_ADDR_HEX: continue validators_count = int(operator["validatorsCount"]) total_validators += validators_count revenue_share = int(operator["revenueShare"]) prev_account_points = int(operator["distributorPoints"]) updated_at_block = BlockNumber(int(operator["updatedAtBlock"])) if from_block > updated_at_block: updated_at_block = from_block prev_account_points = 0 account_points = prev_account_points + (validators_count * revenue_share * (to_block - updated_at_block)) if account_points <= 0: continue points[account] = points.get(account, 0) + account_points total_points += account_points if total_validators <= 0: return {}, total_reward operators_reward = Wei( (total_reward * total_points) // (total_validators * 10000 * (to_block - from_block))) if operators_reward <= 0: return {}, total_reward operators_reward = min(total_reward, operators_reward) rewards = calculate_points_based_rewards( total_reward=operators_reward, points=points, total_points=total_points, reward_token=reward_token_address, ) return rewards, Wei(total_reward - operators_reward)