Skip to content

fix: harden HTTP/RPC response handling and gas-fee fallback - #218

Merged
Sednaoui merged 6 commits into
devfrom
fix/transport-rpc-response-handling
Jul 26, 2026
Merged

fix: harden HTTP/RPC response handling and gas-fee fallback#218
Sednaoui merged 6 commits into
devfrom
fix/transport-rpc-response-handling

Conversation

@sherifahmed990

@sherifahmed990 sherifahmed990 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Fixes a set of failure-masking defects in the transport layer, found during the audit sweep.

  • HTTP errors surfaced as opaque SyntaxErrors: HttpTransport.send never checked the HTTP status and unconditionally parsed JSON, so a 401/429/502 with an HTML or plain-text body lost the status — the actual diagnostic. Non-JSON and non-envelope error bodies now throw a TransportRpcError carrying the status; proper JSON-RPC error envelopes still report the server's error. sendJsonRpcRequest's URL path had the same gap.
  • error: null crashed parsing: BaseRpcTransport.parseResponse used a key-presence check, so a successful response carrying error: null (sent by some non-strict servers) crashed destructuring null instead of returning the result.
  • Scalar/null JSON payloads threw a raw TypeError: sendJsonRpcRequest applied the in operator to primitive bodies; they now produce a malformed-response TransportRpcError.
  • Priority fee dropped when eth_gasPrice is unsupported: getFeeData's fallback ladder discarded a successfully fetched maxPriorityFeePerGas and pinned both fees to the 1-gwei floor, below the chain's actual required tip. The fetched priority fee is now kept, with the existing clamp raising maxFeePerGas to match.

Tests added in test/transport/httpErrorHandling.test.js and test/transport/HttpTransport.test.js; full offline suite passes.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed JSON-RPC handling for responses that include error: null, preventing incorrect failure detection.
    • Strengthened HTTP/JSON-RPC parsing: responses are read as text, non-JSON bodies now report HTTP status, malformed payloads are flagged as such, and HTTP failures ignore any result.
    • Preserved JSON-RPC code/message from valid error envelopes and improved robustness for unexpected error shapes.
    • Refined fee estimation when only priority-fee data is available.
  • Tests

    • Updated HTTP mocks and added offline Jest tests covering error: null, malformed payloads, and HTTP/non-envelope failure combinations.

sherifahmed990 and others added 3 commits July 22, 2026 12:39
…ling

Three related failure-masking defects:
- BaseRpcTransport.parseResponse used a key-presence check, so a
  successful response carrying 'error': null (sent by some non-strict
  servers) crashed destructuring null instead of returning the result.
- HttpTransport.send never checked the HTTP status and unconditionally
  parsed JSON, so a 401/429/502 with an HTML or plain-text body
  surfaced as an opaque SyntaxError with the status — the actual
  diagnostic — lost. Non-JSON and non-envelope error bodies now throw a
  TransportRpcError carrying the status; proper JSON-RPC error
  envelopes still report the server's error.
- sendJsonRpcRequest's URL path had the same gap plus a TypeError when
  the body had neither result nor error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
getFeeData's fallback ladder dropped a successfully fetched
maxPriorityFeePerGas into the 1-gwei-floor branch whenever eth_gasPrice
was unsupported, pinning both fees below the chain's actual required tip
(and contradicting the documented fallback order). The priority fee is
now used, with the existing clamp raising maxFeePerGas to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A scalar or null JSON body slipped past the envelope key checks and
threw a raw TypeError ('in' operator on a primitive) instead of a
malformed-response TransportRpcError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Transport handling now parses response bodies defensively, accepts error: null success envelopes, validates malformed payloads, and reports HTTP failures consistently. Fee calculation also gains a fallback for nodes exposing only priority-fee data.

Changes

Transport response robustness

Layer / File(s) Summary
Response envelope and HTTP parsing
src/transport/BaseRpcTransport.ts, src/transport/HttpTransport.ts, test/transport/HttpTransport.test.js, test/transport/httpErrorHandling.test.js
Nullable JSON-RPC errors are accepted on successful responses; HTTP bodies are parsed from text and malformed or non-envelope failures produce TransportRpcError.
Fetch request validation
src/utils.ts, test/safe/isDeployed.test.js, test/transport/httpErrorHandling.test.js
sendJsonRpcRequest rejects non-object and malformed payloads while tests cover non-JSON, scalar, null, and JSON-RPC error responses.

Fee fallback calculation

Layer / File(s) Summary
Priority-fee fallback
src/transport/JsonRpcNode.ts
getFeeData derives priorityFee from eth_maxPriorityFeePerGas and maxFeePerGas from the one-gwei floor when gas price is unavailable.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HttpTransport
  participant fetch
  participant JSON.parse
  participant TransportRpcError
  HttpTransport->>fetch: Send request
  fetch-->>HttpTransport: Return status and text body
  HttpTransport->>JSON.parse: Parse text body
  JSON.parse-->>HttpTransport: Return payload or parse failure
  HttpTransport->>TransportRpcError: Report malformed or HTTP failure
Loading

Possibly related PRs

Poem

I’m a rabbit guarding the wire,
Parsing each body to quell the fire.
Null errors hop safely by,
Bad JSON gets a clear reply.
Fees find their fallback trail—
A tidy transport, ears held high!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main changes: HTTP/RPC response handling and fee fallback hardening.
Description check ✅ Passed The description covers summary, tests, and compatibility concerns, though it does not use the exact template headings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/transport/JsonRpcNode.ts (1)

299-304: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the priority-only fallback.

The supplied test/transport/JsonRpcNode.test.js coverage exercises both methods being supported and neither being supported, but not eth_gasPrice returning -32601 while eth_maxPriorityFeePerGas succeeds. Add cases where the priority fee is below and above the scaled 1 gwei floor to lock down gas-level scaling and the maxFeePerGas >= priorityFee clamp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transport/JsonRpcNode.ts` around lines 299 - 304, The existing
JsonRpcNode tests lack coverage for the priority-only fallback when eth_gasPrice
returns -32601 and eth_maxPriorityFeePerGas succeeds. Extend
test/transport/JsonRpcNode.test.js with cases where the returned priority fee is
below and above the gas-level-scaled 1 gwei floor, asserting scaling and that
maxFeePerGas is at least priorityFee.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/transport/BaseRpcTransport.ts`:
- Around line 101-103: Validate the JSON-RPC error envelope before destructuring
in BaseRpcTransport.ts lines 101-103: require error to be an object with numeric
code and string message, otherwise return the existing malformed-response error.
Extend the object guard in src/utils.ts lines 572-580 to validate err.code and
err.message, and add transport tests covering string and incomplete error
payloads.

---

Nitpick comments:
In `@src/transport/JsonRpcNode.ts`:
- Around line 299-304: The existing JsonRpcNode tests lack coverage for the
priority-only fallback when eth_gasPrice returns -32601 and
eth_maxPriorityFeePerGas succeeds. Extend test/transport/JsonRpcNode.test.js
with cases where the returned priority fee is below and above the
gas-level-scaled 1 gwei floor, asserting scaling and that maxFeePerGas is at
least priorityFee.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74989980-3c36-43ac-aa2d-73e6a13ef110

📥 Commits

Reviewing files that changed from the base of the PR and between a4f98c7 and 8de4d1b.

📒 Files selected for processing (7)
  • src/transport/BaseRpcTransport.ts
  • src/transport/HttpTransport.ts
  • src/transport/JsonRpcNode.ts
  • src/utils.ts
  • test/safe/isDeployed.test.js
  • test/transport/HttpTransport.test.js
  • test/transport/httpErrorHandling.test.js

Comment on lines +101 to +103
// null check, not key presence — some servers include "error": null
// on successful responses
if (response.error != null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate JSON-RPC error fields at runtime.

Untrusted payloads such as {"error":"bad"} or {"error":{}} can produce a TransportRpcError with undefined code/message rather than the required malformed-response error.

  • src/transport/BaseRpcTransport.ts#L101-L103: validate that error is an object with numeric code and string message before destructuring.
  • src/utils.ts#L572-L580: extend the existing object guard to validate err.code and err.message; add malformed-error-envelope cases to the transport tests.
📍 Affects 2 files
  • src/transport/BaseRpcTransport.ts#L101-L103 (this comment)
  • src/utils.ts#L572-L580
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transport/BaseRpcTransport.ts` around lines 101 - 103, Validate the
JSON-RPC error envelope before destructuring in BaseRpcTransport.ts lines
101-103: require error to be an object with numeric code and string message,
otherwise return the existing malformed-response error. Extend the object guard
in src/utils.ts lines 572-580 to validate err.code and err.message, and add
transport tests covering string and incomplete error payloads.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sherifahmed990 this is valid imo

Comment thread src/transport/HttpTransport.ts Outdated
Comment on lines +101 to +103
// null check, not key presence — some servers include "error": null
// on successful responses
if (response.error != null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sherifahmed990 this is valid imo

sherifahmed990 and others added 3 commits July 25, 2026 18:35
A non-2xx status with a "result" body is contradictory (e.g. a gateway
returning 401 with a stub payload) and was resolved as a success by both
HttpTransport and sendJsonRpcRequest. On HTTP failure, only a non-null
JSON-RPC error envelope now falls through (preserving server errors like
429 rate limits); everything else surfaces the HTTP status. Also keeps
the status for 401 + error:null bodies instead of a generic malformed
error.

Reported by @Sednaoui in #218 review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…port

A non-object error field (e.g. a bare string) previously destructured
into TransportRpcError(undefined, undefined), losing the payload. It now
falls through to the malformed-response error carrying the raw response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sherifahmed990
sherifahmed990 requested a review from Sednaoui July 25, 2026 16:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/transport/HttpTransport.test.js`:
- Around line 98-109: Add an array-valued error payload regression test
alongside the existing malformed-response test, then update BaseRpcTransport’s
JSON-RPC error parsing validation to require a non-null, non-array object before
destructuring or creating TransportRpcError; arrays must follow the
malformed-response error path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: accbffee-56d0-4b21-8d78-749f2b91d71d

📥 Commits

Reviewing files that changed from the base of the PR and between 22bf90e and d59d6b7.

📒 Files selected for processing (2)
  • src/transport/BaseRpcTransport.ts
  • test/transport/HttpTransport.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/transport/BaseRpcTransport.ts

Comment on lines +98 to +109
test("treats a non-object error payload as a malformed response", async () => {
const fetch = makeMockFetch(() =>
jsonResponse({ jsonrpc: "2.0", id: 1, error: "rate limited" }),
);
const t = new ak.HttpTransport("https://example.test/rpc", { fetch });

await expect(t.request({ method: "eth_chainId" })).rejects.toMatchObject({
name: "TransportRpcError",
code: -32603,
data: { error: "rate limited" },
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Cover array-valued error payloads.

typeof [] === "object", so BaseRpcTransport can still destructure an array as an error and produce a TransportRpcError with undefined fields instead of the malformed-response error. Add an array regression case and tighten the parser’s JSON-RPC error-object validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/transport/HttpTransport.test.js` around lines 98 - 109, Add an
array-valued error payload regression test alongside the existing
malformed-response test, then update BaseRpcTransport’s JSON-RPC error parsing
validation to require a non-null, non-array object before destructuring or
creating TransportRpcError; arrays must follow the malformed-response error
path.

@Sednaoui
Sednaoui merged commit bcd30fb into dev Jul 26, 2026
3 checks passed
@Sednaoui
Sednaoui deleted the fix/transport-rpc-response-handling branch July 26, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants