Skip to content

Commit

Permalink
OCT-1326: block rewards issues (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
leoni-q authored Mar 4, 2024
2 parents 67977c5 + a651460 commit 910d54d
Show file tree
Hide file tree
Showing 16 changed files with 88 additions and 194 deletions.
2 changes: 1 addition & 1 deletion backend/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def create_app(config=None):
register_extensions(app)
register_errorhandlers(app)
register_epoch_settings()
register_services()
register_services(app)

return app

Expand Down
39 changes: 20 additions & 19 deletions backend/app/context/epoch_details.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from datetime import datetime
from typing import List, Tuple

from app.settings import config
from app.context.epoch_state import EpochState
from app.context.helpers import check_if_future
from app.exceptions import InvalidBlocksRange
from app.extensions import epochs
from app.infrastructure import graphql
from app.infrastructure.external_api.etherscan.blocks import get_block_num_from_ts
from app.legacy.utils.time import from_timestamp_s, sec_to_days
from app.shared.blockchain_types import compare_blockchain_types, ChainTypes


class EpochDetails:
Expand All @@ -20,6 +18,7 @@ def __init__(
duration,
decision_window,
remaining_sec=None,
with_block_range=False,
):
self.epoch_num = int(epoch_num)
self.duration_sec = int(duration)
Expand All @@ -28,9 +27,9 @@ def __init__(
self.decision_window_days = sec_to_days(self.decision_window_sec)
self.start_sec = int(start)
self.end_sec = self.start_sec + self.duration_sec
self.start_block, self.end_block = self._calc_blocks_range()
self.finalized_sec = self.end_sec + self.decision_window_sec
self.finalized_timestamp = from_timestamp_s(self.finalized_sec)
self.start_block, self.end_block = self._calc_blocks_range(with_block_range)

if remaining_sec is None:
now_sec = int(datetime.utcnow().timestamp())
Expand All @@ -44,29 +43,24 @@ def __init__(
self.remaining_sec = remaining_sec

self.remaining_days = sec_to_days(self.remaining_sec)
self.block_rewards = None

@property
def duration_range(self) -> Tuple[int, int]:
return self.start_sec, self.end_sec

@property
def no_blocks(self):
def blocks_range(self):
"""
Returns the number of blocks within [start_block, end_block) in the epoch.
"""
if not self.end_block or not self.start_block:
raise InvalidBlocksRange
return self.end_block - self.start_block

def _calc_blocks_range(self) -> tuple:
can_blocks_be_calced = (
compare_blockchain_types(config.CHAIN_ID, ChainTypes.MAINNET)
and self.start_sec
and self.end_sec
)
def _calc_blocks_range(self, with_block_range=False) -> tuple:
start_block, end_block = None, None
if can_blocks_be_calced:

if with_block_range:
is_start_future = check_if_future(self.start_sec)
is_end_future = check_if_future(self.end_sec)

Expand All @@ -80,22 +74,26 @@ def _calc_blocks_range(self) -> tuple:
return start_block, end_block


def get_epoch_details(epoch_num: int, epoch_state: EpochState) -> EpochDetails:
def get_epoch_details(
epoch_num: int, epoch_state: EpochState, with_block_range=False
) -> EpochDetails:
if epoch_state == EpochState.FUTURE:
return get_future_epoch_details(epoch_num)
return get_epoch_details_by_number(epoch_num)
return get_epoch_details_by_number(epoch_num, with_block_range)


def get_epoch_details_by_number(epoch_num: int) -> EpochDetails:
def get_epoch_details_by_number(epoch_num: int, with_block_range=False) -> EpochDetails:
epoch_details = graphql.epochs.get_epoch_by_number(epoch_num)
return _epoch_details_from_graphql_result(epoch_details)
return _epoch_details_from_graphql_result(epoch_details, with_block_range)


def get_epochs_details(from_epoch: int, to_epoch: int) -> List[EpochDetails]:
def get_epochs_details(
from_epoch: int, to_epoch: int, with_block_range=False
) -> List[EpochDetails]:
epochs_details = graphql.epochs.get_epochs_by_range(from_epoch, to_epoch)

return [
_epoch_details_from_graphql_result(epoch_details)
_epoch_details_from_graphql_result(epoch_details, with_block_range)
for epoch_details in epochs_details
]

Expand All @@ -111,10 +109,13 @@ def get_future_epoch_details(epoch_num: int) -> EpochDetails:
)


def _epoch_details_from_graphql_result(epoch_details: dict) -> EpochDetails:
def _epoch_details_from_graphql_result(
epoch_details: dict, with_block_range=False
) -> EpochDetails:
return EpochDetails(
epoch_num=epoch_details["epoch"],
start=epoch_details["fromTs"],
duration=epoch_details["duration"],
decision_window=epoch_details["decisionWindow"],
with_block_range=with_block_range,
)
9 changes: 8 additions & 1 deletion backend/app/context/manager.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from flask import current_app as app

from app.extensions import cache
from app.context.epoch_details import get_epoch_details, EpochDetails
from app.context.epoch_state import EpochState, get_epoch_state, get_epoch_number
from app.context.projects import ProjectsDetails, get_projects_details
from app.engine.epochs_settings import get_epoch_settings, EpochSettings
from app.pydantic import Model
from app.shared.blockchain_types import compare_blockchain_types, ChainTypes


class Context(Model):
Expand All @@ -24,8 +26,13 @@ def state_context(epoch_state: EpochState) -> Context:
return build_context(epoch_num, epoch_state)


@cache.memoize(timeout=600)
def build_context(epoch_num: int, epoch_state: EpochState) -> Context:
epoch_details = get_epoch_details(epoch_num, epoch_state)
is_mainnet = compare_blockchain_types(app.config["CHAIN_ID"], ChainTypes.MAINNET)

epoch_details = get_epoch_details(
epoch_num, epoch_state, with_block_range=is_mainnet
)
epoch_settings = get_epoch_settings(epoch_num)
projects_details = get_projects_details(epoch_num)
context = Context(
Expand Down
20 changes: 7 additions & 13 deletions backend/app/infrastructure/external_api/bitquery/blocks_reward.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,20 @@
)


def get_blocks_rewards(
address: str, start_time: str, end_time: str, limit: int
) -> list:
def get_blocks_rewards(address: str, start_block: int, end_block: int) -> float:
"""
Fetch Ethereum blocks within a specified time range in ascending order by timestamp.
Fetch Ethereum blocks rewards for given address and start and end block.
Args:
- start_time (str): The start time in ISO 8601 format.
- end_time (str): The end time in ISO 8601 format.
- start_block (str): The start block number.
- end_block (str): The end block number.
- address (str): The miner (fee recipient) address.
- limit (int): The number of blocks to retrieve starting from start_time.
Useful whilst getting end_blocks exclusively from epochs.
"""
payload = produce_payload(
action_type=BitQueryActions.GET_BLOCK_REWARDS,
address=address,
start_time=start_time,
end_time=end_time,
limit=limit,
start_block=start_block,
end_block=end_block,
)
headers = get_bitquery_header()

Expand All @@ -43,5 +38,4 @@ def get_blocks_rewards(
app_module.ExceptionHandler.print_stacktrace(e)
raise ExternalApiException(api_url, e, 500)

blocks = json_response.json()["data"]["ethereum"]["blocks"]
return blocks
return json_response["data"]["ethereum"]["blocks"][0]["reward"]
30 changes: 9 additions & 21 deletions backend/app/infrastructure/external_api/bitquery/req_producer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,20 @@ def produce_payload(action_type: BitQueryActions, **query_values) -> str:


def _block_rewards_payload(
start_time: str, end_time: str, address: str, limit: int, **kwargs
start_block: int, end_block: int, address: str, **kwargs
) -> str:
payload = json.dumps(
{
"query": f"""query ($network: EthereumNetwork!, $from: ISO8601DateTime, $till: ISO8601DateTime) {{
ethereum(network: $network) {{
blocks(time: {{since: $from, till: $till}}, options: {{asc: "timestamp.unixtime", limit: {limit}}}) {{
timestamp {{
unixtime
}}
reward
address: miner(miner: {{is: "{address}"}}) {{
address
}}
"query": f"""query {{
ethereum {{
blocks(
height: {{gteq: {start_block}, lteq: {end_block}}},
miner: {{is: "{address}"}}
) {{
reward(blockReward: {{gt: 0}})
}}
}}
}}""",
"variables": json.dumps(
{
"network": "ethereum",
"from": start_time,
"till": end_time,
"limit": limit,
"dateFormat": "%Y-%m-%d",
}
),
}}"""
}
)

Expand Down
5 changes: 0 additions & 5 deletions backend/app/legacy/utils/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,3 @@ def sec_to_days(sec: int) -> int:

def days_to_sec(days: int) -> int:
return int(days * 86400)


def timestamp_to_isoformat(timestamp_sec: int) -> str:
timestamp = from_timestamp_s(timestamp_sec)
return timestamp.to_isoformat()
10 changes: 4 additions & 6 deletions backend/app/modules/modules_factory/pre_pending.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import app.modules.staking.proceeds.service.aggregated as aggregated
import app.modules.staking.proceeds.service.contract_balance as contract_balance
from app.shared.blockchain_types import ChainTypes, compare_blockchain_types
from app.modules.modules_factory.protocols import (
AllUserEffectiveDeposits,
OctantRewards,
Expand All @@ -16,7 +15,7 @@
DbAndGraphEventsGenerator,
)
from app.pydantic import Model
from app.settings import config
from app.shared.blockchain_types import compare_blockchain_types, ChainTypes


class PrePendingUserDeposits(UserEffectiveDeposits, AllUserEffectiveDeposits, Protocol):
Expand All @@ -29,13 +28,12 @@ class PrePendingServices(Model):
pending_snapshots_service: PendingSnapshots

@staticmethod
def create() -> "PrePendingServices":
def create(chain_id: int) -> "PrePendingServices":
is_mainnet = compare_blockchain_types(chain_id, ChainTypes.MAINNET)

user_deposits = CalculatedUserDeposits(
events_generator=DbAndGraphEventsGenerator()
)
current_chain_id = config.CHAIN_ID
is_mainnet = compare_blockchain_types(current_chain_id, ChainTypes.MAINNET)

octant_rewards = CalculatedOctantRewards(
staking_proceeds=aggregated.AggregatedStakingProceeds()
if is_mainnet
Expand Down
6 changes: 4 additions & 2 deletions backend/app/modules/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ def get_services(epoch_state: EpochState):
return SERVICE_REGISTRY[epoch_state]


def register_services():
def register_services(app):
chain_id = app.config["CHAIN_ID"]

SERVICE_REGISTRY[EpochState.FUTURE] = FutureServices.create()
SERVICE_REGISTRY[EpochState.CURRENT] = CurrentServices.create()
SERVICE_REGISTRY[EpochState.PRE_PENDING] = PrePendingServices.create()
SERVICE_REGISTRY[EpochState.PRE_PENDING] = PrePendingServices.create(chain_id)
SERVICE_REGISTRY[EpochState.PENDING] = PendingServices.create()
SERVICE_REGISTRY[EpochState.FINALIZING] = FinalizingServices.create()
SERVICE_REGISTRY[EpochState.FINALIZED] = FinalizedServices.create()
13 changes: 2 additions & 11 deletions backend/app/modules/staking/proceeds/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from decimal import Decimal

import pandas as pd
from gmpy2 import mpz

Expand Down Expand Up @@ -56,15 +54,8 @@ def sum_withdrawals(withdrawals_txs: list[dict]) -> int:
return w3.to_wei(int(total_gwei), "gwei")


def sum_blocks_rewards(blocks_rewards: list) -> int:
df = pd.DataFrame(blocks_rewards)
blocks_reward_eth = df["reward"].apply(Decimal).sum()

return int(w3.to_wei(blocks_reward_eth, "ether"))


def aggregate_proceeds(mev: int, withdrawals: int, blocks_rewards: list) -> int:
return mev + withdrawals + sum_blocks_rewards(blocks_rewards)
def aggregate_proceeds(mev: int, withdrawals: int, blocks_rewards_eth: float) -> int:
return mev + withdrawals + int(w3.to_wei(blocks_rewards_eth, "ether"))


def _filter_deposit_withdrawals(amount: mpz) -> mpz:
Expand Down
33 changes: 5 additions & 28 deletions backend/app/modules/staking/proceeds/service/aggregated.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
AccountAction,
)
from app.infrastructure.external_api.bitquery.blocks_reward import get_blocks_rewards
from app.legacy.utils.time import timestamp_to_isoformat
from app.modules.staking.proceeds.core import (
sum_mev,
sum_withdrawals,
Expand All @@ -16,24 +15,6 @@


class AggregatedStakingProceeds(Model):
def _retrieve_blocks_rewards(
self, start_sec: int, end_sec: int, withdrawals_target: str, limit: int
) -> list:
blocks_rewards = []

if end_sec is None:
return blocks_rewards

start_datetime, end_datetime = (
timestamp_to_isoformat(start_sec),
timestamp_to_isoformat(end_sec),
)

blocks_rewards = get_blocks_rewards(
withdrawals_target, start_datetime, end_datetime, limit=limit
)
return blocks_rewards

def get_staking_proceeds(self, context: Context) -> int:
"""
Retrieves a list of transactions, calculates MEV value and aggregates it with withdrawals.
Expand All @@ -50,15 +31,8 @@ def get_staking_proceeds(self, context: Context) -> int:
context.epoch_details.start_block,
context.epoch_details.end_block,
)

start_sec, end_sec = context.epoch_details.duration_range
no_blocks_to_get = context.epoch_details.no_blocks

blocks_rewards = self._retrieve_blocks_rewards(
start_sec, end_sec, withdrawals_target, limit=no_blocks_to_get
)

end_block_for_transactions = end_block - 1

normal = get_transactions(
withdrawals_target,
start_block,
Expand All @@ -77,7 +51,10 @@ def get_staking_proceeds(self, context: Context) -> int:
end_block_for_transactions,
tx_type=AccountAction.BEACON_WITHDRAWAL,
)
block_rewards_value = get_blocks_rewards(
withdrawals_target, start_block, end_block_for_transactions
)
mev_value = sum_mev(withdrawals_target, normal, internal)
withdrawals_value = sum_withdrawals(withdrawals)

return aggregate_proceeds(mev_value, withdrawals_value, blocks_rewards)
return aggregate_proceeds(mev_value, withdrawals_value, block_rewards_value)
Loading

0 comments on commit 910d54d

Please sign in to comment.