Skip to content

Commit e55fbce

Browse files
authored
feat(deps,rpc): add retry logic to fix flaky connection issues in hive (#2205)
* chore(deps): add `tenacity` for retry logic * feat(rpc): add rpc request retries with exp backoff * docs: add retry logic via tenacity to coding standards * docs: add changelog
1 parent 50cbabd commit e55fbce

File tree

5 files changed

+57
-2
lines changed

5 files changed

+57
-2
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Test fixtures for use by clients are available for each release on the [Github r
2121

2222
#### `consume`
2323

24+
- ✨ Add retry logic to RPC requests to fix flaky connection issues in Hive ([#2205](https://github.com/ethereum/execution-spec-tests/pull/2205)).
25+
2426
### 📋 Misc
2527

2628
- ✨ Add tighter validation for EIP-7928 model coming from t8n when filling ([#2138](https://github.com/ethereum/execution-spec-tests/pull/2138)).

docs/getting_started/code_standards.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Code pushed to @ethereum/execution-spec-tests must fulfill the following checks
8686
- Use `PascalCase` for classes.
8787
- Use `UPPER_CASE` for constants.
8888
- **File Paths**: Strongly prefer `pathlib` over `os.path` for file system operations.
89+
- **Retry Logic**: Use [`tenacity`](https://github.com/jd/tenacity) library for handling flaky network connections and transient failures.
8990

9091
## Editor Setup
9192

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies = [
4949
"eth-abi>=5.2.0",
5050
"joblib>=1.4.2",
5151
"ckzg>=2.1.3,<3",
52+
"tenacity>=9.0.0,<10",
5253
]
5354

5455
[project.urls]

src/ethereum_test_rpc/rpc.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
JSON-RPC methods and helper functions for EEST consume based hive simulators.
33
"""
44

5+
import logging
56
import time
67
from itertools import count
78
from pprint import pprint
@@ -10,6 +11,13 @@
1011
import requests
1112
from jwt import encode
1213
from pydantic import ValidationError
14+
from tenacity import (
15+
before_sleep_log,
16+
retry,
17+
retry_if_exception_type,
18+
stop_after_attempt,
19+
wait_exponential,
20+
)
1321

1422
from ethereum_test_base_types import Address, Bytes, Hash, to_json
1523
from ethereum_test_types import Transaction
@@ -88,6 +96,34 @@ def __init_subclass__(cls, namespace: str | None = None) -> None:
8896
namespace = namespace.lower()
8997
cls.namespace = namespace
9098

99+
@retry(
100+
retry=retry_if_exception_type((requests.ConnectionError, ConnectionRefusedError)),
101+
stop=stop_after_attempt(5),
102+
wait=wait_exponential(multiplier=0.5, min=0.5, max=4.0),
103+
before_sleep=before_sleep_log(logger, logging.WARNING),
104+
reraise=True,
105+
)
106+
def _make_request(
107+
self,
108+
url: str,
109+
json_payload: dict,
110+
headers: dict,
111+
timeout: int | None,
112+
) -> requests.Response:
113+
"""
114+
Make HTTP POST request with retry logic for connection errors only.
115+
116+
This method only retries network-level connection failures
117+
(ConnectionError, ConnectionRefusedError). HTTP status errors (4xx/5xx)
118+
are handled by the caller using response.raise_for_status() WITHOUT
119+
retries because:
120+
- 4xx errors are client errors (permanent failures, no point retrying)
121+
- 5xx errors are server errors that typically indicate
122+
application-level issues rather than transient network problems
123+
"""
124+
logger.debug(f"Making HTTP request to {url}, timeout={timeout}")
125+
return requests.post(url, json=json_payload, headers=headers, timeout=timeout)
126+
91127
def post_request(
92128
self,
93129
*,
@@ -123,8 +159,12 @@ def post_request(
123159
}
124160
headers = base_header | extra_headers
125161

126-
logger.debug(f"Sending RPC request, timeout is set to {timeout}...")
127-
response = requests.post(self.url, json=payload, headers=headers, timeout=timeout)
162+
logger.debug(
163+
f"Sending RPC request to {self.url}, method={self.namespace}_{method}, "
164+
f"timeout={timeout}..."
165+
)
166+
167+
response = self._make_request(self.url, payload, headers, timeout)
128168
response.raise_for_status()
129169
response_json = response.json()
130170

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)