You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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_sendRawTransactionmust 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.
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.
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.
Depends on: #217. Blocks: #219, #221, #225.
Description
Octo talks to Stellar through two purpose-built clients —
crates/ingest/src/horizon.rsfor deposit polling andcrates/api/src/horizon.rsfor API-side reads/submits — bothwrapped in
octo-resilience. EVM needs the equivalent overJSON-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
eth_call,eth_getLogs,eth_blockNumber,eth_getTransactionReceiptareCallKind::Readand retryable.eth_sendRawTransactionmust not be transparently retried — see the header comment incrates/ingest/src/horizon.rsfor the established reasoning.errormember. Aclient 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.
eth_getLogsblock-range caps (Alchemy 2k, Infura 10k,others unbounded), rate-limit responses, and inconsistent revert-data encoding. The client must
surface a typed
RangeTooLargeso feat(ingest): EVM ingest worker — ERC-20 Transfer log scanning #221 can adaptively bisect rather than stall.0x-prefixed, minimal-length). Parse into theU256type chosen in feat(evm-core): secp256k1 keys, BIP-44 derivation, and EIP-55 addresses #217 — never into
u64orf64.a malicious or compromised RPC returning a multi-GB response must not OOM the worker.
Suggested execution
Branch:
feat/evm-rpc-clientImplement changes
crates/evm-rpcusing the workspacereqwest(alreadyrustls-tls, no OpenSSL).eth_blockNumber,eth_getBlockByNumber,eth_getLogs,eth_getTransactionReceipt,eth_getTransactionCount,eth_call,eth_estimateGas,eth_feeHistory,eth_chainId,eth_sendRawTransaction.execute(CallKind::Read, ..)with per-chainRetryPolicy/CircuitBreakerfrom feat(config): Per-chain configuration and runtime chain registry #216 for readsonly; document at the call site why the submit path is excluded.
eth_chainIdmatches the configured CAIP-2 chain id — amisconfigured RPC pointing at the wrong chain is a fund-loss bug, and this one-line check
prevents it.
Transport,JsonRpc { code, message },Revert { data },RangeTooLarge,RateLimited { retry_after },CircuitOpen.Test and commit
crates/ingest/tests/horizon_mock_tests.rsandcrates/api/tests/horizon_resilience_tests.rs.errormember is treated as an error, not success.failure_threshold.eth_sendRawTransactionis called exactly once on failure — no silent retry.0x0, values aboveu64::MAX, malformed input, and a response bodyexceeding the size cap.
Example commit message
Guidelines
Do not pull in a full EVM SDK for this. A focused client keeps the dependency audit small and the
cargo denysurface reviewable.