Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom seconds per slot and slots per epoch #97

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions eth_validator_watcher/beacon.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
BlockIdentierType,
Committees,
Genesis,
Spec,
Header,
ProposerDuties,
Rewards,
Expand Down Expand Up @@ -120,6 +121,15 @@ def get_genesis(self) -> Genesis:
genesis_dict = response.json()
return Genesis(**genesis_dict)

def get_spec(self) -> Spec:
"""Get network specification."""
response = self.__get_retry_not_found(
f"{self.__url}/eth/v1/config/spec", timeout=TIMEOUT_BEACON_SEC
)
response.raise_for_status()
spec_dict = response.json()
return Spec(**spec_dict)

def get_header(self, block_identifier: Union[BlockIdentierType, int]) -> Header:
"""Get a header.

Expand All @@ -129,7 +139,8 @@ def get_header(self, block_identifier: Union[BlockIdentierType, int]) -> Header:
"""
try:
response = self.__get(
f"{self.__url}/eth/v1/beacon/headers/{block_identifier}", timeout=TIMEOUT_BEACON_SEC
f"{self.__url}/eth/v1/beacon/headers/{block_identifier}",
timeout=TIMEOUT_BEACON_SEC,
)

response.raise_for_status()
Expand Down Expand Up @@ -176,7 +187,8 @@ def get_proposer_duties(self, epoch: int) -> ProposerDuties:
epoch: Epoch corresponding to the proposer duties to retrieve
"""
response = self.__get_retry_not_found(
f"{self.__url}/eth/v1/validator/duties/proposer/{epoch}", timeout=TIMEOUT_BEACON_SEC
f"{self.__url}/eth/v1/validator/duties/proposer/{epoch}",
timeout=TIMEOUT_BEACON_SEC,
)

response.raise_for_status()
Expand All @@ -193,7 +205,8 @@ def get_status_to_index_to_validator(
inner value : Validator
"""
response = self.__get_retry_not_found(
f"{self.__url}/eth/v1/beacon/states/head/validators", timeout=TIMEOUT_BEACON_SEC
f"{self.__url}/eth/v1/beacon/states/head/validators",
timeout=TIMEOUT_BEACON_SEC,
)

response.raise_for_status()
Expand Down
45 changes: 35 additions & 10 deletions eth_validator_watcher/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
from .utils import (
CHUCK_NORRIS,
MISSED_BLOCK_TIMEOUT_SEC,
NB_SECOND_PER_SLOT,
NB_SLOT_PER_EPOCH,
SLOT_FOR_MISSED_ATTESTATIONS_PROCESS,
SLOT_FOR_REWARDS_PROCESS,
LimitedDict,
Expand Down Expand Up @@ -246,26 +244,35 @@ def _handler(

genesis = beacon.get_genesis()

for idx, (slot, slot_start_time_sec) in enumerate(slots(genesis.data.genesis_time)):
spec = beacon.get_spec()
seconds_per_slot = spec.data.SECONDS_PER_SLOT
slots_per_epoch = spec.data.SLOTS_PER_EPOCH

for idx, (slot, slot_start_time_sec) in enumerate(
slots(
genesis.data.genesis_time,
seconds_per_slot=seconds_per_slot,
)
):
if slot < 0:
chain_start_in_sec = -slot * NB_SECOND_PER_SLOT
chain_start_in_sec = -slot * seconds_per_slot
days, hours, minutes, seconds = convert_seconds_to_dhms(chain_start_in_sec)

print(
f"⏱️ The chain will start in {days:2} days, {hours:2} hours, "
f"{minutes:2} minutes and {seconds:2} seconds."
)

if slot % NB_SLOT_PER_EPOCH == 0:
if slot % slots_per_epoch == 0:
print(f"💪 {CHUCK_NORRIS[slot%len(CHUCK_NORRIS)]}")

if liveness_file is not None:
write_liveness_file(liveness_file)

continue

epoch = slot // NB_SLOT_PER_EPOCH
slot_in_epoch = slot % NB_SLOT_PER_EPOCH
epoch = slot // slots_per_epoch
slot_in_epoch = slot % slots_per_epoch

metric_slot_gauge.set(slot)
metric_epoch_gauge.set(epoch)
Expand Down Expand Up @@ -389,10 +396,21 @@ def _handler(

last_rewards_process_epoch = epoch

process_future_blocks_proposal(beacon, our_pubkeys, slot, is_new_epoch)
process_future_blocks_proposal(
beacon,
our_pubkeys,
slot,
is_new_epoch,
slots_per_epoch=slots_per_epoch,
)

last_processed_finalized_slot = process_missed_blocks_finalized(
beacon, last_processed_finalized_slot, slot, our_pubkeys, slack
beacon,
last_processed_finalized_slot,
slot,
our_pubkeys,
slack,
slots_per_epoch=slots_per_epoch,
)

delta_sec = MISSED_BLOCK_TIMEOUT_SEC - (time() - slot_start_time_sec)
Expand All @@ -408,10 +426,16 @@ def _handler(
block,
slot,
our_active_idx2val,
slots_per_epoch=slots_per_epoch,
)

process_fee_recipient(
block, our_active_idx2val, execution, fee_recipient, slack
block,
our_active_idx2val,
execution,
fee_recipient,
slack,
slots_per_epoch=slots_per_epoch,
)

is_our_validator = process_missed_blocks_head(
Expand All @@ -420,6 +444,7 @@ def _handler(
slot,
our_pubkeys,
slack,
slots_per_epoch=slots_per_epoch,
)

if is_our_validator and potential_block is not None:
Expand Down
3 changes: 2 additions & 1 deletion eth_validator_watcher/fee_recipient.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def process_fee_recipient(
execution: Execution | None,
expected_fee_recipient: str | None,
slack: Slack | None,
slots_per_epoch: int = NB_SLOT_PER_EPOCH,
) -> None:
"""Check if the fee recipient is the one expected.

Expand All @@ -44,7 +45,7 @@ def process_fee_recipient(

short_proposer_pubkey = index_to_validator[proposer_index].pubkey[:10]
slot = block.data.message.slot
epoch = slot // NB_SLOT_PER_EPOCH
epoch = slot // slots_per_epoch

# First, we check if the beacon block fee recipient is the one expected
# `.lower()` is here just in case the execution client returns a fee recipient in
Expand Down
8 changes: 5 additions & 3 deletions eth_validator_watcher/missed_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def process_missed_blocks_head(
slot: int,
our_pubkeys: set[str],
slack: Slack | None,
slots_per_epoch: int = NB_SLOT_PER_EPOCH,
) -> bool:
"""Process missed block proposals detection at head

Expand All @@ -40,7 +41,7 @@ def process_missed_blocks_head(
Returns `True` if we had to propose the block, `False` otherwise
"""
missed = potential_block is None
epoch = slot // NB_SLOT_PER_EPOCH
epoch = slot // slots_per_epoch
proposer_duties = beacon.get_proposer_duties(epoch)

# Get proposer public key for this slot
Expand Down Expand Up @@ -98,6 +99,7 @@ def process_missed_blocks_finalized(
slot: int,
our_pubkeys: set[str],
slack: Slack | None,
slots_per_epoch: int = NB_SLOT_PER_EPOCH,
) -> int:
"""Process missed block proposals detection at finalized

Expand All @@ -114,14 +116,14 @@ def process_missed_blocks_finalized(

last_finalized_header = beacon.get_header(BlockIdentierType.FINALIZED)
last_finalized_slot = last_finalized_header.data.header.message.slot
epoch_of_last_finalized_slot = last_finalized_slot // NB_SLOT_PER_EPOCH
epoch_of_last_finalized_slot = last_finalized_slot // slots_per_epoch

# Only to memoize it, in case of the BN does not serve this request for too old
# epochs
beacon.get_proposer_duties(epoch_of_last_finalized_slot)

for slot_ in range(last_processed_finalized_slot + 1, last_finalized_slot + 1):
epoch = slot_ // NB_SLOT_PER_EPOCH
epoch = slot_ // slots_per_epoch
proposer_duties = beacon.get_proposer_duties(epoch)

# Get proposer public key for this slot
Expand Down
8 changes: 8 additions & 0 deletions eth_validator_watcher/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class Data(BaseModel):
data: Data


class Spec(BaseModel):
class Data(BaseModel):
SECONDS_PER_SLOT: int
SLOTS_PER_EPOCH: int

data: Data


class Header(BaseModel):
class Data(BaseModel):
class Header(BaseModel):
Expand Down
3 changes: 2 additions & 1 deletion eth_validator_watcher/next_blocks_proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def process_future_blocks_proposal(
our_pubkeys: set[str],
slot: int,
is_new_epoch: bool,
slots_per_epoch: int = NB_SLOT_PER_EPOCH,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It'd be better to not default to something here and unconditionally use the value from the spec ; happy to do a pass on top of your PR to force this parameter :)

) -> int:
"""Handle next blocks proposal

Expand All @@ -29,7 +30,7 @@ def process_future_blocks_proposal(
slot : Slot
is_new_epoch: Is new epoch
"""
epoch = slot // NB_SLOT_PER_EPOCH
epoch = slot // slots_per_epoch
proposers_duties_current_epoch = beacon.get_proposer_duties(epoch)
proposers_duties_next_epoch = beacon.get_proposer_duties(epoch + 1)

Expand Down
7 changes: 5 additions & 2 deletions eth_validator_watcher/suboptimal_attestations.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def process_suboptimal_attestations(
block: Block,
slot: int,
our_active_validators_index_to_validator: dict[int, Validators.DataItem.Validator],
slots_per_epoch: int = NB_SLOT_PER_EPOCH,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ditto

) -> set[int]:
"""Process sub-optimal attestations

Expand All @@ -48,7 +49,7 @@ def process_suboptimal_attestations(

# Epoch of previous slot is NOT the previous epoch, but really the epoch
# corresponding to the previous slot.
epoch_of_previous_slot = previous_slot // NB_SLOT_PER_EPOCH
epoch_of_previous_slot = previous_slot // slots_per_epoch

# All our active validators index
our_active_validators_index = set(our_active_validators_index_to_validator)
Expand Down Expand Up @@ -133,7 +134,9 @@ def process_suboptimal_attestations(
)

if suboptimal_attestations_rate is not None:
metric_suboptimal_attestations_rate_gauge.set(100 * suboptimal_attestations_rate)
metric_suboptimal_attestations_rate_gauge.set(
100 * suboptimal_attestations_rate
)

if len(our_validators_index_that_did_not_attest_optimally_during_previous_slot) > 0:
assert suboptimal_attestations_rate is not None
Expand Down
9 changes: 6 additions & 3 deletions eth_validator_watcher/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,15 @@ def send_message(self, message: str) -> None:
self.__client.chat_postMessage(channel=self.__channel, text=message)


def slots(genesis_time_sec: int) -> Iterator[Tuple[int, int]]:
next_slot = int((time() - genesis_time_sec) / NB_SECOND_PER_SLOT) + 1
def slots(
genesis_time_sec: int,
seconds_per_slot: int = NB_SECOND_PER_SLOT,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ditto

) -> Iterator[Tuple[int, int]]:
next_slot = int((time() - genesis_time_sec) / seconds_per_slot) + 1

try:
while True:
next_slot_time_sec = genesis_time_sec + next_slot * NB_SECOND_PER_SLOT
next_slot_time_sec = genesis_time_sec + next_slot * seconds_per_slot
time_to_wait = next_slot_time_sec - time()
sleep(max(0, time_to_wait))

Expand Down
Loading