From c0de9bc8068773031666dc083281f9dd69d11a3b Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 25 Sep 2025 08:43:17 +0200 Subject: [PATCH 1/4] chore(deps): add `tenacity` for retry logic --- pyproject.toml | 1 + uv.lock | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3c861811aa1..29af3d402c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ dependencies = [ "eth-abi>=5.2.0", "joblib>=1.4.2", "ckzg>=2.1.3,<3", + "tenacity>=9.0.0,<10", ] [project.urls] diff --git a/uv.lock b/uv.lock index b86978491aa..6f002e33df4 100644 --- a/uv.lock +++ b/uv.lock @@ -594,6 +594,7 @@ dependencies = [ { name = "requests-unixsocket2" }, { name = "rich" }, { name = "semver" }, + { name = "tenacity" }, { name = "trie" }, { name = "types-pyyaml" }, { name = "typing-extensions" }, @@ -679,6 +680,7 @@ requires-dist = [ { name = "ruff", marker = "extra == 'lint'", specifier = "==0.11.8" }, { name = "semver", specifier = ">=3.0.1,<4" }, { name = "setuptools", marker = "extra == 'docs'", specifier = "==78.0.2" }, + { name = "tenacity", specifier = ">=9.0.0,<10" }, { name = "trie", specifier = ">=3.1.0,<4" }, { name = "types-pyyaml", specifier = ">=6.0.12.20240917,<7" }, { name = "types-requests", marker = "extra == 'lint'", specifier = ">=2.31,<2.33" }, @@ -1899,6 +1901,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + [[package]] name = "tinycss2" version = "1.4.0" From 3bdc4b0ab7518160e2013ab8d68693e539e46da9 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 25 Sep 2025 08:44:47 +0200 Subject: [PATCH 2/4] feat(rpc): add rpc request retries with exp backoff --- src/ethereum_test_rpc/rpc.py | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/ethereum_test_rpc/rpc.py b/src/ethereum_test_rpc/rpc.py index a09055ba123..e654340c28f 100644 --- a/src/ethereum_test_rpc/rpc.py +++ b/src/ethereum_test_rpc/rpc.py @@ -2,6 +2,7 @@ JSON-RPC methods and helper functions for EEST consume based hive simulators. """ +import logging import time from itertools import count from pprint import pprint @@ -10,6 +11,13 @@ import requests from jwt import encode from pydantic import ValidationError +from tenacity import ( + before_sleep_log, + retry, + retry_if_exception_type, + stop_after_attempt, + wait_exponential, +) from ethereum_test_base_types import Address, Bytes, Hash, to_json from ethereum_test_types import Transaction @@ -88,6 +96,34 @@ def __init_subclass__(cls, namespace: str | None = None) -> None: namespace = namespace.lower() cls.namespace = namespace + @retry( + retry=retry_if_exception_type((requests.ConnectionError, ConnectionRefusedError)), + stop=stop_after_attempt(5), + wait=wait_exponential(multiplier=0.5, min=0.5, max=4.0), + before_sleep=before_sleep_log(logger, logging.WARNING), + reraise=True, + ) + def _make_request( + self, + url: str, + json_payload: dict, + headers: dict, + timeout: int | None, + ) -> requests.Response: + """ + Make HTTP POST request with retry logic for connection errors only. + + This method only retries network-level connection failures + (ConnectionError, ConnectionRefusedError). HTTP status errors (4xx/5xx) + are handled by the caller using response.raise_for_status() WITHOUT + retries because: + - 4xx errors are client errors (permanent failures, no point retrying) + - 5xx errors are server errors that typically indicate + application-level issues rather than transient network problems + """ + logger.debug(f"Making HTTP request to {url}, timeout={timeout}") + return requests.post(url, json=json_payload, headers=headers, timeout=timeout) + def post_request( self, *, @@ -123,8 +159,12 @@ def post_request( } headers = base_header | extra_headers - logger.debug(f"Sending RPC request, timeout is set to {timeout}...") - response = requests.post(self.url, json=payload, headers=headers, timeout=timeout) + logger.debug( + f"Sending RPC request to {self.url}, method={self.namespace}_{method}, " + f"timeout={timeout}..." + ) + + response = self._make_request(self.url, payload, headers, timeout) response.raise_for_status() response_json = response.json() From c674211e28411afb412b5c76c7c935d698976212 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 25 Sep 2025 09:01:03 +0200 Subject: [PATCH 3/4] docs: add retry logic via tenacity to coding standards --- docs/getting_started/code_standards.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/getting_started/code_standards.md b/docs/getting_started/code_standards.md index 0dc014eac79..924e81cf1ef 100644 --- a/docs/getting_started/code_standards.md +++ b/docs/getting_started/code_standards.md @@ -86,6 +86,7 @@ Code pushed to @ethereum/execution-spec-tests must fulfill the following checks - Use `PascalCase` for classes. - Use `UPPER_CASE` for constants. - **File Paths**: Strongly prefer `pathlib` over `os.path` for file system operations. +- **Retry Logic**: Use [`tenacity`](https://github.com/jd/tenacity) library for handling flaky network connections and transient failures. ## Editor Setup From f60e9d90732ad1cf6d0bec11110ca372b4de936a Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 25 Sep 2025 09:01:26 +0200 Subject: [PATCH 4/4] docs: add changelog --- docs/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 731dd139f58..032fe1bec6c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -21,6 +21,8 @@ Test fixtures for use by clients are available for each release on the [Github r #### `consume` +- ✨ Add retry logic to RPC requests to fix flaky connection issues in Hive ([#2205](https://github.com/ethereum/execution-spec-tests/pull/2205)). + ### 📋 Misc - ✨ Add tighter validation for EIP-7928 model coming from t8n when filling ([#2138](https://github.com/ethereum/execution-spec-tests/pull/2138)).