Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ members = [".", "crates/sentrix-primitives", "crates/sentrix-wallet", "crates/se
# `version.workspace = true`. Same goes for edition/license/repository so
# they can't drift across crates.
[workspace.package]
version = "2.2.13"
version = "2.2.14"
edition = "2024"
license = "BUSL-1.1"
repository = "https://github.com/sentrix-labs/sentrix"
Expand Down
21 changes: 14 additions & 7 deletions crates/sentrix-rpc/src/jsonrpc/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,22 @@ pub(super) async fn dispatch(method: &str, params: &Value, state: &SharedState)
// accepted only the first, the rest piled up rejected
// mid-block. Live discovery 2026-05-02.
//
// 2026-05-06: extend the same historical-state honesty
// pattern Bug A applied to eth_getBalance — specific past
// heights return -32004 instead of silently returning
// current nonce. `pending` keeps its mempool-aware path.
// 2026-05-21: relaxed the historical-state gate for this
// method only. Hyperlane relayer + ethers + viem all query
// `eth_getTransactionCount(addr, blockN)` against a recent
// past block as part of routine nonce bookkeeping — they do
// NOT actually care about the historical value, they just
// want a nonce they can pass to the next signed tx.
// Returning -32004 here breaks every off-the-shelf relayer.
//
// Unlike eth_getBalance / eth_getCode / eth_getStorageAt /
// eth_call (kept strict), a "wrong" nonce is self-correcting:
// the chain rejects a tx with a stale nonce, the caller
// retries, no protocol-level decision was made on stale data.
// So this method serves current nonce regardless of block
// tag and trusts the caller to handle the retry loop.
let block_tag = params.get(1).and_then(|v| v.as_str()).unwrap_or("latest");
let bc = state.read().await;
if block_tag != "pending" {
require_latest_state_read(params.get(1), bc.height())?;
}
let mut nonce = bc.accounts.get_nonce(&address);
if block_tag == "pending" {
nonce = nonce.saturating_add(bc.mempool_pending_count(&address));
Comment on lines 71 to 75
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate non-string block_tag instead of silently treating it as latest.

params[1] values with invalid types (number/object/array) are currently coerced to "latest". This hides malformed requests and can return unintended nonce values. Return -32602 for non-string/non-null tags, while keeping relaxed behavior for string tags (including historical hex strings).

Suggested patch
-            let block_tag = params.get(1).and_then(|v| v.as_str()).unwrap_or("latest");
+            let block_tag = match params.get(1) {
+                None | Some(Value::Null) => "latest",
+                Some(Value::String(s)) => s.as_str(),
+                Some(other) => {
+                    return Err((-32602, format!("invalid block tag: {other}")));
+                }
+            };
🤖 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 71 - 75, The code
currently coerces params.get(1) to "latest" when it's not a string, which hides
malformed requests; change the params[1] handling so that if params.get(1)
exists and is not a string or null, the method returns a JSON-RPC invalid params
error (-32602); preserve existing behavior for string tags (including historical
hex strings) and for missing/null tags by treating them as "latest"/relaxed;
update the logic around params.get(1) / block_tag in the eth RPC handler (the
block_tag variable and the branch that checks "pending" and uses
bc.accounts.get_nonce and bc.mempool_pending_count) to perform this type check
and return the proper error instead of silently defaulting to "latest".

Expand Down
6 changes: 4 additions & 2 deletions crates/sentrix-rpc/src/jsonrpc/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ pub(super) fn resolve_block_tag(v: Option<&Value>, latest: u64) -> Result<u64, &
}

/// Gate state-read methods (`eth_getBalance`, `eth_getCode`,
/// `eth_getStorageAt`, `eth_getTransactionCount`, `eth_call`) against
/// historical-specific block heights.
/// `eth_getStorageAt`, `eth_call`) against historical-specific block
/// heights. `eth_getTransactionCount` was removed from this gate
/// 2026-05-21 — see the comment in eth.rs for why nonces are different
/// from other state reads.
///
/// Sentrix doesn't yet have MDBX snapshot isolation, so account state
/// reads always serve current-tip data regardless of the block tag
Expand Down
Loading