From 8bde0639d52a50d680970216d7e9bb96e0bdcac0 Mon Sep 17 00:00:00 2001 From: monty-sei Date: Tue, 11 Aug 2026 15:35:41 +1000 Subject: [PATCH 1/4] docs(evm-parity): v6.6.0 RPC and transaction behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five additions from the v6.6.0 catch-up, each verified against sei-chain at tag v6.6.0 rather than against the PR description that proposed it. - state-proofs: eth_getProof now rejects non-hex storage keys and caps a request at 1024 keys. Verified in #3556: `const MaxStorageKeysPerProof = 1024` and the errors "too many storage keys: got %d, max %d" and "invalid storage key %q" verbatim. - transaction-types: EIP-7702 SetCode requires a non-empty auth list. The error string "auth list cannot be empty" is in x/evm/types/ethtx/semantic_validation.go. - evm-compatibility: eth_getProof resolves across store backends, not only classic IAVL. - gas-and-fees: receipts report the actual effectiveGasPrice for type-2 transactions, min(baseFee + tip, maxFee), matching EIP-1559 semantics. - websocket: newHeads under Autobahn returns zero hashes for parentHash, receiptsRoot and transactionsRoot, sources stateRoot from AppHash, and approximates gasUsed — so head-chaining subscribers need another mechanism. --- evm/evm-parity/evm-compatibility.mdx | 2 +- evm/evm-parity/gas-and-fees.mdx | 8 ++++++++ evm/evm-parity/state-proofs.mdx | 7 +++++++ evm/evm-parity/transaction-types.mdx | 7 +++++++ evm/evm-parity/websocket.mdx | 17 +++++++++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/evm/evm-parity/evm-compatibility.mdx b/evm/evm-parity/evm-compatibility.mdx index f994b82..1d6892e 100644 --- a/evm/evm-parity/evm-compatibility.mdx +++ b/evm/evm-parity/evm-compatibility.mdx @@ -37,7 +37,7 @@ Standard EVM tooling — viem, wagmi, ethers, Foundry, Hardhat — works on Sei | `eth_getTransactionCount` (nonce) | Supported | | | `eth_getCode` | Supported | | | `eth_getStorageAt` | Supported — differences | SSTORE cost is governance-adjustable; do not hard-code gas assumptions. [See Gas and Fees.](/evm/evm-parity/gas-and-fees) | -| `eth_getProof` | Supported — differences | Returns IAVL proof data rather than Ethereum Merkle Patricia Trie proofs. Proof verification logic must account for this. [See State Proofs.](/evm/evm-parity/state-proofs) | +| `eth_getProof` | Supported — differences | Returns IAVL proof data rather than Ethereum Merkle Patricia Trie proofs. Proof verification logic must account for this. Proofs resolve across supported store backends (classic IAVL, store/v2 memiavl, and other proof-capable queryable stores), so the method works across a broader range of node configurations. [See State Proofs.](/evm/evm-parity/state-proofs) | ## Blocks and Finality diff --git a/evm/evm-parity/gas-and-fees.mdx b/evm/evm-parity/gas-and-fees.mdx index f4665b5..5827302 100644 --- a/evm/evm-parity/gas-and-fees.mdx +++ b/evm/evm-parity/gas-and-fees.mdx @@ -53,6 +53,14 @@ const feeData = await provider.getFeeData(); + +### Effective Gas Price on Receipts + +For dynamic-fee (type 2) transactions, the transaction receipt's `effectiveGasPrice` field reports the **actual price charged** — `min(baseFee + maxPriorityFeePerGas, maxFeePerGas)` — not the fee cap. This matches standard EIP-1559 semantics, so clients such as ethers and hardhat see the same `effectiveGasPrice` behavior they expect from Ethereum. + +In practice, when your priority tip plus the base fee stays below `maxFeePerGas`, the receipt reports `baseFee + maxPriorityFeePerGas` rather than `maxFeePerGas`. Use the receipt's `effectiveGasPrice` (not `maxFeePerGas`) when computing what a transaction actually paid. + + ## SSTORE Cost The gas cost of `SSTORE` (writing to contract storage) is governance-adjustable on Sei. It is currently **72,000 gas** — the same on mainnet and testnet (see [Divergence from Ethereum](/evm/differences-with-ethereum#sstore-gas-cost)) — but treat that as the current value, not a constant: do not hard-code storage write estimates in your application. diff --git a/evm/evm-parity/state-proofs.mdx b/evm/evm-parity/state-proofs.mdx index dbe256a..8fa8462 100644 --- a/evm/evm-parity/state-proofs.mdx +++ b/evm/evm-parity/state-proofs.mdx @@ -25,6 +25,13 @@ If you are doing standard contract reads, event queries, or transaction lookups, ## Calling eth_getProof +### Storage key requirements + +Before calling `eth_getProof`, note two requirements Sei enforces on the `storageKeys` argument: + +- **Keys must be hex-encoded.** Each storage key must be a valid hex-encoded value (for example `0x0000000000000000000000000000000000000000000000000000000000000001`). Keys are decoded and left-padded to 32 bytes. A malformed, non-hex key is rejected with an `invalid storage key` error. Raw byte strings, which were previously accepted, no longer work. +- **At most 1024 keys per request.** A single proof request may include a maximum of 1024 storage keys. Requesting more returns a `too many storage keys` error. Split larger sets across multiple requests. + The call works through standard libraries: diff --git a/evm/evm-parity/transaction-types.mdx b/evm/evm-parity/transaction-types.mdx index 7f11981..badc49d 100644 --- a/evm/evm-parity/transaction-types.mdx +++ b/evm/evm-parity/transaction-types.mdx @@ -16,6 +16,13 @@ Sei supports most Ethereum transaction types. The one notable exception is blob | 2 | EIP-1559 | Fee market | Supported — base fee is not burned | | 4 | EIP-7702 | Set code | Supported | + +### Set Code (EIP-7702) Auth List Requirement + +Type 4 (EIP-7702) SetCode transactions must include a non-empty authorization list. A transaction with an empty or nil auth list is rejected during validation with the error `auth list cannot be empty`. + +Each authorization entry must also carry a valid (non-nil) chain ID. If you are constructing SetCode transactions directly, ensure at least one authorization is present before submitting. + ## Not Supported | Type | EIP | Name | Notes | diff --git a/evm/evm-parity/websocket.mdx b/evm/evm-parity/websocket.mdx index a1315a6..be7d876 100644 --- a/evm/evm-parity/websocket.mdx +++ b/evm/evm-parity/websocket.mdx @@ -114,3 +114,20 @@ const unwatch = client.watchEvent({ - Sei's instant finality means every block emitted over WebSocket is already final — no need to wait for additional confirmations before acting on an event. - Pending transaction subscriptions (`newPendingTransactions`) are supported at the RPC level but Sei does not guarantee Ethereum-style pending state visibility. + + + +## `newHeads` Under Autobahn Consensus + +When a node runs under Autobahn consensus, `eth_subscribe("newHeads")` notifications are delivered from an in-process notifier that publishes committed-block headers directly, rather than from the legacy consensus event bus. Subscribers still only observe headers for fully committed blocks, but the header payload differs from the legacy path in a few ways: + +- **`parentHash`, `receiptsRoot`, and `transactionsRoot` are returned as zero hashes** (`0x0000…0000`). The Autobahn block-execution path does not build a Tendermint-style hash chain, so there is no meaningful value to surface for these fields. +- **`stateRoot`** is sourced from the finalized block's `AppHash` (the post-execution application hash), rather than from a pre-execution header field. +- **`hash`** is the Autobahn block-header hash — the same value reported as `blockHash` by `eth_getBlockByNumber` and the receipt APIs, keeping `newHeads` consistent with the rest of the EVM RPC surface. +- **`gasUsed`** is an approximation (summed from per-transaction results) to keep the notification cheap. + +Because of these differences: + +- Subscribers that chain-validate the head stream by linking `parentHash` values cannot rely on `newHeads` under Autobahn and need a different mechanism. +- If you need exact `gasUsed` or the omitted hash fields, fetch the block explicitly with `eth_getBlockByNumber`. + From 7e964b35d7f2e86e2f4542c4c2a6760feaa339f0 Mon Sep 17 00:00:00 2001 From: monty-sei Date: Tue, 11 Aug 2026 15:35:41 +1000 Subject: [PATCH 2/4] docs(evm): JSON precompile value limit, realistic gas price example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - precompiles/json: extractAsUint256 rejects value strings over 100 characters. Verified at precompiles/json/json.go:175 — `if len(strValue) > 100`. - transactions: replace the placeholder effectiveGasPrice "0x1234" with a realistic value. Cosmetic, no behavioural claim. --- evm/precompiles/json.mdx | 4 ++++ evm/transactions.mdx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/evm/precompiles/json.mdx b/evm/precompiles/json.mdx index fdcd386..6541c0d 100644 --- a/evm/precompiles/json.mdx +++ b/evm/precompiles/json.mdx @@ -131,6 +131,10 @@ The JSON precompile has specific limitations for different data types: | extractAsBytesList | Arrays of strings/objects | Each element returned as bytes | + +**Value Length Limit:** `extractAsUint256` rejects value strings longer than 100 characters. If the numeric string extracted for the given key exceeds 100 characters, the call fails with `value string too long`. Ensure the numeric values you pass stay within this limit. + + ### Data Type Conversion Strategies ```typescript diff --git a/evm/transactions.mdx b/evm/transactions.mdx index 6b9861b..d9eacbe 100644 --- a/evm/transactions.mdx +++ b/evm/transactions.mdx @@ -258,7 +258,7 @@ EVM transactions in Sei follow the Ethereum transaction format with standard pro "logs": [], "status": "0x1", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0x1234", + "effectiveGasPrice": "0x77359400", "type": "0x2" } } From dd66c9abd46e48bf97b79ed47f510192523b8c8f Mon Sep 17 00:00:00 2001 From: monty-sei Date: Tue, 11 Aug 2026 15:35:41 +1000 Subject: [PATCH 3/4] docs(node): PebbleDB descending-version MVCC, seictl wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rocksdb-backend: fresh PebbleDB state stores use a descending-version MVCC encoding so latest-version reads land on the newest version directly. Verified at sei-db/db_engine/pebbledb/mvcc/db.go:44 — `descendingMVCCMarkerKey = "s/_mvcc_descending"` — with detectMVCCMode() and db_ascending.go providing the legacy read path for stores written by earlier builds. RocksDB remains recommended for iteration-heavy archive workloads. - seictl: drop the stale "proxy app" mention from the settings list. --- node/rocksdb-backend.mdx | 8 ++++++++ node/seictl.mdx | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/node/rocksdb-backend.mdx b/node/rocksdb-backend.mdx index 587b477..05098a4 100644 --- a/node/rocksdb-backend.mdx +++ b/node/rocksdb-backend.mdx @@ -26,6 +26,14 @@ By contrast, **RocksDB** supports native user-defined timestamps and optimized c In Sei’s benchmarks, RocksDB achieved up to **10–30× faster traceBlock iteration times** compared to PebbleDB, with even greater benefits observed on archive nodes. + + +### PebbleDB descending-version encoding + +Recent PebbleDB builds partly narrow this gap for latest-version reads. Because PebbleDB has no native MVCC, Sei encodes the version into each key. Freshly created PebbleDB state stores now use a **descending-version MVCC encoding**, which sorts newer versions before older ones for the same logical key. This lets latest-version reads land directly on the newest visible version instead of scanning through older versions, improving read performance on the fast path. Fresh stores are marked on disk with a sentinel key (`s/_mvcc_descending`) so the mode is detected automatically on open. + +Legacy PebbleDB stores written by earlier builds use the older **ascending-version encoding**. These are detected automatically on open and read using the legacy ascending path—no error is raised—but they stay unmarked and cannot benefit from the descending fast path unless the store is recreated or migrated. This mirrors the migration constraint on RocksDB: archive nodes that cannot recreate their state store will continue running on the slower legacy path. Note that even with descending encoding, PebbleDB still lacks native MVCC and column-family support, so RocksDB remains the recommended backend for iteration-heavy archive and long-history RPC workloads. + ## Example: TraceBlock Latency Comparison The following chart compares iteration (trace time) performance between **PebbleDB** and **RocksDB** over a 3 million block history: diff --git a/node/seictl.mdx b/node/seictl.mdx index 3b57536..f90adc4 100644 --- a/node/seictl.mdx +++ b/node/seictl.mdx @@ -264,7 +264,7 @@ Client-level configuration including: Node-level configuration including: -- Proxy app and database settings +- Database settings - Logging configuration - RPC and P2P settings - Mempool and consensus parameters From 00b54994a2d257df171131e32d2856c540251f44 Mon Sep 17 00:00:00 2001 From: monty-sei Date: Tue, 11 Aug 2026 15:35:41 +1000 Subject: [PATCH 4/4] docs(learn): correct the Unsafe*TimeoutOverride description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page stated that the Unsafe*TimeoutOverride settings enforce shorter consensus timeouts. They are gated by `unsafe-overrides-enabled` under [consensus], which defaults to false — so by default they are ignored and the on-chain timeout consensus params apply instead. Verified: the flag exists in sei-tendermint/config/config.go at v6.6.0. --- learn/twin-turbo-consensus.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/twin-turbo-consensus.mdx b/learn/twin-turbo-consensus.mdx index 919547f..6490542 100644 --- a/learn/twin-turbo-consensus.mdx +++ b/learn/twin-turbo-consensus.mdx @@ -14,7 +14,7 @@ The key to achieving sub-second finality lies in aggressively optimizing and par This optimized flow involves several key enhancements: -1. **Aggressive Timeout Configuration:** Sei utilizes heavily tuned Tendermint consensus parameters. Configuration settings (e.g., `UnsafeProposeTimeoutOverride`, `UnsafeCommitTimeoutOverride`) enforce much shorter durations for block proposal, voting, and commit rounds compared to standard Tendermint configurations, directly contributing to the sub-second target block time. Faster gossip propagation for consensus messages further reduces communication latency between validators. +1. **Aggressive Timeout Configuration:** Sei utilizes heavily tuned Tendermint consensus parameters. Configuration settings (e.g., `UnsafeProposeTimeoutOverride`, `UnsafeCommitTimeoutOverride`) can enforce much shorter durations for block proposal, voting, and commit rounds compared to standard Tendermint configurations, directly contributing to the sub-second target block time. These `Unsafe*TimeoutOverride` fields are gated by the `unsafe-overrides-enabled` flag under the `[consensus]` section of the node config. This flag defaults to `false`, meaning the overrides are ignored and the on-chain timeout consensus parameters are used instead. The overrides are only applied when `unsafe-overrides-enabled` is set to `true` (or, during the transition period, while the on-chain timeout params still match the legacy values). In practice, timeout tuning should be governed by the on-chain consensus parameters rather than these unsafe per-node overrides. Faster gossip propagation for consensus messages further reduces communication latency between validators. 2. **Intelligent Mempool Management & Transaction Preparation:** Even before a block proposal is formally initiated for height `H`, validators can begin processing transactions intended for that block. This involves collecting transactions from the network, decoding them concurrently (`DecodeTransactionsConcurrently`), analyzing potential state dependencies (`GenerateEstimatedWritesets`), and potentially pre-fetching required state data from SeiDB. This "pre-consensus" preparation minimizes the work needed once the actual proposal for height `H` arrives. 3. **Optimized BFT Rounds with Parallel Execution Integration:** The critical optimization is the deep integration with Sei's parallelization engine. When a validator receives a block proposal for height `H`, it doesn't necessarily wait for the prevote/precommit rounds to complete before starting execution. Instead: - The block's transactions are dispatched to the parallel execution engine (`ProcessTXsWithOCC`, `DeliverTxBatch`).