From a0be7a74193581d573484cad835476b90d4f4c82 Mon Sep 17 00:00:00 2001 From: satyakwok <119509589+satyakwok@users.noreply.github.com> Date: Thu, 21 May 2026 14:48:49 +0200 Subject: [PATCH] fix(rpc): eth_getTransactionCount accepts any block tag Hyperlane relayer + every off-the-shelf EVM tool queries `eth_getTransactionCount(addr, blockN)` with a recent past block as part of normal nonce bookkeeping. Since the 2026-05-06 strict gate, we returned -32004 for those calls, which broke the agent's submit loop (logged repeatedly as "historical state reads not yet supported; use 'latest'" until the host was deprioritized in its FallbackProvider). Unlike eth_getBalance / eth_getCode / eth_getStorageAt / eth_call where a stale-vs-current answer can drive wrong protocol decisions, a stale nonce is self-correcting: the chain rejects a tx with a wrong nonce, the caller retries, no decision is made on stale data. So this method serves current nonce regardless of block tag and trusts the caller to handle the retry loop. The strict gate stays on the other four state-read methods. Bumps workspace 2.2.13 -> 2.2.14. --- Cargo.lock | 38 +++++++++++------------ Cargo.toml | 2 +- crates/sentrix-rpc/src/jsonrpc/eth.rs | 21 ++++++++----- crates/sentrix-rpc/src/jsonrpc/helpers.rs | 6 ++-- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84c205b2..599b2eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5189,7 +5189,7 @@ dependencies = [ [[package]] name = "sentrix" -version = "2.2.12" +version = "2.2.14" dependencies = [ "aes-gcm", "alloy-consensus", @@ -5237,7 +5237,7 @@ dependencies = [ [[package]] name = "sentrix-bft" -version = "2.2.12" +version = "2.2.14" dependencies = [ "bincode", "hex", @@ -5255,7 +5255,7 @@ dependencies = [ [[package]] name = "sentrix-codec" -version = "2.2.12" +version = "2.2.14" dependencies = [ "bincode", "hex", @@ -5264,7 +5264,7 @@ dependencies = [ [[package]] name = "sentrix-core" -version = "2.2.12" +version = "2.2.14" dependencies = [ "alloy-consensus", "alloy-eips 2.0.4", @@ -5294,7 +5294,7 @@ dependencies = [ [[package]] name = "sentrix-evm" -version = "2.2.12" +version = "2.2.14" dependencies = [ "alloy-primitives", "hex", @@ -5309,7 +5309,7 @@ dependencies = [ [[package]] name = "sentrix-faucet" -version = "2.2.12" +version = "2.2.14" dependencies = [ "anyhow", "axum", @@ -5330,7 +5330,7 @@ dependencies = [ [[package]] name = "sentrix-grpc" -version = "2.2.12" +version = "2.2.14" dependencies = [ "async-stream", "bincode", @@ -5349,7 +5349,7 @@ dependencies = [ [[package]] name = "sentrix-network" -version = "2.2.12" +version = "2.2.14" dependencies = [ "async-trait", "bincode", @@ -5367,7 +5367,7 @@ dependencies = [ [[package]] name = "sentrix-node" -version = "2.2.12" +version = "2.2.14" dependencies = [ "anyhow", "axum", @@ -5392,14 +5392,14 @@ dependencies = [ [[package]] name = "sentrix-precompiles" -version = "2.2.12" +version = "2.2.14" dependencies = [ "alloy-primitives", ] [[package]] name = "sentrix-primitives" -version = "2.2.12" +version = "2.2.14" dependencies = [ "hex", "proptest", @@ -5414,7 +5414,7 @@ dependencies = [ [[package]] name = "sentrix-prom-exporter" -version = "2.2.12" +version = "2.2.14" dependencies = [ "http-body-util", "hyper", @@ -5442,7 +5442,7 @@ dependencies = [ [[package]] name = "sentrix-rpc" -version = "2.2.12" +version = "2.2.14" dependencies = [ "alloy-consensus", "alloy-eips 2.0.4", @@ -5472,14 +5472,14 @@ dependencies = [ [[package]] name = "sentrix-rpc-types" -version = "2.2.12" +version = "2.2.14" dependencies = [ "serde_json", ] [[package]] name = "sentrix-staking" -version = "2.2.12" +version = "2.2.14" dependencies = [ "sentrix-primitives", "serde", @@ -5489,7 +5489,7 @@ dependencies = [ [[package]] name = "sentrix-storage" -version = "2.2.12" +version = "2.2.14" dependencies = [ "bincode", "libmdbx", @@ -5504,7 +5504,7 @@ dependencies = [ [[package]] name = "sentrix-trie" -version = "2.2.12" +version = "2.2.14" dependencies = [ "bincode", "blake3", @@ -5521,7 +5521,7 @@ dependencies = [ [[package]] name = "sentrix-wallet" -version = "2.2.12" +version = "2.2.14" dependencies = [ "aes-gcm", "argon2", @@ -5540,7 +5540,7 @@ dependencies = [ [[package]] name = "sentrix-wire" -version = "2.2.12" +version = "2.2.14" dependencies = [ "bincode", "secp256k1 0.31.1", diff --git a/Cargo.toml b/Cargo.toml index c5b1b06d..1d0d2350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/sentrix-rpc/src/jsonrpc/eth.rs b/crates/sentrix-rpc/src/jsonrpc/eth.rs index a58b6aa3..ee4e48a9 100644 --- a/crates/sentrix-rpc/src/jsonrpc/eth.rs +++ b/crates/sentrix-rpc/src/jsonrpc/eth.rs @@ -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)); diff --git a/crates/sentrix-rpc/src/jsonrpc/helpers.rs b/crates/sentrix-rpc/src/jsonrpc/helpers.rs index 9d86b4eb..efb4c5f9 100644 --- a/crates/sentrix-rpc/src/jsonrpc/helpers.rs +++ b/crates/sentrix-rpc/src/jsonrpc/helpers.rs @@ -33,8 +33,10 @@ pub(super) fn resolve_block_tag(v: Option<&Value>, latest: u64) -> Result