From b34425d22cd064aab04c7f8c6c097dd7cfaee0dc Mon Sep 17 00:00:00 2001 From: "s. rannou" Date: Tue, 21 Nov 2023 16:07:48 +0100 Subject: [PATCH] feat: add new config setting for beacon timeout --- etc/config.local.yaml | 2 +- eth_validator_watcher/beacon.py | 32 +++++++++---------- eth_validator_watcher/config.py | 1 + eth_validator_watcher/entrypoint.py | 4 ++- eth_validator_watcher/execution.py | 2 +- tests/beacon/test_get_block.py | 6 ++-- ..._to_committee_index_to_validators_index.py | 2 +- tests/beacon/test_get_genesis.py | 2 +- tests/beacon/test_get_header.py | 6 ++-- tests/beacon/test_get_proposer_duties.py | 2 +- tests/beacon/test_get_rewards.py | 4 +-- .../test_get_status_to_index_to_validator.py | 2 +- tests/beacon/test_get_validators_liveness.py | 12 +++---- tests/beacon/test_potential_block.py | 4 +-- tests/config/assets/config.null.yaml | 1 - tests/config/assets/config.yaml | 1 + tests/config/test_load_config.py | 5 +++ tests/entrypoint/test__handler.py | 11 +++++-- 18 files changed, 56 insertions(+), 43 deletions(-) diff --git a/etc/config.local.yaml b/etc/config.local.yaml index b5122d8..6f43721 100644 --- a/etc/config.local.yaml +++ b/etc/config.local.yaml @@ -1,7 +1,7 @@ # Example config file for the Ethereum watcher. beacon_url: http://localhost:5051/ -beacon_type: ~ +beacon_type: other execution_url: ~ web3signer_url: ~ default_fee_recipient: ~ diff --git a/eth_validator_watcher/beacon.py b/eth_validator_watcher/beacon.py index 13560bb..8fa9f1a 100644 --- a/eth_validator_watcher/beacon.py +++ b/eth_validator_watcher/beacon.py @@ -1,6 +1,5 @@ """Contains the Beacon class which is used to interact with the consensus layer node.""" - import functools from collections import defaultdict from functools import lru_cache @@ -26,11 +25,8 @@ ValidatorsLivenessResponse, ) -StatusEnum = Validators.DataItem.StatusEnum - -# Hard-coded for now, will need to move this to a config. -TIMEOUT_BEACON_SEC = 90 +StatusEnum = Validators.DataItem.StatusEnum print = functools.partial(print, flush=True) @@ -43,13 +39,15 @@ class NoBlockError(Exception): class Beacon: """Beacon node abstraction.""" - def __init__(self, url: str) -> None: + def __init__(self, url: str, timeout_sec: int) -> None: """Beacon Parameters: - url: URL where the beacon can be reached + url : URL where the beacon can be reached + timeout_sec: timeout in seconds used to query the beacon """ self.__url = url + self.__timeout_sec = timeout_sec self.__http_retry_not_found = Session() self.__http = Session() self.__first_liveness_call = True @@ -114,7 +112,7 @@ def __post_retry_not_found(self, *args: Any, **kwargs: Any) -> Response: def get_genesis(self) -> Genesis: """Get genesis data.""" response = self.__get_retry_not_found( - f"{self.__url}/eth/v1/beacon/genesis", timeout=TIMEOUT_BEACON_SEC + f"{self.__url}/eth/v1/beacon/genesis", timeout=self.__timeout_sec ) response.raise_for_status() genesis_dict = response.json() @@ -129,7 +127,7 @@ 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=self.__timeout_sec ) response.raise_for_status() @@ -153,7 +151,7 @@ def get_block(self, slot: int) -> Block: """ try: response = self.__get( - f"{self.__url}/eth/v2/beacon/blocks/{slot}", timeout=TIMEOUT_BEACON_SEC + f"{self.__url}/eth/v2/beacon/blocks/{slot}", timeout=self.__timeout_sec ) response.raise_for_status() @@ -176,7 +174,7 @@ 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=self.__timeout_sec ) response.raise_for_status() @@ -193,7 +191,7 @@ 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=self.__timeout_sec ) response.raise_for_status() @@ -226,7 +224,7 @@ def get_duty_slot_to_committee_index_to_validators_index( response = self.__get_retry_not_found( f"{self.__url}/eth/v1/beacon/states/head/committees", params=dict(epoch=epoch), - timeout=TIMEOUT_BEACON_SEC, + timeout=self.__timeout_sec, ) response.raise_for_status() @@ -289,7 +287,7 @@ def get_rewards( if validators_index is not None else [] ), - timeout=TIMEOUT_BEACON_SEC, + timeout=self.__timeout_sec, ) response.raise_for_status() @@ -390,7 +388,7 @@ def __get_validators_liveness_lighthouse( json=ValidatorsLivenessRequestLighthouse( epoch=epoch, indices=sorted(list(validators_index)) ).model_dump(), - timeout=TIMEOUT_BEACON_SEC, + timeout=self.__timeout_sec, ) def __get_validators_liveness_old_teku( @@ -410,7 +408,7 @@ def __get_validators_liveness_old_teku( json=ValidatorsLivenessRequestTeku( indices=sorted(list(validators_index)) ).model_dump(), - timeout=TIMEOUT_BEACON_SEC, + timeout=self.__timeout_sec, ) def __get_validators_liveness_beacon_api( @@ -431,5 +429,5 @@ def __get_validators_liveness_beacon_api( str(validator_index) for validator_index in sorted(list(validators_index)) ], - timeout=TIMEOUT_BEACON_SEC, + timeout=self.__timeout_sec, ) diff --git a/eth_validator_watcher/config.py b/eth_validator_watcher/config.py index c2a5612..e3b8073 100644 --- a/eth_validator_watcher/config.py +++ b/eth_validator_watcher/config.py @@ -22,6 +22,7 @@ class Config(BaseSettings): beacon_url: Optional[str] = None beacon_type: BeaconType = BeaconType.OTHER + beacon_timeout_sec: int = 90 execution_url: Optional[str] = None web3signer_url: Optional[str] = None default_fee_recipient: Optional[str] = None diff --git a/eth_validator_watcher/entrypoint.py b/eth_validator_watcher/entrypoint.py index 7dee7a9..0612487 100644 --- a/eth_validator_watcher/entrypoint.py +++ b/eth_validator_watcher/entrypoint.py @@ -145,6 +145,7 @@ def handler( try: # pragma: no cover _handler( cfg.beacon_url, + cfg.beacon_timeout_sec, cfg.execution_url, cfg.watched_keys, cfg.web3signer_url, @@ -161,6 +162,7 @@ def handler( def _handler( beacon_url: str, + beacon_timeout_sec: int, execution_url: str | None, watched_keys: List[WatchedKeyConfig] | None, web3signer_url: str | None, @@ -195,7 +197,7 @@ def _handler( else None ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, beacon_timeout_sec) execution = Execution(execution_url) if execution_url is not None else None coinbase = Coinbase() web3signer = Web3Signer(web3signer_url) if web3signer_url is not None else None diff --git a/eth_validator_watcher/execution.py b/eth_validator_watcher/execution.py index 476b6fe..18fc1ab 100644 --- a/eth_validator_watcher/execution.py +++ b/eth_validator_watcher/execution.py @@ -8,7 +8,7 @@ class Execution: - """Beacon node abstraction.""" + """Execution node abstraction.""" def __init__(self, url: str) -> None: """Execution node diff --git a/tests/beacon/test_get_block.py b/tests/beacon/test_get_block.py index 5400163..bb2888b 100644 --- a/tests/beacon/test_get_block.py +++ b/tests/beacon/test_get_block.py @@ -15,7 +15,7 @@ def test_get_block_exists() -> None: with block_path.open() as file_descriptor: block_dict = json.load(file_descriptor) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) with Mocker() as mock: mock.get( @@ -33,7 +33,7 @@ def get(url: str, **_) -> Response: raise HTTPError(response=response) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) beacon._Beacon__http.get = get # type: ignore with raises(NoBlockError): @@ -48,7 +48,7 @@ def get(url: str, **_) -> Response: raise HTTPError(response=response) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) beacon._Beacon__http.get = get # type: ignore with raises(exceptions.RequestException): diff --git a/tests/beacon/test_get_duty_slot_to_committee_index_to_validators_index.py b/tests/beacon/test_get_duty_slot_to_committee_index_to_validators_index.py index da30978..d7236de 100644 --- a/tests/beacon/test_get_duty_slot_to_committee_index_to_validators_index.py +++ b/tests/beacon/test_get_duty_slot_to_committee_index_to_validators_index.py @@ -26,7 +26,7 @@ def test_get_duty_slot_to_committee_index_to_validators_index(): json=committees, ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) assert ( beacon.get_duty_slot_to_committee_index_to_validators_index(epoch) diff --git a/tests/beacon/test_get_genesis.py b/tests/beacon/test_get_genesis.py index b3da996..5633e51 100644 --- a/tests/beacon/test_get_genesis.py +++ b/tests/beacon/test_get_genesis.py @@ -26,6 +26,6 @@ def test_get_genesis(): f"{beacon_url}/eth/v1/beacon/genesis", json=genesis, ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) assert beacon.get_genesis() == expected diff --git a/tests/beacon/test_get_header.py b/tests/beacon/test_get_header.py index 2f5f1c0..3008110 100644 --- a/tests/beacon/test_get_header.py +++ b/tests/beacon/test_get_header.py @@ -16,7 +16,7 @@ def test_get_header_exists() -> None: with block_path.open() as file_descriptor: header_dict = json.load(file_descriptor) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) for identifier, value in { "head": BlockIdentierType.HEAD, @@ -42,7 +42,7 @@ def get(url: str, **_) -> Response: raise HTTPError(response=response) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) beacon._Beacon__http.get = get # type: ignore with raises(NoBlockError): @@ -57,7 +57,7 @@ def get(url: str, **_) -> Response: raise HTTPError(response=response) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) beacon._Beacon__http.get = get # type: ignore with raises(exceptions.RequestException): diff --git a/tests/beacon/test_get_proposer_duties.py b/tests/beacon/test_get_proposer_duties.py index 3c986a1..d080729 100644 --- a/tests/beacon/test_get_proposer_duties.py +++ b/tests/beacon/test_get_proposer_duties.py @@ -42,6 +42,6 @@ def test_(): f"{beacon_url}/eth/v1/validator/duties/proposer/6542", json=proposer_duties ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) assert beacon.get_proposer_duties(6542) == expected diff --git a/tests/beacon/test_get_rewards.py b/tests/beacon/test_get_rewards.py index bb4beb6..3342ae8 100644 --- a/tests/beacon/test_get_rewards.py +++ b/tests/beacon/test_get_rewards.py @@ -9,7 +9,7 @@ def test_get_rewards_not_supported() -> None: - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) expected = Rewards(data=Rewards.Data(ideal_rewards=[], total_rewards=[])) @@ -26,7 +26,7 @@ def test_get_rewards() -> None: with rewards_path.open() as file_descriptor: rewards_dict = json.load(file_descriptor) - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) def match_request(request) -> bool: return request.json() == ["8499", "8500"] diff --git a/tests/beacon/test_get_status_to_index_to_validator.py b/tests/beacon/test_get_status_to_index_to_validator.py index 94fed63..13a1019 100644 --- a/tests/beacon/test_get_status_to_index_to_validator.py +++ b/tests/beacon/test_get_status_to_index_to_validator.py @@ -17,7 +17,7 @@ def test_get_status_to_index_to_validator() -> None: with asset_path.open() as file_descriptor: validators = json.load(file_descriptor) - beacon = Beacon("http://localhost:5052") + beacon = Beacon("http://localhost:5052", 90) expected = { StatusEnum.activeOngoing: { 0: Validator( diff --git a/tests/beacon/test_get_validators_liveness.py b/tests/beacon/test_get_validators_liveness.py index cb12d21..a3eca80 100644 --- a/tests/beacon/test_get_validators_liveness.py +++ b/tests/beacon/test_get_validators_liveness.py @@ -33,7 +33,7 @@ def match_request(request) -> bool: additional_matcher=match_request, json=liveness_response, ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) expected = {42: True, 44: False, 46: True} assert ( @@ -48,7 +48,7 @@ def match_request(request) -> bool: def test_get_validators_liveness_nimbus(): beacon_url = "http://beacon:5052" - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) assert beacon.get_validators_liveness( beacon_type=BeaconType.NIMBUS, epoch=1664, validators_index={42, 44, 46} @@ -76,7 +76,7 @@ def match_request(request) -> bool: additional_matcher=match_request, json=liveness_response, ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) expected = {42: True, 44: False, 46: True} assert ( @@ -113,7 +113,7 @@ def match_request(request) -> bool: additional_matcher=match_request, json=liveness_response, ) - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) expected = {42: True, 44: False, 46: True} assert ( @@ -134,7 +134,7 @@ def post(url: str, **_) -> Response: return response - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) beacon._Beacon__http_retry_not_found.post = post # type: ignore expected = {42: True, 44: True, 46: True} @@ -154,7 +154,7 @@ def post(url: str, **_) -> Response: return response - beacon = Beacon(beacon_url) + beacon = Beacon(beacon_url, 90) beacon._Beacon__http_retry_not_found.post = post # type: ignore with raises(HTTPError): diff --git a/tests/beacon/test_potential_block.py b/tests/beacon/test_potential_block.py index c75c108..8109bce 100644 --- a/tests/beacon/test_potential_block.py +++ b/tests/beacon/test_potential_block.py @@ -7,7 +7,7 @@ def get_block(slot: int) -> Block: assert slot == 42 return "a fake block" # type: ignore - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) beacon.get_block = get_block # type: ignore assert beacon.get_potential_block(42) == "a fake block" @@ -18,7 +18,7 @@ def get_block(slot: int) -> Block: assert slot == 42 raise NoBlockError - beacon = Beacon("http://beacon-node:5052") + beacon = Beacon("http://beacon-node:5052", 90) beacon.get_block = get_block # type: ignore assert beacon.get_potential_block(42) is None diff --git a/tests/config/assets/config.null.yaml b/tests/config/assets/config.null.yaml index 5d9fca1..7adb329 100644 --- a/tests/config/assets/config.null.yaml +++ b/tests/config/assets/config.null.yaml @@ -1,5 +1,4 @@ beacon_url: ~ -beacon_type: other execution_url: ~ web3signer_url: ~ default_fee_recipient: ~ diff --git a/tests/config/assets/config.yaml b/tests/config/assets/config.yaml index 45bfdc1..bdb309a 100644 --- a/tests/config/assets/config.yaml +++ b/tests/config/assets/config.yaml @@ -2,6 +2,7 @@ beacon_url: http://localhost:5051/ beacon_type: other +beacon_timeout_sec: 90 execution_url: http://localhost:8545/ web3signer_url: http://web3signer:9000/ default_fee_recipient: '0x41bF25fC8C52d292bD66D3BCEcd8a919ecB9EF88' diff --git a/tests/config/test_load_config.py b/tests/config/test_load_config.py index ddd8fcb..1711e5f 100644 --- a/tests/config/test_load_config.py +++ b/tests/config/test_load_config.py @@ -11,6 +11,7 @@ def test_null_config() -> None: config = load_config(path) assert config.beacon_url is None + assert config.beacon_timeout_sec == 90 assert config.execution_url is None assert config.web3signer_url is None assert config.default_fee_recipient is None @@ -27,6 +28,7 @@ def test_empty_config() -> None: config = load_config(path) assert config.beacon_url is None + assert config.beacon_timeout_sec == 90 assert config.execution_url is None assert config.web3signer_url is None assert config.default_fee_recipient is None @@ -43,6 +45,7 @@ def test_filled_config() -> None: config = load_config(path) assert config.beacon_url == 'http://localhost:5051/' + assert config.beacon_timeout_sec == 90 assert config.execution_url == 'http://localhost:8545/' assert config.web3signer_url == 'http://web3signer:9000/' assert config.default_fee_recipient == '0x41bF25fC8C52d292bD66D3BCEcd8a919ecB9EF88' @@ -59,6 +62,7 @@ def test_filled_config_overriden() -> None: environ = os.environ.copy() os.environ['eth_watcher_beacon_url'] = 'http://override-beacon/' + os.environ['eth_watcher_beacon_timeout_sec'] = '42' os.environ['eth_watcher_execution_url'] = 'http://override-exec/' os.environ['eth_watcher_web3signer_url'] = 'http://override-web3signer/' os.environ['eth_watcher_default_fee_recipient'] = '0x42' @@ -72,6 +76,7 @@ def test_filled_config_overriden() -> None: config = load_config(path) assert config.beacon_url == 'http://override-beacon/' + assert config.beacon_timeout_sec == 42 assert config.execution_url == 'http://override-exec/' assert config.web3signer_url == 'http://override-web3signer/' assert config.default_fee_recipient == '0x42' diff --git a/tests/entrypoint/test__handler.py b/tests/entrypoint/test__handler.py index b0f6d17..e0879c8 100644 --- a/tests/entrypoint/test__handler.py +++ b/tests/entrypoint/test__handler.py @@ -22,6 +22,7 @@ def test_fee_recipient_set_while_execution_url_not_set() -> None: with raises(BadParameter): _handler( beacon_url="", + beacon_timeout_sec=90, execution_url=None, watched_keys=None, web3signer_url=None, @@ -38,6 +39,7 @@ def test_fee_recipient_not_valid() -> None: with raises(BadParameter): _handler( beacon_url="", + beacon_timeout_sec=90, execution_url="http://localhost:8545", watched_keys=None, web3signer_url=None, @@ -54,6 +56,7 @@ def test_slack_token_not_defined() -> None: with raises(BadParameter): _handler( beacon_url="", + beacon_timeout_sec=90, execution_url=None, watched_keys=None, web3signer_url=None, @@ -69,8 +72,9 @@ def test_slack_token_not_defined() -> None: def test_chain_not_ready() -> None: class Beacon: - def __init__(self, url: str) -> None: + def __init__(self, url: str, timeout_sec: int) -> None: assert url == "http://localhost:5052" + assert timeout_sec == 90 def get_genesis(self) -> Genesis: return Genesis( @@ -105,6 +109,7 @@ def start_http_server(_: int) -> None: _handler( beacon_url="http://localhost:5052", + beacon_timeout_sec=90, execution_url=None, watched_keys=None, web3signer_url=None, @@ -120,8 +125,9 @@ def start_http_server(_: int) -> None: @freeze_time("2023-01-01 00:00:00", auto_tick_seconds=15) def test_nominal() -> None: class Beacon: - def __init__(self, url: str) -> None: + def __init__(self, url: str, timeout_sec: int) -> None: assert url == "http://localhost:5052" + assert timeout_sec == 90 def get_genesis(self) -> Genesis: return Genesis( @@ -325,6 +331,7 @@ def write_liveness_file(liveness_file: Path) -> None: _handler( beacon_url="http://localhost:5052", + beacon_timeout_sec=90, execution_url=None, watched_keys=[WatchedKeyConfig(public_key="0xfff")], web3signer_url="http://localhost:9000",