Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
1 change: 1 addition & 0 deletions docs/getting_started/code_standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
44 changes: 42 additions & 2 deletions src/ethereum_test_rpc/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
*,
Expand Down Expand Up @@ -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()

Expand Down
11 changes: 11 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading