Skip to content

fix(rpc): eth_call accepts any block tag; empty logsBloom is now 256 bytes#707

Merged
github-actions[bot] merged 2 commits into
mainfrom
fix/rpc-eth-call-and-logsbloom
May 22, 2026
Merged

fix(rpc): eth_call accepts any block tag; empty logsBloom is now 256 bytes#707
github-actions[bot] merged 2 commits into
mainfrom
fix/rpc-eth-call-and-logsbloom

Conversation

@satyakwok
Copy link
Copy Markdown
Collaborator

@satyakwok satyakwok commented May 22, 2026

Follow-up to #704. Verifying the eth_getTransactionCount fix on Sentrix Testnet surfaced two more chain-side blockers that stopped the Hyperlane relayer from auto-submitting process() against the Sentrix Mailbox. Both fixed here.

1) eth_call rejected historical block tags

eth_call still went through the strict historical-state gate. Hyperlane queries Mailbox.delivered(msgId) and recipientIsm(addr) at a recent past block as routine bookkeeping — the gate returned -32004, the relayer's FallbackProvider deprioritized the host, no message ever got submitted.

Fix: drop the gate for eth_call, same rationale as eth_getTransactionCount in #704. The strict gate stays on eth_getBalance / eth_getCode / eth_getStorageAt where a silently-stale answer would let a caller make a wrong protocol decision on data that purports to be historical.

2) EMPTY_LOGS_BLOOM was 304 bytes, not 256

The constant literal had 608 hex chars after 0x (= 304 bytes). The doc comment said "256-byte logs bloom" but the hand-typed literal was wrong. ethers' fee-oracle middleware strict-parses Block.logsBloom against the Ethereum spec — the relayer's gas-estimation path hit SerdeJson("invalid length 608, expected 256 bytes") on every attempt to estimate process() gas.

Fix: shrink the constant to exactly 512 hex chars (256 bytes per spec).

Verified live on testnet

  • eth_call(Mailbox.delivered, blockN) returns current state instead of -32004
  • Block.logsBloom length is 514 chars (256 bytes)
  • Two consecutive 0.1 USDC bridges auto-relayed Base Sepolia -> Sentrix Testnet end-to-end with no manual cast send

Test plan

  • cargo check -p sentrix-rpc --release passes with -D warnings
  • All 10 require_latest_state_read_* tests still pass (gate unchanged for the three remaining methods)
  • Live end-to-end auto-relay on testnet
  • Mainnet pickup as 2.2.15

Summary by CodeRabbit

  • Bug Fixes

    • Resolved logs bloom filter to meet Ethereum specifications, improving compatibility with stricter JSON-RPC clients.
  • Improvements

    • eth_call RPC method now accepts historical block tags for more flexible queries.
  • Documentation

    • Clarified state read gating behavior in module documentation.

Review Change Stack

satyakwok added 2 commits May 21, 2026 18:21
Three discrete chain-RPC fixes bundled because they share one cause
(strict-but-non-spec behavior breaking off-the-shelf EVM agents). All
discovered together standing up the Hyperlane relayer for the Base
Sepolia ↔ Sentrix Testnet bridge (audit fix H-4).

1) eth_getTransactionCount accepts any block tag.
   Relayer + ethers + viem all pin nonce queries to a recent past
   block. A stale nonce is self-correcting (chain rejects wrong-nonce
   tx, caller retries) so this method serves current nonce regardless
   of block tag.

2) eth_call accepts any block tag.
   Same pattern: Hyperlane queries Mailbox.delivered(msgId) and
   recipientIsm(addr) at past blocks. Returns current-state. The strict
   gate stays on eth_getBalance / eth_getCode / eth_getStorageAt where
   wrong = wrong protocol decision.

3) Empty logsBloom is now actually 256 bytes (Ethereum spec).
   The EMPTY_LOGS_BLOOM const was 304 bytes (608 hex chars) because of
   an off-by-one in the original hand-typed string. The doc comment
   said 256 but the literal was 304. ethers' fee-oracle middleware
   strict-parses Block.logsBloom — Hyperlane gas estimation
   SerdeJson'd on this with "invalid length 608, expected 256 bytes"
   before any process() tx could be submitted.

Bumps workspace 2.2.13 -> 2.2.14.
2.2.14 went live on mainnet 2026-05-21 17:02 UTC carrying PRs
#692/#696/#702/#703/#704. Document the five fixes + flag that
the eth_call + logsBloom fixes on this branch are scheduled
for 2.2.15.
@github-actions github-actions Bot enabled auto-merge (squash) May 22, 2026 06:38
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

The PR corrects the EMPTY_LOGS_BLOOM constant to a 256-byte Ethereum-spec-compliant zero bloom value, fixing a prior incorrect-length definition. It removes the require_latest_state_read enforcement gate from the eth_call JSON-RPC handler, changing execution to always use current chain state regardless of the provided block tag. The corresponding module-level documentation in helpers.rs is updated to reflect that both eth_call and eth_getTransactionCount are no longer subject to the historical-state read restriction.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • sentrix-labs/sentrix#704: Removes the historical-state read gate from eth_getTransactionCount with similar documentation updates to helpers.rs.
  • sentrix-labs/sentrix#705: Implements the identical eth.rs changes—fixing EMPTY_LOGS_BLOOM and removing the require_latest_state_read gate from eth_call—plus corresponding documentation updates.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the main changes, root causes, fixes, and verification; however, the Deploy impact section required by the template is missing. Complete the Deploy impact checklist section to clarify whether this is a backwards-compatible change or requires a version bump and redeploy.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: removing the historical block-tag gate for eth_call and correcting EMPTY_LOGS_BLOOM to the spec-compliant 256-byte size.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rpc-eth-call-and-logsbloom

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 `@crates/sentrix-rpc/src/jsonrpc/eth.rs`:
- Around line 964-987: The eth_call handler currently ignores params[1] (block
tag); add basic validation like eth_getBlockByNumber: in the eth_call handling
function (the eth_call RPC handler that reads params[1]) parse/validate
params[1] when present — accept the canonical tags
("latest","earliest","pending") or a hex-prefixed block number (validate hex
digits and length) and reject other JSON types (objects/arrays) or malformed hex
by returning an RPC invalid-params error (-32602); do not change the existing
behavior of executing against tip when the tag is valid but historical, only
surface errors for malformed or wrong-typed block-tag inputs.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c2888514-ab8e-4fe6-8d51-21d227415ec3

📥 Commits

Reviewing files that changed from the base of the PR and between ab3d349 and dcc56ce.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !**/CHANGELOG.md
📒 Files selected for processing (2)
  • crates/sentrix-rpc/src/jsonrpc/eth.rs
  • crates/sentrix-rpc/src/jsonrpc/helpers.rs

Comment on lines +964 to +987
// params[1] = block tag.
//
// 2026-05-21: dropped the strict historical-state gate here for
// the same reason as eth_getTransactionCount. Off-the-shelf
// EVM agents (Hyperlane relayer, ethers, viem) pin eth_call to
// a recent past block as routine bookkeeping, even when the
// underlying view function only has meaning against tip
// (Mailbox.delivered, ERC20.totalSupply at finality, etc.).
// Returning -32004 here kills every off-the-shelf integration.
//
// The trade-off: callers asking for "balanceOf(x) at h=N" get
// current balance instead of historical. The agent ecosystem
// already accounts for this by reading from a deterministic
// current-state view of the chain. Use eth_getBalance /
// eth_getCode / eth_getStorageAt for explicit state-read
// pinning — those keep the strict gate so a wallet asking
// "what was my balance at h=N" gets an honest -32004 instead
// of a stale-passing-as-historical answer.
//
// Telemetry note: callers that pin eth_call to a non-tip block
// are visible in tracing — span enters with the requested tag,
// result reflects current state. No on-the-wire warning is
// emitted because the spec compatibility cost outweighs the
// audit-surface gain.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider validating the block tag parameter even when ignoring it.

The comment states params[1] = block tag (line 964), but the implementation at line 988 doesn't parse, validate, or use it. While the PR objective is to maximize compatibility with off-the-shelf agents, silently ignoring malformed block tags (e.g., "0xZZZ", invalid JSON types) means callers won't receive -32602 errors for parameter mistakes.

Compare with eth_getBlockByNumber (lines 141-146), which validates the block parameter format even when it can serve the request. Basic validation would catch caller bugs without compromising compatibility.

Optional: Add basic block tag validation
 async fn eth_call(params: &Value, state: &SharedState) -> DispatchResult {
     // Execute a read-only EVM call without state mutation.
     // params[0] = {from, to, data, value, gas}
     // params[1] = block tag.
     //
     // 2026-05-21: dropped the strict historical-state gate here for
     // the same reason as eth_getTransactionCount. Off-the-shelf
     // EVM agents (Hyperlane relayer, ethers, viem) pin eth_call to
     // a recent past block as routine bookkeeping, even when the
     // underlying view function only has meaning against tip
     // (Mailbox.delivered, ERC20.totalSupply at finality, etc.).
     // Returning -32004 here kills every off-the-shelf integration.
     //
     // The trade-off: callers asking for "balanceOf(x) at h=N" get
     // current balance instead of historical. The agent ecosystem
     // already accounts for this by reading from a deterministic
     // current-state view of the chain. Use eth_getBalance /
     // eth_getCode / eth_getStorageAt for explicit state-read
     // pinning — those keep the strict gate so a wallet asking
     // "what was my balance at h=N" gets an honest -32004 instead
     // of a stale-passing-as-historical answer.
     //
     // Telemetry note: callers that pin eth_call to a non-tip block
     // are visible in tracing — span enters with the requested tag,
     // result reflects current state. No on-the-wire warning is
     // emitted because the spec compatibility cost outweighs the
     // audit-surface gain.
+    
+    // Optional: validate block tag format even though we ignore its value
+    if let Some(tag) = params.get(1) {
+        let bc = state.read().await;
+        let _ = resolve_block_tag(Some(tag), bc.height())
+            .map_err(|e| (-32602, e.to_string()))?;
+    }
+    
     match run_evm_dry_run(&params[0], state).await {
🤖 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 `@crates/sentrix-rpc/src/jsonrpc/eth.rs` around lines 964 - 987, The eth_call
handler currently ignores params[1] (block tag); add basic validation like
eth_getBlockByNumber: in the eth_call handling function (the eth_call RPC
handler that reads params[1]) parse/validate params[1] when present — accept the
canonical tags ("latest","earliest","pending") or a hex-prefixed block number
(validate hex digits and length) and reject other JSON types (objects/arrays) or
malformed hex by returning an RPC invalid-params error (-32602); do not change
the existing behavior of executing against tip when the tag is valid but
historical, only surface errors for malformed or wrong-typed block-tag inputs.

@github-actions github-actions Bot merged commit dd5ba93 into main May 22, 2026
25 checks passed
github-actions Bot pushed a commit that referenced this pull request May 22, 2026
PRs #705 + #707 (eth_call historical tags + logsBloom 256-byte fix)
landed on main without a version bump, so post-merge main carried more
RPC changes than the 2.2.14 binary already deployed to mainnet — two
distinct binaries sharing one version string.

Bump to 2.2.15 so the RPC-complete binary is distinguishable. This is
the version to deploy on the next mainnet upgrade.
satyakwok added a commit that referenced this pull request May 22, 2026
2.2.15 shipped to mainnet 2026-05-22 (34s halt window, zero
cascade-jail). Documents PRs #705/#707/#708.

Also scrubs host identifiers from the 2.2.14 + 2.2.15 entries —
topology now reads 'mainnet' / 'testnet' generically.
satyakwok added a commit that referenced this pull request May 22, 2026
2.2.15 shipped to mainnet 2026-05-22 (34s halt window, zero
cascade-jail). Documents PRs #705/#707/#708.

Also scrubs all host identifiers from the file — the 2.2.14/2.2.15
entries plus six historical v2.0.0/v2.1.x entries. Topology now reads
'mainnet' / 'testnet' / 'host' generically, consistent with the file
header's host-mapping-not-published note.
satyakwok added a commit that referenced this pull request May 22, 2026
…counts

2.2.15 shipped to mainnet 2026-05-22 (34s halt window, zero
cascade-jail). Documents PRs #705/#707/#708.

Also scrubs infra detail from the file: host identifiers from the
2.2.14/2.2.15 entries + six historical v2.0.0/v2.1.x entries, and the
explicit validator/host counts from the v1.0.0 entry. Topology now
reads generically.
github-actions Bot pushed a commit that referenced this pull request May 22, 2026
…counts (#709)

2.2.15 shipped to mainnet 2026-05-22 (34s halt window, zero
cascade-jail). Documents PRs #705/#707/#708.

Also scrubs infra detail from the file: host identifiers from the
2.2.14/2.2.15 entries + six historical v2.0.0/v2.1.x entries, and the
explicit validator/host counts from the v1.0.0 entry. Topology now
reads generically.
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.

1 participant