Skip to content

RPC adapters silently return a successful null on rate-limit/gateway errors #19

Description

@Kingvic300

Summary

The Sui, Ethereum, Solana, and Soroban JSON-RPC adapters never check HTTP status before parsing a response. A rate-limit/gateway error whose body doesn't happen to be JSON-RPC-shaped (no error key) is silently converted into a "successful" null result, printed with exit code 0.

Background

call_rpc/call_rpc_internal in each of the four adapters call response.json().await? unconditionally and only check whether the parsed body has a top-level "error" key. reqwest::Response::json() succeeds regardless of the HTTP status code. A rate-limiting proxy or gateway in front of a public RPC endpoint commonly returns 429/5xx with a body that is not JSON-RPC-shaped (e.g. {"message":"rate limited"}). Because neither "error" nor "result" is present in that shape, the code falls through to Value::Null and reports success.

All five supported chains default to unauthenticated public nodes (api.mainnet-beta.solana.com, eth.llamarpc.com, fullnode.mainnet.sui.io, soroban-rpc.mainnet.stellar.org) — rate limiting under this pattern is routine, not an edge case.

Evidence

  • src/chains/sui.rs:60-93 (call_rpc_internal) — status never read; result defaults to Value::Null at line 92.
  • src/chains/ethereum.rs:55-81 (call_rpc) — same pattern, result at line 80.
  • src/chains/solana.rs:48-74 (call_rpc) — same pattern, result at line 73.
  • src/chains/soroban.rs:62-88 (call_rpc) — same pattern, result at line 87.
  • Downstream, src/cli/handlers.rs (Balance/Gas/Block/Tx/Call handlers, ~lines 444-691) take Ok(Value::Null) at face value — for Sui, result.as_array() on Null is None, which falls through to printing the literal null with exit code 0.

This is a distinct failure mechanism from open issue #2 (Aptos/Soroban REST calls ignoring HTTP status and displaying error bodies as data): #2 covers the two REST-based adapters and their message/error_code shape specifically. This issue covers the four JSON-RPC-over-HTTP adapters, whose failure mode is worse — a real error becomes an indistinguishable null "success" rather than visibly wrong data.

Proposed Solution

In each call_rpc/call_rpc_internal, capture response.status() before parsing. Treat any non-2xx response that doesn't parse into a recognizable JSON-RPC error object as a hard anyhow! error including the HTTP status code and a truncated raw body. Keep the existing 200-with-JSON-RPC-error-field handling exactly as is (spec-compliant case).

Technical Scope

  • src/chains/sui.rscall_rpc_internal
  • src/chains/ethereum.rscall_rpc
  • src/chains/solana.rscall_rpc
  • src/chains/soroban.rscall_rpc
  • Tests for each adapter

Acceptance Criteria

  • A non-2xx HTTP response is never silently converted to Ok(Value::Null) in any of the four adapters
  • Existing 200-with-JSON-RPC-error behavior is unchanged
  • A genuine "result": null on a 200 response (e.g. eth_getTransactionByHash for an unknown hash) still returns Ok(Value::Null)
  • Error output includes the HTTP status code for diagnosability

Edge Cases

  • 200 response with a JSON-RPC error object (existing, correct behavior — must not regress)
  • 200 response with a legitimate null result field
  • Non-2xx response with an empty body
  • Non-2xx response with a JSON-RPC-shaped error object (some gateways forward these with a non-2xx status)

Risks

  • Low regression risk — the change only tightens what currently falls through to Null; existing passing tests for the 200-with-error case should continue to pass unmodified

Deliverables

  • Status-aware error handling in all four adapters
  • Mock-server tests per chain (extending the existing pattern in src/chains/sui.rs, e.g. unresolvable_name_is_left_as_literal) simulating a 429 with a non-JSON-RPC body and asserting call_rpc returns Err

Priority

High — affects the primary command surface (balance, tx, gas, block, raw call) on four of five supported chains, silently breaks scripting/automation that checks exit codes, and is trivial to trigger against the default public endpoints.


GrantFox Evaluation

Impact Score: 78/100
Difficulty Score: 35/100
Priority Score: 64
Confidence: 82%

Category: Reliability / Bug

Estimated Reward Tier: B

AI Rationale: A well-evidenced correctness bug in the shared RPC layer, reachable via routine rate-limiting on default public endpoints, that silently returns a fake successful result rather than an error. Fix is well-scoped to four adapter files with a clear, testable acceptance boundary.


Estimated Completion: 96 hours

Telegram: https://t.me/txioCommunity

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 rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignbugSomething isn't workingpriority:highShould be next

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions