diff --git a/pyproject.toml b/pyproject.toml index 54549d4..80b5752 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sw-utils" -version = "v0.6.21" +version = "v0.6.22" description = "StakeWise Python utils" authors = ["StakeWise Labs "] license = "GPL-3.0-or-later" diff --git a/sw_utils/consensus.py b/sw_utils/consensus.py index 2c671f8..3c10ccd 100644 --- a/sw_utils/consensus.py +++ b/sw_utils/consensus.py @@ -208,10 +208,9 @@ def get_consensus_client( async def get_chain_finalized_head( consensus_client: ExtendedAsyncBeacon, slots_per_epoch: int, - state: str = 'finalized', ) -> ChainHead: - """Fetches the fork safe chain head.""" - block_data = await consensus_client.get_block(state) + """Fetches the fork finalized chain head.""" + block_data = await consensus_client.get_block('finalized') slot = int(block_data['data']['message']['slot']) return ChainHead( @@ -226,6 +225,34 @@ async def get_chain_finalized_head( ) +async def get_chain_justified_head( + consensus_client: ExtendedAsyncBeacon, + slots_per_epoch: int, +) -> ChainHead: + """Fetches the fork safe chain head.""" + checkpoints = await consensus_client.get_finality_checkpoint() + epoch: int = int(checkpoints['data']['current_justified']['epoch']) + last_slot_id: int = epoch * slots_per_epoch + for i in range(slots_per_epoch): + try: + slot = await consensus_client.get_block(str(last_slot_id - i)) + except ClientResponseError as e: + if hasattr(e, 'status') and e.status == 404: + # slot was not proposed, try the previous one + continue + raise e + + execution_payload = slot['data']['message']['body']['execution_payload'] + return ChainHead( + epoch=epoch, + slot=last_slot_id - i, + block_number=BlockNumber(int(execution_payload['block_number'])), + execution_ts=Timestamp(int(execution_payload['timestamp'])), + ) + + raise RuntimeError(f'Failed to fetch slot for epoch {epoch}') + + async def get_chain_epoch_head( epoch: int, slots_per_epoch: int, @@ -233,7 +260,7 @@ async def get_chain_epoch_head( consensus_client: ExtendedAsyncBeacon, ) -> ChainHead: """Fetches the epoch chain head.""" - slot_id: int = (epoch * slots_per_epoch) + slots_per_epoch - 1 + slot_id: int = epoch * slots_per_epoch for i in range(slots_per_epoch): try: slot = await consensus_client.get_block(str(slot_id - i)) diff --git a/sw_utils/typings.py b/sw_utils/typings.py index fdd6040..1f9a165 100644 --- a/sw_utils/typings.py +++ b/sw_utils/typings.py @@ -24,14 +24,6 @@ class ChainHead: block_number: BlockNumber execution_ts: Timestamp - @property - def consensus_block(self) -> int: - return self.slot - - @property - def execution_block(self) -> BlockNumber: - return self.block_number - @dataclass class Oracle: