Skip to content

feat(evm-rpc): Resilient JSON-RPC client for EVM chains #218

Description

@Emmyt24

Depends on: #217. Blocks: #219, #221, #225.

Description

Octo talks to Stellar through two purpose-built clients —
crates/ingest/src/horizon.rs for deposit polling and
crates/api/src/horizon.rs for API-side reads/submits — both
wrapped in octo-resilience. EVM needs the equivalent over
JSON-RPC 2.0.

Build crates/evm-rpc (octo-evm-rpc) as a typed client covering the methods this epic needs,
with the same resilience posture.

Requirements and context

  • Honour the submit-asymmetry rule (AD-5). eth_call, eth_getLogs, eth_blockNumber,
    eth_getTransactionReceipt are CallKind::Read and retryable.
    eth_sendRawTransaction must not be transparently retried — see the header comment in
    crates/ingest/src/horizon.rs for the established reasoning.
  • JSON-RPC error handling is a trap. A JSON-RPC error is HTTP 200 with an error member. A
    client that only checks HTTP status will treat every failure as success. Handle the error object
    explicitly and distinguish transport failure from protocol error from execution revert.
  • Providers differ in real, breaking ways: eth_getLogs block-range caps (Alchemy 2k, Infura 10k,
    others unbounded), rate-limit responses, and inconsistent revert-data encoding. The client must
    surface a typed RangeTooLarge so feat(ingest): EVM ingest worker — ERC-20 Transfer log scanning #221 can adaptively bisect rather than stall.
  • All quantities are hex-encoded strings (0x-prefixed, minimal-length). Parse into the U256
    type chosen in feat(evm-core): secp256k1 keys, BIP-44 derivation, and EIP-55 addresses #217never into u64 or f64.
  • Security: RPC URLs contain API keys — never log the URL (see feat(config): Per-chain configuration and runtime chain registry #216). Cap response body size;
    a malicious or compromised RPC returning a multi-GB response must not OOM the worker.

Suggested execution

Branch: feat/evm-rpc-client

Implement changes

  • Add crates/evm-rpc using the workspace reqwest (already rustls-tls, no OpenSSL).
  • Implement typed wrappers: eth_blockNumber, eth_getBlockByNumber, eth_getLogs,
    eth_getTransactionReceipt, eth_getTransactionCount, eth_call, eth_estimateGas,
    eth_feeHistory, eth_chainId, eth_sendRawTransaction.
  • Wire execute(CallKind::Read, ..) with per-chain RetryPolicy/CircuitBreaker from feat(config): Per-chain configuration and runtime chain registry #216 for reads
    only; document at the call site why the submit path is excluded.
  • Add a startup assertion that eth_chainId matches the configured CAIP-2 chain id — a
    misconfigured RPC pointing at the wrong chain is a fund-loss bug
    , and this one-line check
    prevents it.
  • Typed errors: Transport, JsonRpc { code, message }, Revert { data }, RangeTooLarge,
    RateLimited { retry_after }, CircuitOpen.

Test and commit

  • Mock-server tests in the style of
    crates/ingest/tests/horizon_mock_tests.rs and
    crates/api/tests/horizon_resilience_tests.rs.
  • A test that HTTP 200 with a JSON-RPC error member is treated as an error, not success.
  • Test retry/backoff on 5xx and the circuit opening after failure_threshold.
  • A test asserting eth_sendRawTransaction is called exactly once on failure — no silent retry.
  • Test hex parsing edge cases: 0x0, values above u64::MAX, malformed input, and a response body
    exceeding the size cap.
  • Test the chain-id mismatch guard rejects at startup.

Example commit message

feat(evm-rpc): typed JSON-RPC client with per-chain resilience

Reads go through octo-resilience retry + circuit breaker;
eth_sendRawTransaction deliberately does not, preserving the
submit-asymmetry rule.

JSON-RPC errors arrive as HTTP 200 with an error member, so status-only
checking would read every failure as success — handled explicitly.

Startup asserts eth_chainId matches the configured CAIP-2 id; an RPC
pointed at the wrong chain is a fund-loss bug.

Refs #218

Guidelines

Do not pull in a full EVM SDK for this. A focused client keeps the dependency audit small and the
cargo deny surface reviewable.


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea/backendBackend crates: api, store, ingest, webhooks, bin/serverdifficulty/mediumMedium difficultytype/epicTracking issue for a multi-issue epic

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions