Skip to content

Commit

Permalink
added flexibility to number of sync committees to display
Browse files Browse the repository at this point in the history
  • Loading branch information
ph1go committed Nov 3, 2021
1 parent f6b2324 commit e46c2c3
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 63 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ The idea is that the application is run via a job/task scheduler (eg crontab) an
validator index appears in the current or next sync committee. The sync committees change every ~27 hours so there's
no point checking every minute but a period of an hour will give you plenty of notice.

By default, with the value of `number_of_future_committees` set to 1, 3 sync committees are shown (the current one, the
next one and the one after). Change this value to show more sync committees. Only the validators in the current and
next sync committees are known but it might be useful to see the times at which future committees occur.

Running the application
=
Run `eth_sync_committee.py` to launch the application.
Expand Down
3 changes: 3 additions & 0 deletions config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ port = 5052
from_address =
from_password =
to_address =

[options]
number_of_future_committees = 1
5 changes: 5 additions & 0 deletions constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import re
from dataclasses import dataclass, field

altair_epoch = 74240

source_path = Path(__file__).parent
config_file = source_path / 'config.ini'
validators_file = source_path / 'validators.txt'
Expand All @@ -13,6 +15,7 @@
if not config_file.is_file():
cfg['beacon'] = {'url': 'localhost', 'port': '5052'}
cfg['email'] = {'from_address': '', 'from_password': '', 'to_address': ''}
cfg['options'] = {'number_of_future_committees': '1'}

with config_file.open('w') as f:
cfg.write(f)
Expand All @@ -24,6 +27,8 @@
beacon_node_url = cfg['beacon'].get('url', fallback='localhost')
beacon_node_port = cfg['beacon'].get('port', fallback='5052')

number_of_future_committees = cfg['options'].getint('number_of_future_committees', fallback=1)

base_url = f'http://{beacon_node_url}:{beacon_node_port}'
head_url = f'{base_url}/eth/v1/beacon/states/head/sync_committees'
finalized_url = f'{base_url}/eth/v1/beacon/states/finalized/sync_committees'
Expand Down
31 changes: 20 additions & 11 deletions eth_sync_committee.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,30 @@

print(f'\n{check_str}')

longest_name = len(max([epochs[x].name for x in epochs], key=len))
print(f'\n {" " * longest_name} {"epoch":>8} {"start date & time":^19} validators')
for k in ['c_sync', 'c_epoch', 'n_sync', 'n_sync_2']:
longest_name = len(max([x.name_with_num for x in epochs], key=len))
longest_val_str = len(max([x.validators_str for x in epochs], key=len))

print(
f'\n {" " * longest_name} {"epoch":>7} {"start date & time":^19} validators\n'
f' {"-" * (longest_name + 33 + longest_val_str)}'
)

for e in epochs:
print(
f' {epochs[k].name:{longest_name}} {epochs[k].epoch_number:8} '
f'{epochs[k].start_time.strftime("%Y/%m/%d %H:%M:%S")} '
f'{epochs[k].validators_str if epochs[k].is_sync_committee else "n/a"}'
f' {e.name_with_num:{longest_name}} {e.epoch_number:>7} '
f'{e.start_time.strftime("%Y/%m/%d %H:%M:%S")} '
f'{e.validators_str if e.is_sync_committee else "n/a"}'
)

if epochs['c_sync'].validators or epochs['n_sync'].validators:
generate_notification(current_committee=epochs['c_sync'], next_committee=epochs['n_sync'])
current_committee = [e for e in epochs if e.name == 'current'][0]
next_committee = [e for e in epochs if e.name == 'next'][0]

if current_committee.validators or next_committee.validators:
generate_notification(current_committee=current_committee, next_committee=next_committee)

if args.print_all:
for k in ['c_sync', 'n_sync']:
print(f'\n {epochs[k].name}:\n')
print_all_validators(epochs[k].all_validators)
for committee in [current_committee, next_committee]:
print(f'\n {committee.name_with_num}:\n')
print_all_validators(committee.all_validators)

print()
126 changes: 74 additions & 52 deletions functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
from dataclasses import dataclass, field, InitVar
from typing import List

from constants import validators_file, config_file, notified_file, finalized_url, genesis_url, block_url, email_details
from constants import (
validators_file, config_file, notified_file,
finalized_url, genesis_url, block_url,
email_details, altair_epoch, number_of_future_committees
)


def fetch_url(url):
# print(url)
try:
response = requests.get(url)

Expand All @@ -26,16 +31,20 @@ def fetch_url(url):
@dataclass
class Epoch:
name: str
name_with_num: str = field(init=False)
epoch_number: int
is_sync_committee: bool = False
start_time: datetime = field(init=False)
end_time: datetime = field(init=False)
start_str: str = field(init=False, default='')
end_str: str = field(init=False, default='')
validators_str: str = field(init=False)

genesis_time: InitVar[datetime] = None

def __post_init__(self, genesis_time: datetime):
_epoch = self.epoch_number - (int(int(self.epoch_number / 256)) * 256)
self.name_with_num = f'{self.name} epoch ({_epoch} of 256)'
start_time_utc = genesis_time + timedelta(seconds=384 * self.epoch_number)
self.start_time = start_time_utc.astimezone()
start_from_now = (self.start_time - datetime.now().astimezone()).total_seconds()
Expand All @@ -49,41 +58,86 @@ def __post_init__(self, genesis_time: datetime):
self.end_time = end_time_utc.astimezone()
end_from_now = (self.end_time - datetime.now().astimezone()).total_seconds()
self.end_str = f'{self.end_time.strftime("%Y/%m/%d %H:%M:%S")} ({seconds_to_hms(end_from_now)} from now)'
self.validators_str = 'n/a'


@dataclass
class SyncCommittee(Epoch):
sync_committee_number: int = field(init=False)
all_validators: List[str] = field(init=False, default_factory=list)
validators: List[str] = field(init=False, default_factory=list)
validators_str: str = field(init=False)

my_validators: InitVar[List[str]] = None
genesis_time: InitVar[datetime] = None
check_for_validators: InitVar[bool] = True

def __post_init__(self, genesis_time: datetime, my_validators: List[str]):
def __post_init__(self, genesis_time: datetime, my_validators: List[str], check_for_validators: bool):
super().__post_init__(genesis_time=genesis_time)
self.sync_committee_number = int((self.epoch_number - altair_epoch) / 256)
self.name_with_num = f'sync committee {self.sync_committee_number}{f" ({self.name})" if self.name else ""}'
self.is_sync_committee = True
response = fetch_url(f'{finalized_url}?epoch={self.epoch_number}')
try:
self.all_validators = sorted(response['data']['validators'], key=int)
if check_for_validators:
response = fetch_url(f'{finalized_url}?epoch={self.epoch_number}')

except KeyError:
self.validators_str = 'validators in this sync committee are not yet known'
try:
self.all_validators = sorted(response['data']['validators'], key=int)

else:
self.validators = sorted(list(set(self.all_validators).intersection(set(my_validators))), key=int)
if my_validators:
if self.validators:
self.validators_str = stringify_list(self.validators)
except KeyError:
self.validators_str = 'validators in this sync committee are not yet known'

else:
self.validators = sorted(list(set(self.all_validators).intersection(set(my_validators))), key=int)
if my_validators:
if self.validators:
self.validators_str = stringify_list(self.validators)

else:
self.validators_str = (
'your validator isn\'t' if len(my_validators) == 1 else 'none of your validators are'
)
self.validators_str += f' in the {self.name} sync committee :('

else:
self.validators_str = (
'your validator isn\'t' if len(my_validators) == 1 else 'none of your validators are'
)
self.validators_str += f' in the {self.name} :('
self.validators_str = 'you haven\'t specified any validators'

else:
self.validators_str = 'validators in this sync committee are not yet known'

else:
self.validators_str = 'you haven\'t specified any validators'

def get_epochs(my_validators):
response = fetch_url(f'{block_url}head')
current_slot = int(response['data']['message']['slot'])
current_epoch = int(current_slot / 32)
current_sc_start_epoch = int(current_epoch / 256) * 256
next_sc_start_epoch = current_sc_start_epoch + 256

response = fetch_url(genesis_url)
genesis_time = datetime.fromtimestamp(int(response['data']['genesis_time']), timezone.utc)

epochs = [
SyncCommittee(
name='current', epoch_number=current_sc_start_epoch,
genesis_time=genesis_time, my_validators=my_validators
),
Epoch(
name='current', epoch_number=current_epoch, genesis_time=genesis_time
),
SyncCommittee(
name='next', epoch_number=next_sc_start_epoch,
genesis_time=genesis_time, my_validators=my_validators
)
]

if number_of_future_committees:
for c in range(number_of_future_committees):
epochs.append(
SyncCommittee(
name='', epoch_number=next_sc_start_epoch + ((c + 1) * 256),
genesis_time=genesis_time, my_validators=my_validators,
check_for_validators=True if c == 0 else False
)
)

return epochs


def get_user_validators(user_provided):
Expand Down Expand Up @@ -148,38 +202,6 @@ def get_user_validators(user_provided):
return sorted(my_validators, key=int)


def get_epochs(my_validators):
response = fetch_url(f'{block_url}head')
current_slot = int(response['data']['message']['slot'])
current_epoch = int(current_slot / 32)
current_sc_start_epoch = int(current_epoch / 256) * 256
next_sc_start_epoch = current_sc_start_epoch + 256
next_sc_2_start_epoch = current_sc_start_epoch + 512

response = fetch_url(genesis_url)
genesis_time = datetime.fromtimestamp(int(response['data']['genesis_time']), timezone.utc)

epochs = {
'c_sync': SyncCommittee(
name='current sync committee', epoch_number=current_sc_start_epoch,
genesis_time=genesis_time, my_validators=my_validators
),
'c_epoch': Epoch(
name='current epoch', epoch_number=current_epoch, genesis_time=genesis_time
),
'n_sync': SyncCommittee(
name='next sync committee', epoch_number=next_sc_start_epoch,
genesis_time=genesis_time, my_validators=my_validators
),
'n_sync_2': SyncCommittee(
name='next sync committee', epoch_number=next_sc_2_start_epoch,
genesis_time=genesis_time, my_validators=my_validators
)
}

return epochs


def print_all_validators(validators):
start_idx = 0
per_line = 20
Expand Down

0 comments on commit e46c2c3

Please sign in to comment.