diff --git a/CHANGELOG.md b/CHANGELOG.md index e9b3faf..88cd62e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,18 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [2.2.14] — 2026-05-21 — Five RPC compat fixes; off-the-shelf EVM tooling unblocked + +**Production binary on mainnet (vps3 + vps6) + testnet (vps4) since 2026-05-21.** Mainnet halt window 21s. Chain progression resumed 1.36 s/blk. Zero cascade-jail. + +- **`eth_getTransactionByHash` returns EVM-standard JSON shape** (PR #692). Pre-fix the response carried the chain-native shape (`block_hash` / `transaction.{amount,chain_id,data:"EVM:..."}`) which crashed ethers / viem / alloy / Hyperlane CLI on every tx fetch. The new path converts to the canonical EVM tx response (`from`, `to`, `value`, `gas`, `gasPrice`, `nonce`, `v`, `r`, `s`, `hash`, `input`, `transactionIndex`, `blockNumber`, `blockHash`). Live-verified against a recent mainnet tx — all 14 keys present. +- **B3 trie reconcile fail-soft on missing nodes** (PR #696). The boot-time integrity check used to halt-crash if a single trie node was missing in the snapshot. Now it logs + skips so the chain can self-heal via consensus replay instead of needing a manual operator rsync from a clean peer. +- **RPC pending fields + coinbase intermediate fix** (PR #702). Pending tx response shape now matches spec; `miner` / `coinbase` block fields return canonical hex strings. +- **`null` v/r/s for undecodable EVM tx sigs** (PR #703). When `extract_vrs_from_rlp` fails we now return `null` for the three signature fields instead of `"0x0"`. EIP-1474 clients already treat null sig fields as "not recoverable"; the previous all-zero strings parsed as a valid ECDSA point pair and `ecrecover` returned junk that callers could trust by accident. +- **`eth_getTransactionCount` accepts any block tag** (PR #704). The 2026-05-06 strict gate (`-32004 historical state reads not yet supported`) broke Hyperlane relayer / ethers / viem nonce bookkeeping. A stale nonce is self-correcting (chain rejects wrong-nonce tx, caller retries) so this method now serves current nonce regardless of block tag. The strict gate stays on `eth_getBalance` / `eth_getCode` / `eth_getStorageAt` / `eth_call` — those keep returning `-32004` for any non-`latest` tag because silently serving current state for an explicit "balance at h=N" query is the original silent-lie risk the 2026-05-05 audit caught. + +**Sibling work, not in this binary**: `eth_call` historical-tag loosening + empty `logsBloom` constant 304B → 256B (Ethereum spec). Both live on testnet via branch `fix/rpc-eth-call-and-logsbloom`. Mainnet pickup planned for 2.2.15. + ## [2.2.11] — 2026-05-13 — EVM value-transfer + gas-fix forks activated; Blockchain refactor pass **Production binary on mainnet + testnet since 2026-05-13.** diff --git a/crates/sentrix-rpc/src/jsonrpc/eth.rs b/crates/sentrix-rpc/src/jsonrpc/eth.rs index ee4e48a..5087e6e 100644 --- a/crates/sentrix-rpc/src/jsonrpc/eth.rs +++ b/crates/sentrix-rpc/src/jsonrpc/eth.rs @@ -176,8 +176,13 @@ const ZERO_HASH_HEX: &str = "0x0000000000000000000000000000000000000000000000000 // chain emits. const EMPTY_SHA3_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; -// Empty 256-byte logs bloom (2 hex chars per byte → 512 zeros after 0x). -const EMPTY_LOGS_BLOOM: &str = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; +// Empty 256-byte logs bloom (Ethereum spec: 2048-bit / 256-byte field, +// rendered as 512 hex chars after the `0x` prefix). The constant pre-2026-05-21 +// was accidentally 608 hex chars (304 bytes), which broke ethers / viem fee +// oracle middlewares that strict-parse Block.logsBloom — the Hyperlane +// relayer fee-estimation path SerdeJson'd on this with "invalid length 608, +// expected 256 bytes" before we could submit any process() tx. +const EMPTY_LOGS_BLOOM: &str = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; fn build_block_json(block: &sentrix_primitives::Block) -> Value { let state_root = match block.state_root { @@ -956,12 +961,30 @@ async fn run_evm_dry_run( 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 (latest by default; specific historical - // heights gated until snapshot isolation lands) - { - let bc = state.read().await; - require_latest_state_read(params.get(1), bc.height())?; - } + // 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. match run_evm_dry_run(¶ms[0], state).await { Ok(receipt) => { let output_hex = format!("0x{}", hex::encode(&receipt.output)); diff --git a/crates/sentrix-rpc/src/jsonrpc/helpers.rs b/crates/sentrix-rpc/src/jsonrpc/helpers.rs index efb4c5f..1fb41dd 100644 --- a/crates/sentrix-rpc/src/jsonrpc/helpers.rs +++ b/crates/sentrix-rpc/src/jsonrpc/helpers.rs @@ -32,11 +32,10 @@ pub(super) fn resolve_block_tag(v: Option<&Value>, latest: u64) -> Result