mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-09-30 20:46:32 +00:00
api: the bundler is off unless BUNDLER_ENABLED; only then is BUNDLER_PRIVATE_KEY required
Hanzo CI/CD / test (linux-amd64) (push) Successful in 2m24s
Hanzo CI/CD / test (linux-arm64) (push) Successful in 6m31s
Hanzo CI/CD / cicd (push) Failing after 42m45s
CI/CD / cicd (push) Failing after 1h23m53s
Hanzo CI/CD / test (linux-amd64) (push) Successful in 2m24s
Hanzo CI/CD / test (linux-arm64) (push) Successful in 6m31s
Hanzo CI/CD / cicd (push) Failing after 42m45s
CI/CD / cicd (push) Failing after 1h23m53s
Off, every /v1/bundler route answers 503. JWT_SECRET and API_KEY_SALT stay required in production. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -2,13 +2,24 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
from bootnode.api.deps import ApiKeyDep
|
||||
from bootnode.config import get_settings
|
||||
from bootnode.core.chains import ChainRegistry, RPCClient
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def require_bundler() -> None:
|
||||
"""Refuse every bundler call with 503 unless BUNDLER_ENABLED is set."""
|
||||
if not get_settings().bundler_enabled:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="ERC-4337 bundler is disabled on this deployment",
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(dependencies=[Depends(require_bundler)])
|
||||
|
||||
|
||||
# EntryPoint addresses
|
||||
|
||||
@@ -136,7 +136,8 @@ class Settings(BaseSettings):
|
||||
validation_alias=AliasChoices("commerce_webhook_secret", "hanzo_commerce_webhook_secret"),
|
||||
)
|
||||
|
||||
# ERC-4337 Bundler
|
||||
# ERC-4337 Bundler: off unless BUNDLER_ENABLED; on, it needs BUNDLER_PRIVATE_KEY
|
||||
bundler_enabled: bool = False
|
||||
bundler_private_key: str = ""
|
||||
bundler_beneficiary: str = ""
|
||||
|
||||
@@ -173,7 +174,7 @@ class Settings(BaseSettings):
|
||||
insecure.append("JWT_SECRET")
|
||||
if self.api_key_salt == "change-me-in-production":
|
||||
insecure.append("API_KEY_SALT")
|
||||
if self.bundler_private_key == "":
|
||||
if self.bundler_enabled and self.bundler_private_key == "":
|
||||
insecure.append("BUNDLER_PRIVATE_KEY")
|
||||
if insecure:
|
||||
raise RuntimeError(
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""The ERC-4337 bundler is off unless BUNDLER_ENABLED, and only then needs a key."""
|
||||
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from bootnode.config import Settings, get_settings
|
||||
from bootnode.main import app
|
||||
|
||||
BODY = {"jsonrpc": "2.0", "method": "eth_supportedEntryPoints", "params": [], "id": 1}
|
||||
|
||||
|
||||
def production(**kw) -> Settings:
|
||||
return Settings(_env_file=None, app_env="production", **kw)
|
||||
|
||||
|
||||
def test_production_starts_without_bundler_key_when_bundler_off():
|
||||
production(jwt_secret="j", api_key_salt="s").validate_production()
|
||||
|
||||
|
||||
def test_production_requires_bundler_key_when_bundler_on():
|
||||
s = production(jwt_secret="j", api_key_salt="s", bundler_enabled=True)
|
||||
with pytest.raises(RuntimeError, match=r"secrets: BUNDLER_PRIVATE_KEY$"):
|
||||
s.validate_production()
|
||||
|
||||
|
||||
def test_production_accepts_bundler_on_with_key():
|
||||
production(
|
||||
jwt_secret="j", api_key_salt="s", bundler_enabled=True, bundler_private_key="0x1"
|
||||
).validate_production()
|
||||
|
||||
|
||||
def test_production_still_requires_jwt_secret_and_api_key_salt():
|
||||
with pytest.raises(RuntimeError, match=r"secrets: JWT_SECRET, API_KEY_SALT$"):
|
||||
production().validate_production()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client():
|
||||
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
|
||||
yield ac
|
||||
|
||||
|
||||
async def test_bundler_route_is_503_when_off(client: AsyncClient):
|
||||
assert get_settings().bundler_enabled is False
|
||||
for path in ("/v1/bundler/ethereum", "/v1/bundler/ethereum/sepolia"):
|
||||
r = await client.post(path, json=BODY)
|
||||
assert r.status_code == 503
|
||||
assert r.json()["detail"] == "ERC-4337 bundler is disabled on this deployment"
|
||||
|
||||
|
||||
async def test_bundler_route_reaches_auth_when_on(client: AsyncClient, monkeypatch):
|
||||
monkeypatch.setattr(get_settings(), "bundler_enabled", True)
|
||||
r = await client.post("/v1/bundler/ethereum", json=BODY)
|
||||
assert r.status_code == 401
|
||||
@@ -58,6 +58,8 @@ BTC_RPC_PASSWORD=bootnode_btc_secret
|
||||
# =============================================================================
|
||||
# ERC-4337 BUNDLER (Optional)
|
||||
# =============================================================================
|
||||
# Off by default: /v1/bundler answers 503. Enabling it requires BUNDLER_PRIVATE_KEY.
|
||||
# BUNDLER_ENABLED=true
|
||||
# Private key for bundler to submit UserOperations
|
||||
# BUNDLER_PRIVATE_KEY=0x...
|
||||
|
||||
|
||||
Reference in New Issue
Block a user