From 16ce148ef1466d7eae7f17d2d163f0faa9eb3945 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:07:33 +0000 Subject: [PATCH 01/41] docs: Adds new opt-in config fields to enable estimated read/write metrics for FlatKV state-commit, state-store, and receipt-store Pebble DBs, plus exposes FlatKV config fields in the server config template. (sei-protocol/sei-chain#3566) --- node/advanced-config-monitoring.mdx | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index d9c80c2..c46da6d 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -582,6 +582,38 @@ FlatKV metrics carry these labels where applicable: A single FlatKV-level knob controls Pebble's internal (per-DB) metrics: `enable-pebble-metrics` under `[state-commit.flatkv]` in `app.toml` (default `true`). The node honors this key when it is present, but the `app.toml` that `seid init` generates does not include it. To change the default, add the key manually. The value propagates to every data DB (account, code, storage, legacy, and metadata) during initialization. It overrides any per-DB `EnableMetrics` settings. Configure Pebble metrics through this knob, not through the individual per-DB settings. + +## Pebble estimated read/write metrics + +SeiDB can emit lightweight, opt-in estimates of logical read and write operations observed by its Pebble-backed wrappers. These counters are disabled by default and are intended to approximate read amplification (estimated reads divided by estimated writes) without the overhead of Pebble's full internal metrics. + +### Available metrics + +| Metric | Type | Unit | Description | +| --- | --- | --- | --- | +| `pebble_estimated_reads` | Counter | `{count}` | Estimated logical PebbleDB reads observed by SeiDB wrappers. | +| `pebble_estimated_writes` | Counter | `{count}` | Estimated logical PebbleDB writes observed by SeiDB wrappers. | + +Both counters carry a `db` attribute identifying the database the measurement applies to (derived from the base name of the data directory), so you can distinguish FlatKV data DBs, the state-store backend, and receipt storage. + +### Enabling the counters + +These estimates are controlled per subsystem in `app.toml`. All default to `false`: + +| Config key | Section | Description | +| --- | --- | --- | +| `enable-read-write-metrics` | `[state-commit.flatkv]` | Emits estimated read/write counters for FlatKV's Pebble DBs. | +| `ss-enable-read-write-metrics` | `[state-store]` | Emits estimated PebbleDB MVCC read/write counters for the state-store backend. Applies when `ss-backend = "pebbledb"`. | +| `enable-read-write-metrics` | `[receipt-store]` | Emits estimated read/write counters for Pebble-backed receipt storage. | + +For example, to enable the FlatKV counters, add the following to `app.toml`: + +```toml +[state-commit.flatkv] +enable-read-write-metrics = true +``` + + ## LittDB OpenTelemetry metrics LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` instead of a private Prometheus client. When `MetricsEnabled` is set, LittDB configures a Prometheus exporter on the global provider and serves `/metrics` on `MetricsPort` (default `9101`). The `MetricsNamespace` and `MetricsRegistry` config fields were removed, and all metric names use a fixed `litt_` prefix. From 81b7e66e3a02cd5c77df3e247a6efea32d6ec08d Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:08:27 +0000 Subject: [PATCH 02/41] docs: Autobahn (GigaRouter) now supports fullnode mode driven by the node's `mode` config field, adds a new `max_inbound_fullnode_peers` config field, moves the `autobahn-config-file` key to top-level in config.toml, and changes the `evmrpc` field in autobahn.json to be required on every validator. (sei-protocol/sei-chain#3525) --- evm/reference.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evm/reference.mdx b/evm/reference.mdx index 1fb73ef..f629d44 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -127,7 +127,11 @@ You can also browse every method below in the interactive explorer above. This s **Sei-specific behavior:** Decodes the bytes to an ethtypes.Transaction, wraps it in a Cosmos MsgEVMTransaction, and broadcasts it through CometBFT. By default, it uses a CheckTx-level BroadcastTx (that is, `broadcast_tx_sync`), and slow mode uses BroadcastTxCommit. Non-zero CheckTx codes are returned as ABCI errors, not geth mempool errors. Legacy (non-1559) transactions must set gasPrice at or above the governance minimum (currently 50 gwei on Sei Mainnet). Blob (EIP-4844) transactions are not enabled. The method supports per-sender EvmProxy forwarding. -**Under Autobahn (`AutobahnConfigFile` set):** Transaction broadcast routes through Autobahn's producer-backed mempool instead of CometBFT's `TxMempool`. This mempool admits EVM transactions strictly in sequential nonce order for each sender. It rejects a transaction with a bad-nonce error if its nonce does not match the sender's next expected nonce. Senders must therefore submit nonces contiguously. +**Under Autobahn (`AutobahnConfigFile` set):** The node's role follows the top-level `mode` field in the CometBFT config. With `mode = "validator"`, the node runs the validator path and backs transaction submission with a local mempool; any other mode runs the node as a fullnode that loads the committee for routing only and has no local mempool. + +On a validator, transaction broadcast routes through Autobahn's producer-backed mempool instead of CometBFT's `TxMempool`. This mempool admits EVM transactions strictly in sequential nonce order for each sender. It rejects a transaction with a bad-nonce error if its nonce does not match the sender's next expected nonce. Senders must therefore submit nonces contiguously. + +On an Autobahn fullnode, there is no local mempool. `eth_sendRawTransaction` is proxied to the shard owner (the committee member that owns the sender's EVM shard), which must have an EVM RPC URL configured — the `evmrpc` field is required for every committee member in the Autobahn config file (`autobahn.json`). The underlying Tendermint `broadcast_tx_*` methods (`broadcast_tx_async`, `broadcast_tx_sync`, `broadcast_tx_commit`) have no local backing on a fullnode and return the error `autobahn fullnode has no local mempool; broadcast_tx_* must be sent to a validator`. Submit transactions to a validator (or let `eth_sendRawTransaction`'s per-sender proxy forwarding handle the routing). Pending-nonce queries also behave differently: `eth_getTransactionCount` with the `pending` tag returns the on-chain confirmed nonce (the pending nonce lives on the shard owner), and `unconfirmed_txs` / `num_unconfirmed_txs` return empty. Under Autobahn, the default CheckTx-synchronous broadcast (`broadcast_tx_sync`, the same path named above) blocks while the mempool is full. It returns only after capacity is available. As a result, a default `eth_sendRawTransaction` call can stall for as long as the mempool stays full. The async path (`broadcast_tx_async`) may silently drop the transaction instead. The `unsafe_flush_mempool` Tendermint RPC endpoint is not supported under Autobahn and returns `unsafe_flush_mempool is not supported with autobahn mempool`. From 35822b4b475e0df3c58db6b1a57e6fb4b685a74a Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:08:57 +0000 Subject: [PATCH 03/41] docs: A new config field `evm.max_estimate_gas_calls` limits the number of calls allowed in an `eth_estimateGasAfterCalls` request, defaulting to 100. (sei-protocol/sei-chain#3631) --- evm/reference.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/reference.mdx b/evm/reference.mdx index f629d44..97b90b6 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -1086,7 +1086,7 @@ The `filter` object applies only to `logs` subscriptions. For `newHeads`, pass t **Limited.** Estimates gas for a transaction after it applies a sequence of preceding calls to the same simulated state. -**Sei-specific behavior:** This is a non-standard Sei/geth extension (not part of the standard Ethereum JSON-RPC spec). It has the same gas-cap and fail-fast-limiter behavior as `eth_estimateGas`. +**Sei-specific behavior:** This is a non-standard Sei/geth extension (not part of the standard Ethereum JSON-RPC spec). It has the same gas-cap and fail-fast-limiter behavior as `eth_estimateGas`. The number of preceding `calls` is capped by the `evm.max_estimate_gas_calls` config (default 100). A request whose `calls` array exceeds this limit is rejected early with a 'too many calls' error before any state wrapping or resource acquisition. **Parameters:** From 3fbe09db2cad3a84a5d0ca0f210fea0ed4703c40 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:10:19 +0000 Subject: [PATCH 04/41] docs: Adds a new 'auto' value for the sc-write-mode node config field, enabling runtime write-mode migration transitions without restarts, with important operator warnings about never using it on flatkv_only nodes. (sei-protocol/sei-chain#3581) --- node/giga-storage-migration.mdx | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/node/giga-storage-migration.mdx b/node/giga-storage-migration.mdx index 3ee7f6a..76ce52f 100644 --- a/node/giga-storage-migration.mdx +++ b/node/giga-storage-migration.mdx @@ -258,6 +258,57 @@ While a node is in a migration mode: a migration. - The migration boundary advances at most once per block. + + +### The `auto` write mode (runtime transitions without restarts) + +`auto` is an additional `sc-write-mode` value that removes the stop-edit-restart +step between each migration stage. Instead of fixing the write mode in `app.toml`, +`auto` derives the **effective** write mode from the migration metadata persisted +in FlatKV. A node stays on `auto` in config across the entire migration chain, +and the effective mode advances through `memiavl_only` → `migrate_evm` → +`evm_migrated` → `migrate_all_but_bank` → `all_migrated_but_bank` → +`migrate_bank` → `flatkv_only` via coordinated runtime transitions, with no +restart required at any stage. + +```toml copy +[state-commit] +sc-write-mode = "auto" +``` + +Under `auto`: + +- FlatKV is **lazy**. On a fresh node the FlatKV directory is not created on disk + until the first `migrate_evm` transition occurs. While the directory is absent, + the store is effectively `memiavl_only` and FlatKV does not contribute to the + app hash at all. +- Transitions walk the migration chain **one step forward at a time**, and a + migration stage must have completed its drain before the node can advance to + the following steady state. Skipping steps, moving backward, or exiting a + migration mid-flight is rejected. +- Transitions are **deterministic and level-triggered** across the quorum: every + node performs the same transition at the same height. Because migration writes + feed the app hash, a transition that is not yet persisted is re-evaluated on + every commit, so a node that restarts inside the pre-commit window re-fires the + transition and self-heals rather than diverging. +- On restart, the effective mode is re-derived from the on-disk migration state. + An in-flight migration resumes in its migration mode; a completed one comes up + in the following steady state even if no explicit completion flip was issued. + +State proofs remain supported only for memiavl-resident data — FlatKV has no +proof builder. This is not specific to `auto`, but it becomes operator-visible +once an `auto` chain migrates stores into FlatKV. + +**Never set `sc-write-mode = "auto"` on an existing `flatkv_only` +node.** `auto` is only valid for a node whose history began in `memiavl` (for +example, a node switching from `memiavl_only`). A node that started in FlatKV +mode must keep `flatkv_only` forever. Depending on the node's on-disk metadata, +setting `auto` on a flatkv-origin node fails in one of two ways: with migration +metadata present (for example a state-synced node), every commit fails with a +version-mismatch error; with metadata absent (a genesis FlatKV chain), mode +derivation resolves `memiavl_only` and reads silently route to an empty +memiavl, serving incorrect empty results. + ### Operator-facing knobs **`sc-keys-to-migrate-per-block`** (`app.toml`, `[state-commit]` section) From 25ebb8c02f3623002643e0279661de28bae6e1bc Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:10:48 +0000 Subject: [PATCH 05/41] docs: Adds a new EVM RPC config field `evm.max_subscriptions_logs` to cap concurrent logs subscriptions (default 1000), beyond which new logs subscriptions are rejected. (sei-protocol/sei-chain#3621) --- evm/reference.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/reference.mdx b/evm/reference.mdx index 97b90b6..70f09b2 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -963,7 +963,7 @@ Heights with pruned or partial base-fee data are skipped in `baseFeePerGas`, but **Limited.** Opens a WebSocket-only push subscription for newHeads or logs notifications. -**Sei-specific behavior:** Available over WebSocket only. SubscriptionAPI is not registered on the HTTP server, so over HTTP the method returns rpc.ErrNotificationsUnsupported. Only 'newHeads' and 'logs' are implemented in source. There is no 'newPendingTransactions' subscription, although some client docs imply otherwise. MaxSubscriptionsNewHead caps newHeads subscriptions. +**Sei-specific behavior:** Available over WebSocket only. SubscriptionAPI is not registered on the HTTP server, so over HTTP the method returns rpc.ErrNotificationsUnsupported. Only 'newHeads' and 'logs' are implemented in source. There is no 'newPendingTransactions' subscription, although some client docs imply otherwise. The number of concurrent subscriptions is capped per connection: `max_subscriptions_new_head` (`evm.max_subscriptions_new_head`) caps `newHeads` subscriptions, and `max_subscriptions_logs` (`evm.max_subscriptions_logs`, default 1000) caps concurrent `logs` subscriptions. Once the logs cap is reached, a new `logs` subscription is rejected with `no new subscription can be created`. **Parameters:** From 5e373a9b4be11ab6095a7cd10ebac941cd7ef6d6 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:11:28 +0000 Subject: [PATCH 06/41] docs: A new 'littidx' backend option is now accepted for the receipt-store configuration, adding a LittDB-based receipt store with a pebble tag index for eth_getLogs filtering. (sei-protocol/sei-chain#3620) --- node/giga-storage-migration.mdx | 11 +++++++---- node/node-operators.mdx | 21 ++++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/node/giga-storage-migration.mdx b/node/giga-storage-migration.mdx index 76ce52f..15ce5b9 100644 --- a/node/giga-storage-migration.mdx +++ b/node/giga-storage-migration.mdx @@ -419,7 +419,10 @@ In the `localnode` and `rpcnode` configuration scripts, setting `RECEIPT_BACKEND` environment variable explicitly. The explicit value takes precedence over the default. -`pebbledb` (also called `pebble`) is now the only supported receipt-store -backend. The former `parquet` option was removed. A `RECEIPT_BACKEND=parquet` -setting (or `rs-backend = "parquet"` in `app.toml`) is rejected with an error -(`unsupported receipt-store backend "parquet"; supported: pebbledb`). +The supported receipt-store backends are `pebbledb` (also called `pebble`) and +`littidx`. The `littidx` backend stores receipt bodies in LittDB with a pebble +tag index for `eth_getLogs` filtering, honoring the `KeepRecent` and +`PruneIntervalSeconds` config for retention and pruning. The former `parquet` +option was removed. An unsupported value for `RECEIPT_BACKEND` (or +`rs-backend` in `app.toml`) is rejected with an error +(`unsupported receipt-store backend "parquet"; supported: pebbledb, littidx`). diff --git a/node/node-operators.mdx b/node/node-operators.mdx index f9ea999..d455104 100644 --- a/node/node-operators.mdx +++ b/node/node-operators.mdx @@ -106,15 +106,26 @@ ss-backend = "pebbledb" ss-keep-recent = 100000 [receipt-store] -# Storage backend for EVM transaction receipts. pebbledb (aka pebble) is the -# only supported backend. +# Storage backend for EVM transaction receipts. Supported backends: pebbledb +# (aka pebble) and littidx. rs-backend = "pebbledb" ``` -`pebbledb` (also called `pebble`) is the only supported receipt-store backend. -The setting `rs-backend = "parquet"` (or `RECEIPT_BACKEND=parquet`) is rejected -with the error `unsupported receipt-store backend "parquet"; supported: pebbledb`. +The receipt-store backend accepts `pebbledb` (also called `pebble`) and +`littidx`. An unsupported value such as `rs-backend = "parquet"` (or +`RECEIPT_BACKEND=parquet`) is rejected with the error `unsupported receipt-store +backend "parquet"; supported: pebbledb, littidx`. + +`littidx` is a LittDB-based backend that stores receipt bodies in LittDB's +immutable append-only segments alongside a PebbleDB tag index used to answer +`eth_getLogs` filters without scanning whole blocks. It honors the same +`KeepRecent` (derived from `min-retain-blocks`) and `prune-interval-seconds` +retention and pruning settings as the PebbleDB backend. Because receipt bodies +are buffered in memory and flushed periodically rather than written through a +WAL, a hard crash can lose the most recently written receipt bodies; this is an +accepted tradeoff for auxiliary, non-consensus RPC data. + In the localnode and rpcnode configuration scripts, if you enable Giga Storage (`GIGA_STORAGE=true`) and `RECEIPT_BACKEND` is not set, the receipt backend defaults to `pebble`. The former `receipt-store.tx-index-backend` config field From 648c233d9adfef98592cc7c3d7869b830ea0c4cf Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:12:37 +0000 Subject: [PATCH 07/41] docs: The eth_createAccessList JSON-RPC endpoint now enforces a request rate limiter and can reject requests with a 'server busy' error when the limit is exceeded. (sei-protocol/sei-chain#3646) --- evm/reference.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/reference.mdx b/evm/reference.mdx index 70f09b2..f5ad4a0 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -1119,7 +1119,7 @@ The `filter` object applies only to `logs` subscriptions. For `newHeads`, pass t **Supported.** Generates an EIP-2930 access list (and gas used) for a transaction. -**Sei-specific behavior:** Defaults to the pending block tag, as geth does. If a VM error occurs during simulation, the method reports it in the result's 'error' field instead of failing the RPC. +**Sei-specific behavior:** Defaults to the pending block tag, as geth does. If a VM error occurs during simulation, the method reports it in the result's 'error' field instead of failing the RPC. A fail-fast request limiter may reject the call with 'eth_createAccessList rejected due to rate limit: server busy' when the limiter is saturated, as with `eth_call`, `eth_estimateGas`, and `eth_estimateGasAfterCalls`. **Parameters:** From 3b24f946a7be97c992dc841000ffda9b245a4c9c Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:13:45 +0000 Subject: [PATCH 08/41] docs: Adds a new node config field `hash-vault-disabled-unsafe` that lets operators disable the HashVault app-hash equivocation guard, and introduces HashVault recovery guidance for startup panics involving hash mismatches. (sei-protocol/sei-chain#3602) --- node/technical-reference.mdx | 22 ++++++++++++++++++++ node/troubleshooting.mdx | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 0b42f0b..03c4214 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -263,6 +263,28 @@ Any message whose fields exceed these limits is rejected at decode time, so an o Giga replaces the CometBFT mempool, so Giga does not support the `unsafe_flush_mempool` RPC endpoint. The endpoint returns `unsafe_flush_mempool is not supported with autobahn mempool`. + + +#### HashVault app-hash equivocation guard + +When a node runs under Autobahn (GigaRouter), the GigaRouter builds and owns an app-hash equivocation guard called HashVault. As each finalized height is executed, HashVault records that height's committed app hash before the implied state is committed. If the node ever attempts to commit a different app hash for a height it has already finalized, HashVault detects the conflict and halts the node. This protects a validator against externalizing two different app hashes for the same height, which could otherwise lead to slashing. + +HashVault is enabled by default on both Autobahn validators and fullnodes. It stores committed app hashes in a durable Pebble DB under the Autobahn persistent state directory, at `/hashvault` (see the `--persistent-state-dir` flag and the `PersistentStateDir` config field above). When Autobahn runs in memory only (no persistent state directory, for example in tests), HashVault falls back to a no-op with no equivocation protection. + + + If a node hits a startup panic reporting a HashVault app-hash mismatch, **do not restart it without human investigation** — the node attempted to change its mind about an already-finalized height. The panic and the preceding HashVault error log the conflicting hashes and the on-disk HashVault data directory (`hashVaultDir`). + + Only if you are certain there is no real equivocation, you can recover by stopping the node, deleting the HashVault data directory shown in the panic message, and restarting. **This removes equivocation protection**: if the node then commits a conflicting hash for an already-finalized height, the validator may be slashed. + + +You can disable HashVault entirely with the top-level `hash-vault-disabled-unsafe` field in `config.toml` (default `false`). This is an explicit, last-resort operator decision to run **without** equivocation protection; a node started with it enabled logs error-level warnings on every startup. Prefer the recovery steps above — only set `hash-vault-disabled-unsafe = true` if you are very sure the stored hashes are wrong and you keep hitting the same panic on new blocks. + +```toml +# config.toml — top-level key, before any [section] header. +# Leave false to keep equivocation protection enabled (recommended). +hash-vault-disabled-unsafe = false +``` + ### Key management Proper key management is critical for security. Use these commands to manage diff --git a/node/troubleshooting.mdx b/node/troubleshooting.mdx index 7135cff..46bf601 100644 --- a/node/troubleshooting.mdx +++ b/node/troubleshooting.mdx @@ -427,3 +427,43 @@ failed to initialize database: resource temporarily unavailable This means that you did not shut down the node properly. In that case, try to shut down or kill the `seid` process directly. If this does not help, restart your machine. Then try the rollback steps again. + + + +## HashVault app-hash equivocation panic + +Autobahn validators and fullnodes run an app-hash equivocation guard called HashVault, enabled by default. It stores the app hashes the node has committed in a durable database under `/hashvault`. If the node is ever about to commit a different app hash for a height it has already finalized, HashVault halts the node to prevent it from "changing its mind" about a finalized block. + +When the guard trips, you will see a panic in the logs similar to: + +```text +Hashvault detected app hash mismatch; node attempted to change its mind. DO NOT RESTART WITHOUT HUMAN INVESTIGATION. ... +blockHeight=[HEIGHT] existingHex=[...] incomingHex=[...] hashVaultDir=[/path/to/hashvault] +``` + + +**Do not restart the node without human investigation.** A HashVault mismatch can indicate a genuine equivocation. Restarting blindly, or removing the guard, can cause your validator to externalize a conflicting hash for an already-finalized height, which risks **slashing**. + + +**Investigate first.** Determine whether the mismatch reflects a real equivocation (for example, the same validator key running on more than one machine, or committed state that diverged from what the vault recorded) or a benign situation such as an out-of-band rollback or restore that left the committed app state inconsistent with the vault's history. + +### Recovery (only if you are certain there is no real equivocation) + +If, and only if, you are certain the stored hashes are wrong and there is no real equivocation, you can bypass the guard: + +1. **Stop the node.** +2. **Delete the HashVault data directory** shown as `hashVaultDir` in the panic message (by default `/hashvault`). +3. **Restart the node.** It starts with an empty equivocation history and re-commits hashes as it re-executes blocks. + +Deleting this directory removes equivocation protection for the heights it covered. If the node then commits a conflicting hash for a height it has already finalized, the validator may be slashed. + +### Last-resort: disabling HashVault + +If you repeatedly hit the same panic on new blocks and are very sure the stored hashes are totally wrong, you can run the node with the guard disabled by setting the top-level `hash-vault-disabled-unsafe` field in `config.toml`: + +```toml +# config.toml (top-level, before any [section] header) +hash-vault-disabled-unsafe = true +``` + +This runs the node **without any app-hash equivocation protection** and logs error-level warnings on every startup. The default is `false`, and you should re-enable the guard (set it back to `false` or remove the line) as soon as the underlying issue is resolved. From 1a0e2c2b2b2b185baa70f74fddabd8b7619c2d89 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:14:19 +0000 Subject: [PATCH 09/41] docs: Adds a new receipt-store config field/flag `receipt-store.log-filter-parallelism` (default 16) that bounds how many blocks a single eth_getLogs query scans concurrently in the littidx backend. (sei-protocol/sei-chain#3652) --- node/advanced-config-monitoring.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index c46da6d..e504f0c 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -614,6 +614,19 @@ enable-read-write-metrics = true ``` + +## Tuning littidx eth_getLogs parallelism + +When the receipt store uses the `littidx` backend, each `eth_getLogs` query scans the requested block range across a bounded worker pool instead of one block at a time. Per-block tag index scans and litt body reads are independent, so fanning them across multiple workers reduces latency on wide-range queries while results stay in strict `(block, txIndex)` order. + +The `log-filter-parallelism` config key under `[receipt-store]` in `app.toml` bounds how many blocks a single query scans concurrently. It defaults to `16` and applies only to the `littidx` backend. A value `<= 0` falls back to the default. + +```toml +[receipt-store] +log-filter-parallelism = 16 +``` + + ## LittDB OpenTelemetry metrics LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` instead of a private Prometheus client. When `MetricsEnabled` is set, LittDB configures a Prometheus exporter on the global provider and serves `/metrics` on `MetricsPort` (default `9101`). The `MetricsNamespace` and `MetricsRegistry` config fields were removed, and all metric names use a fixed `litt_` prefix. From 6a08627c8a380a8b7d65e47b943d9630b1fc63e7 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:15:09 +0000 Subject: [PATCH 10/41] docs: The debug_traceStateAccess JSON-RPC method now honors the configured TraceTimeout and caps retained per-module state-access payload at 4 MiB (adding a 'truncated' field to the response), changing behavior to prevent OOM and unbounded traces. (sei-protocol/sei-chain#3653) --- evm/reference.mdx | 27 +++++++++++++++++++++++++++ evm/tracing/index.mdx | 2 ++ 2 files changed, 29 insertions(+) diff --git a/evm/reference.mdx b/evm/reference.mdx index f5ad4a0..7f9b6f8 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -1248,6 +1248,33 @@ The max block lookback guard (`max_trace_lookback_blocks`) applies. If a request } ``` +#### `debug_traceStateAccess` + +**Supported.** Replays a transaction by hash and returns the per-module state (KVStore) accesses it made, used for prestate derivation and state-access analysis. + +**Sei-specific behavior:** Available over HTTP only. The `max_trace_lookback_blocks` historical guard applies (see `debug_traceTransaction`). The call honors the configured `TraceTimeout`: replay and the subsequent prestate/trace serialization bail out when the trace deadline elapses, failing with a `context deadline exceeded` error instead of running unbounded. This prevents long-running replays from consuming resources without limit. + +Each module entry in the response carries a `truncated` boolean. Sei caps the key/value payload retained per module in the state-access log at 4 MiB. Once that cap is reached, the module's log is marked `truncated: true` and further accesses are dropped from the retained log, bounding a pathological transaction (such as a huge iterator scan) that would otherwise grow the response without limit. When a module is truncated, its `reads` and `has` entries reflect only the retained prefix, but its `stats` (operation counts and durations) stay complete and accurate, because they are accumulated at record time regardless of truncation. + +**Parameters:** + +| # | Name | Type | Description | +| :- | :- | :- | :- | +| 1 | `hash` | DATA, 32 bytes | Transaction hash to trace. | + +**Example request:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "debug_traceStateAccess", + "params": [ + "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060" + ] +} +``` + #### `debug_traceCall` **Supported.** Executes and traces a call against a block's state without creating a transaction. diff --git a/evm/tracing/index.mdx b/evm/tracing/index.mdx index 4edd7ec..72ea160 100644 --- a/evm/tracing/index.mdx +++ b/evm/tracing/index.mdx @@ -56,6 +56,8 @@ Debug tracing is your primary tool to understand how EVM transactions execute on | `debug_traceBlockByNumber` | Trace an entire block | Block-level analysis | | `debug_traceCall` | Simulate and trace | Testing before execution | | `debug_traceStateAccess` | State access patterns | Performance optimization | + +`debug_traceStateAccess` respects the node's configured `TraceTimeout`. If the trace deadline elapses during replay or serialization, the call aborts with a `context deadline exceeded` error instead of running unbounded. Additionally, the per-module state-access log is capped at 4 MiB of retained key/value payload. When a module exceeds this cap, its trace dump sets `"truncated": true`: the `reads`/`has` fields then reflect only the retained prefix, while the `stats` (operation counts and durations) remain complete and accurate. | `debug_traceTransactionProfile` | Trace plus timing/store-access profiling | Latency breakdown and DB-access analysis | ## Transaction analysis example From 22541886e9cd5e6368b4f2517fd131fb067a3b39 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:16:01 +0000 Subject: [PATCH 11/41] docs: Adds two new EVM RPC config fields (max_request_body_bytes and max_concurrent_request_bytes) plus a new Prometheus metric to bound JSON-RPC request body sizes and concurrent request memory via pre-decode admission control. (sei-protocol/sei-chain#3648) --- node/advanced-config-monitoring.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index e504f0c..dc93ac8 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -517,6 +517,7 @@ The EVM RPC layer emits OpenTelemetry metrics through the process-wide `MeterPro | `evmrpc_websocket_connects_total` | Counter | Number of new WebSocket connections. | | `evmrpc_redirected_requests_total` | Counter | Number of EVM RPC requests forwarded to another validator. Labeled by `endpoint` and `connection`. | | `evmrpc_historical_debug_trace_attempts_total` | Counter | Number of `debug_trace*` requests that target historical blocks beyond the configured max block lookback. Labeled by `endpoint` and `connection`. | +| `evmrpc_requests_rejected_total` | Counter | Number of HTTP JSON-RPC requests rejected by pre-decode admission control. Labeled by `reason`, which is `oversize` when a request body exceeds the configured `max_request_body_bytes` cap (rejected with HTTP 413) or `busy` when the `max_concurrent_request_bytes` budget is exhausted (rejected with HTTP 429). | The `evmrpc_request_latency_seconds` histogram carries these labels: From bc4e0a6ea5a521b4a06e6b3c659f2758bcb10f0c Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:17:19 +0000 Subject: [PATCH 12/41] docs: Adds a per-block hash logger feature to the state-commit store with five new app.toml config fields (sc-hash-logger-*), enabled by default, for recording named block hashes for debugging/forensics. (sei-protocol/sei-chain#3647) --- node/advanced-config-monitoring.mdx | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index dc93ac8..d9590ff 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -628,6 +628,36 @@ log-filter-parallelism = 16 ``` + +## Per-block hash logger + +The state-commit store can record a per-block CSV of named block hashes to disk as a debugging and forensics tool. For each committed block it logs the memIAVL per-module and root hashes, the flatKV per-DB and root hashes, the application hash, the Tendermint block hash, the result hash (the merkle root over the block's deterministic transaction results, equal to the next block's `LastResultsHash`), and the changeset hash. Recording the same hashes under one block number on every node makes it straightforward to pinpoint where and when block-hash computation diverges across nodes. + +The feature is **enabled by default**. By default it writes files into a `hash.log` directory under the state-commit store's data directory (that is, `/data/hash.log`). + +### Configuration + +The hash logger is configured with five `app.toml` fields under `[state-commit]`: + +| Config key | Default | Description | +| --- | --- | --- | +| `sc-hash-logger-enable` | `true` | Turns per-block hash logging on or off. | +| `sc-hash-logger-directory` | empty | Directory for hash log files. When empty, defaults to `/data/hash.log`. | +| `sc-hash-logger-blocks-to-retain` | `0` | Number of most-recent blocks to keep on disk. `0` disables block-count retention (the disk-size cap is then the only bound). | +| `sc-hash-logger-target-file-size` | `16 MB` | Size in bytes a log file may reach before it is sealed and a new one is opened. Must be greater than `0`. | +| `sc-hash-logger-max-disk-size` | `16 GB` | Backstop cap in bytes on the total size of sealed log files. `0` disables the disk-size cap (block-count retention is then the only bound). | + +Retention is disk-driven by default: up to roughly 16 GiB of sealed files are kept, with block-count retention disabled. If both `sc-hash-logger-blocks-to-retain` and `sc-hash-logger-max-disk-size` are set to `0`, no retention bound applies and the logs grow without limit — a deliberate operator choice. + +For example, to disable the hash logger, add the following to `app.toml`: + +```toml +[state-commit] +sc-hash-logger-enable = false +``` + + + ## LittDB OpenTelemetry metrics LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` instead of a private Prometheus client. When `MetricsEnabled` is set, LittDB configures a Prometheus exporter on the global provider and serves `/metrics` on `MetricsPort` (default `9101`). The `MetricsNamespace` and `MetricsRegistry` config fields were removed, and all metric names use a fixed `litt_` prefix. From 04a4cf4635db054f2f0d89254a7d357a96152fe9 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:17:54 +0000 Subject: [PATCH 13/41] docs: LittDB's garbage collection was rewritten to run asynchronously with a durable gc-watermark file, adding six new config fields, changing the default GC period, and removing the CacheAwareGet API. (sei-protocol/sei-chain#3645) --- node/advanced-config-monitoring.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index d9590ff..7232a54 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -668,7 +668,7 @@ LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` | --- | --- | --- | --- | | `litt_table_size_bytes` | Gauge | bytes | The size of individual tables in the database. | | `litt_table_key_count` | Gauge | count | The number of keys in individual tables in the database. | -| `litt_open_iterator_count` | Gauge | count | The number of currently open iterators for individual tables in the database. A persistently nonzero value indicates a leaked iterator, which suspends garbage collection for the table. | +| `litt_open_iterator_count` | Gauge | count | The number of currently open iterators for individual tables in the database. A persistently nonzero value indicates a leaked iterator. Garbage collection proceeds while iterators are open — an iterator pins its snapshot segments via reservations so their files survive until it closes — so a leaked iterator pins those segment files on disk indefinitely rather than suspending garbage collection. | | `litt_bytes_read` | Counter | bytes | The number of bytes read from disk since startup. | | `litt_keys_read` | Counter | count | The number of keys read from disk since startup. | | `litt_cache_hits` | Counter | count | The number of cache hits since startup. | From a84ba06624b86a0b072c1a7ef6ffa67ef9ee2aff Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:05:57 +0000 Subject: [PATCH 14/41] docs: Introduces an experimental SEI_CONFIG_MANAGER environment variable that selects the config manager for seid; unset/'legacy' uses the existing path, 'v2' is a not-yet-implemented stub that errors, and invalid values fail hard. (sei-protocol/sei-chain#3671) --- node/technical-reference.mdx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 03c4214..9b56933 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -36,6 +36,27 @@ seid tendermint show-validator seid query node info ``` + +#### Experimental config manager selection (`SEI_CONFIG_MANAGER`) + +`seid` reads the experimental `SEI_CONFIG_MANAGER` environment variable to select which configuration manager resolves a node's configuration at startup. The value is matched exactly — it is not trimmed or case-folded. + +- Unset or `legacy`: uses the existing legacy config loader. This is the default and leaves the configuration path unchanged. +- `v2`: selects the new sei-config-backed manager. This manager is not yet implemented and fails fast with a hard error (`SEI_CONFIG_MANAGER=v2 not yet implemented`) rather than silently falling back to the legacy path, so a `v2` invocation is observable. +- Any other value: `seid` refuses to start and reports an `invalid SEI_CONFIG_MANAGER` error naming the legal tokens (unset, `legacy`, or `v2`). There is no silent fallback. + +```bash +# Default behavior (legacy loader) — no variable set +seid start + +# Equivalent to the default +SEI_CONFIG_MANAGER=legacy seid start + +# Selects the v2 manager, which currently returns a hard error +SEI_CONFIG_MANAGER=v2 seid start +``` + + #### Freeze mode (`--freeze-height`) As of v6.6.3, you can put a full node into read-only freeze mode at a specified block height. Use the `--freeze-height` start flag or the corresponding `freeze-height` field in `app.toml`. Freeze mode exists for historical RPC nodes. A node frozen at an upgrade height keeps running the pre-upgrade binary and keeps serving the state that binary produced. It does not shut down at the boundary or execute the upgrade block with code that no longer matches that state. From d5f10e8ecbf9fb62304511a53b30cfc3fbb736f7 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:06:41 +0000 Subject: [PATCH 15/41] docs: Adds new gRPC server config fields in app.toml (max-recv-msg-size, max-open-connections, keepalive/connection-age settings) with bounded defaults applied even when absent from older config files. (sei-protocol/sei-chain#3641) --- node/technical-reference.mdx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 9b56933..3d8f0b7 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -385,10 +385,40 @@ ss-enable = true ss-backend = "pebbledb" ss-keep-recent = 100000 ss-prune-interval = 600 + +# gRPC server configuration +[grpc] +enable = true +address = "0.0.0.0:9090" +# Maximum message size in bytes the server can receive. Bounds per-request memory +# allocation before the rate limiter fires. Default 4 MB (4194304). +max-recv-msg-size = 4194304 +# Maximum number of simultaneous open connections. 0 means unlimited. +max-open-connections = 1000 +# Duration after which an idle connection is closed. 0 means infinity. +max-connection-idle = "5m0s" +# Maximum duration a connection may exist before it is closed. 0 means infinity. +max-connection-age = "0s" +# Additive grace period after max-connection-age during which the connection is +# forcibly closed. 0 means infinity. +max-connection-age-grace = "0s" +# Interval after which, with no activity, the server pings the client for liveness. +keepalive-time = "2h0m0s" +# Duration the server waits for a keepalive ping ack before closing the connection. +keepalive-timeout = "20s" +# Minimum interval a client must wait between keepalive pings; more frequent pings +# are penalized. +keepalive-min-time = "5m0s" +# Whether the server allows keepalive pings even when there are no active streams. +keepalive-permit-without-stream = false ``` + + The `[grpc]` server applies bounded defaults even when these keys are absent from an older `app.toml`. A node upgrading with a config file that predates these fields gets the in-code defaults (for example, a 4 MB `max-recv-msg-size`, `1000` `max-open-connections`, and a `5m` `max-connection-idle`) rather than running with unlimited connections or message sizes. A negative duration override for a keepalive or connection-age field is treated as a misconfiguration and clamped back to its safe default. The `max-connection-age` and `max-connection-age-grace` fields default to `0` (gRPC's "infinity"), and the keepalive-time/timeout/min-time defaults mirror gRPC's own defaults, so they are opt-in and do not change behavior unless configured. + + ### Config.toml parameters The `config.toml` file controls the core consensus engine and networking: From 2ab34ae5d7aed4d493665230c30c4571fc20feb3 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:07:31 +0000 Subject: [PATCH 16/41] docs: The seidb dump-flatkv command gains new flags (--lthash, --lthash-only, --read-limit-mb) for computing/verifying lattice hashes and throttling scan throughput, and --output-dir is no longer required when using --lthash-only. (sei-protocol/sei-chain#3665) --- node/technical-reference.mdx | 36 ++++++++++++++++++++++++++++++++++++ node/troubleshooting.mdx | 23 +++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 3d8f0b7..491f0fd 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -201,6 +201,42 @@ seidb evm-logical-digest --backend memiavl \ Two backends match when the FlatKV `FINAL_DIGEST` equals the memIAVL `FINAL_DIGEST`. FlatKV also writes an internal migration-version marker row, which a memiavl-only node never owns. The command automatically omits that row from the final comparison. + + +#### Dumping FlatKV state and verifying its lattice hash + +The `dump-flatkv` subcommand dumps every physical `(key, value)` pair of a FlatKV store into per-bucket files, formatted to match `dump-iavl` so the same diff tooling works on both. It can optionally also compute the per-bucket and total LtHash (lattice hash) over the scanned state and verify that total against the committed root recorded in snapshot metadata. + +The command opens an independent read-only clone of the store (the snapshot is hard-linked and the changelog WAL is replayed into a temporary directory under the data dir), so it is safe to run against a live, block-producing node. The scan is throttled by `--read-limit-mb` so a dump against a running node does not starve the chain of disk bandwidth. + +```bash +# Latest version, all buckets, default 64 MiB/s throttle, LtHash + verify. +seidb dump-flatkv -d /.sei/data/state_commit/flatkv -o /tmp/flatkv-dump + +# Pin a specific height. +seidb dump-flatkv -d /.sei/data/state_commit/flatkv -o /tmp/flatkv-dump \ + --height 216890000 + +# Offline / idle disk: go full speed, skip LtHash. +seidb dump-flatkv -d /.sei/data/state_commit/flatkv -o /tmp/flatkv-dump \ + --read-limit-mb 0 --lthash=false + +# Verify the FlatKV lattice hash only, without writing key/value dump files. +seidb dump-flatkv -d /.sei/data/state_commit/flatkv --lthash-only +``` + +The command accepts these flags: + +- `--db-dir` (`-d`): The FlatKV data directory (the directory containing `current/`, `snapshot-*`, and `changelog/`). Required. +- `--output-dir` (`-o`): Where to write the per-bucket dump files (one file per bucket). Required unless `--lthash-only` is set. +- `--height`: The target version. `0` (the default) selects the latest available version by replaying the WAL to the tip. +- `--bucket` (`-b`): Restrict the on-disk dump to a single bucket (`account`, `code`, `storage`, or `legacy`). The default is all buckets. This only filters which hex files are written; the full keyspace is always scanned, and `--lthash` always covers all four buckets, so the LtHash total stays valid. +- `--lthash`: Compute the per-bucket and total LtHash over the scanned state and verify the total against committed snapshot metadata. Default `true`. The LtHash is always computed over all buckets regardless of `--bucket`, so the total matches the node's committed LtHash. +- `--lthash-only`: Compute and verify the LtHash without writing any bucket dump files. Default `false`. It requires `--lthash=true`, cannot be combined with `--bucket`, and does not require `--output-dir`. +- `--read-limit-mb`: Throttle the scan to at most this many MiB/s of `(key+value)` bytes read. Default `64`. A value of `0` disables throttling. Keep it low (default or less) on a shared or live node; raise it only for offline runs on idle disks. Negative values are rejected. + +With `--lthash`, the command prints an `LtHash (lattice hash)` block listing each bucket's count and checksum and the `TOTAL`, followed by a verification line comparing the re-scanned total against the committed snapshot metadata. A match prints `PASS`; a mismatch prints `FAIL` and the command exits non-zero. Verification is skipped when the selected snapshot predates LtHash metadata (its committed hash then covers only replayed WAL deltas, not full state) or when no committed LtHash is recorded at that version. + The command accepts these flags: - `--backend`: The backend to read (`flatkv` or `memiavl`). diff --git a/node/troubleshooting.mdx b/node/troubleshooting.mdx index 46bf601..7018597 100644 --- a/node/troubleshooting.mdx +++ b/node/troubleshooting.mdx @@ -114,9 +114,14 @@ systemctl restart seid The `dump-flatkv` command accepts these flags: - `--db-dir` (`-d`): The FlatKV database directory. -- `--output-dir` (`-o`): The output directory, with one file for each bucket. +- `--output-dir` (`-o`): The output directory, with one file for each bucket. Required unless `--lthash-only` is set. - `--height`: The FlatKV target version. The default, `0`, selects the latest available version. -- `--bucket` (`-b`): Restrict the dump to a single bucket (`account`, `code`, `storage`, or `legacy`). The default is all buckets. +- `--bucket` (`-b`): Restrict the dump to a single bucket (`account`, `code`, `storage`, or `legacy`). The default is all buckets. This only filters which files are written; the full keyspace is always scanned, and the LtHash always covers all four buckets. +- `--lthash`: Also compute the per-bucket and total LtHash (lattice hash) over the scanned state, and verify the total against the committed root recorded in snapshot metadata. The default is `true`. A mismatch exits non-zero. +- `--lthash-only`: Compute and verify the LtHash without writing any bucket dump files. Requires `--lthash=true`, and does not require `--output-dir`. The default is `false`. +- `--read-limit-mb`: Throttle the scan to at most this many MiB/s of (key+value) bytes read, so a dump against a running node does not starve the chain of disk bandwidth. The default is `64`. Set `0` for unlimited. Keep it at the default or lower on a shared/live node; raise it only for offline runs on idle disks. + +Because `dump-flatkv` opens an independent read-only clone of the store (the snapshot is hard-linked and the changelog WAL is replayed into a temp directory), it is safe to run against a live, block-producing node, and the `--read-limit-mb` throttle prevents it from starving the node of disk bandwidth. For example, to dump only the `storage` bucket at a specific version: @@ -124,6 +129,20 @@ For example, to dump only the `storage` bucket at a specific version: seidb dump-flatkv --db-dir $HOME/.sei/data/state_commit/flatkv --output-dir /home/ubuntu/flatkv-dump --height 12345678 --bucket storage ``` +To verify the FlatKV lattice hash against the committed snapshot metadata without writing any key/value dump files: + +```bash +seidb dump-flatkv --db-dir $HOME/.sei/data/state_commit/flatkv --lthash-only +``` + +For an offline run on an idle disk, go full speed and skip the LtHash: + +```bash +seidb dump-flatkv --db-dir $HOME/.sei/data/state_commit/flatkv --output-dir /home/ubuntu/flatkv-dump --read-limit-mb 0 --lthash=false +``` + +With `--lthash`, the output prints a per-bucket count and checksum plus a `TOTAL` LtHash, followed by a `LtHash verification vs snapshot metadata` PASS/FAIL line. Verification is skipped (rather than failing) when the selected snapshot predates LtHash metadata, since the committed hash then covers only replayed WAL deltas rather than full state. + ### Comparing EVM state between memIAVL and FlatKV When you debug an AppHash mismatch that involves EVM state, a byte-for-byte physical dump can diverge between backends, even when the underlying state is identical. This happens because every FlatKV value embeds a per-key block-height stamp (the height at which the key was last written or migrated). On a freshly migrated node, this stamp differs from the memIAVL leaf versions. From 2b6d1bef04e8e1e38612c3a3658f8e23bb1a14a4 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:08:44 +0000 Subject: [PATCH 17/41] docs: OTEL/Prometheus metrics now include a `chain_id` constant label on every emitted metric series, which affects dashboards and alerting queries for node operators. (sei-protocol/sei-chain#3692) --- node/advanced-config-monitoring.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index 7232a54..277a624 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -509,6 +509,12 @@ scrape_configs: The EVM RPC layer emits OpenTelemetry metrics through the process-wide `MeterProvider` (for example, a Prometheus exporter). It emits these metrics in parallel with the legacy `sei_*` metrics, so you can migrate dashboards incrementally. + + + +Every OpenTelemetry metric series exported through the Prometheus exporter (the `sei_chain` namespace, including the `evmrpc_*`, `flatkv_*`, `litt_*`, and `pebble_*` metrics documented below) now carries a `chain_id` constant label derived from the node's chain ID. The label is applied to every emitted series, so dashboards and alerting queries can group or filter by chain — for example when a single Prometheus instance scrapes nodes on more than one chain. Update any PromQL that aggregates across series (for example `sum(...) without (...)` or `by (...)` clauses) to account for the new label, and add a `chain_id` match where you want to scope a query to a single chain. + + ### Available EVM RPC metrics | Metric | Type | Description | From 6b3e429c31735ceec785675359cd3253c9e237a6 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:11:01 +0000 Subject: [PATCH 18/41] docs: Adds a new 'historical_replay' build tag that produces a consensus-unsafe binary variant using a lenient tx decoder (no body-bloat rejection) for replaying historical blocks. (sei-protocol/sei-chain#3691) --- node/technical-reference.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 491f0fd..95f29fb 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -603,3 +603,21 @@ $HOME/.sei/ This reference guide gives essential technical information for operating Sei nodes and validators. For API documentation and other detailed specifications, see the relevant sections of this documentation. + + + +## Build tags + +### `historical_replay` + +The `historical_replay` build tag produces a consensus-unsafe `seid` variant intended solely for replaying historical blocks. When you build with this tag, the block-execution transaction decoder is swapped for a lenient protobuf decoder (`NewTxConfigWithoutBodyBloatRejection`) that does **not** reject non-canonical (body-bloat) transaction bodies. This lets a node decode and execute historical blocks whose transaction bodies predate strict body-bloat rejection. + +```bash +# Build a historical-replay seid variant +go build -tags historical_replay ... +``` + + + A binary built with the `historical_replay` tag is **consensus-unsafe** and must only be used for historical replay. Never run it on live or production paths. The lenient decoder is compiled in only when the tag is present, so an untagged (production) binary can never reach it — the lenient execution decoder stays off every mempool, `CheckTx`, and `DeliverTx` path. Do not use a `historical_replay` build to validate, produce blocks, or serve live traffic. + + From bf9c1033bb5c002e81568be7c24aadcfdf8d09cf Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:11:48 +0000 Subject: [PATCH 19/41] docs: Adds a new EVM RPC config field `evm.max_trace_struct_log_bytes` that caps retained struct-logger output per traced transaction on default debug_trace* endpoints, defaulting to 32 MiB (0 = unlimited). (sei-protocol/sei-chain#3677) --- evm/tracing/index.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/evm/tracing/index.mdx b/evm/tracing/index.mdx index 72ea160..d4cd08c 100644 --- a/evm/tracing/index.mdx +++ b/evm/tracing/index.mdx @@ -445,6 +445,25 @@ These `[evm]` fields in `app.toml` control trace baking: Trace baking adds a persistent on-disk store at `/data/trace_db` and increases disk usage. The node flushes the store's write-ahead log when it shuts down cleanly. + + +## Struct-logger output cap + +The default `debug_trace*` endpoints (`debug_traceCall`, `debug_traceTransaction`, and `debug_traceBlockByNumber`/`debug_traceBlockByHash`) use the built-in struct logger when no custom tracer is supplied. Traces that read many distinct storage slots can retain large amounts of output, so the node bounds the retained struct-logger output per traced transaction. + +The `max_trace_struct_log_bytes` field under `[evm]` in `app.toml` controls this cap: + +| Field | Default | Description | +| ----- | ------- | ----------- | +| `max_trace_struct_log_bytes` | `33554432` (32 MiB) | Bounds the retained struct-logger output, in bytes, per traced transaction on the default `debug_trace*` endpoints. Set to `0` for unlimited (upstream geth behavior). | + +Behavior notes: + +- The bound is applied **per transaction**, not per RPC call. Because geth builds a fresh struct logger for each transaction, a `debug_traceBlock*` call over N transactions can retain up to N times this value (and the parallelized block-trace path holds several concurrent traces live). +- A caller-supplied `Limit` larger than `max_trace_struct_log_bytes` is clamped down to the configured value. +- A smaller caller-supplied `Limit` is honored unchanged. +- Custom tracers (for example `callTracer`, `prestateTracer`, or JavaScript tracers) are unaffected, as are requests when the cap is disabled (`0`). + ## Removed legacy trace filters The legacy `*ExcludeTraceFail` endpoints have been removed. For block tracing, use `debug_traceBlockByNumber` or `debug_traceBlockByHash`. For EVM receipts, use `eth_getTransactionReceipt`. There is no block or filter method to discover synthetic logs from Cosmos-originated transactions. If you already know a synthetic transaction hash, enable `sei_getTransactionReceipt` to get its receipt and logs. From 8f94f400ba20ccd93218dee400abfc9602c31487 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:12:50 +0000 Subject: [PATCH 20/41] docs: Adds a new `hashlog` command group to the seidb tool with `get-block` and `compare` subcommands for inspecting and diffing hash log archives produced by the hashlogger. (sei-protocol/sei-chain#3676) --- node/giga-storage-migration.mdx | 28 +++++++++++++++++++ node/technical-reference.mdx | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/node/giga-storage-migration.mdx b/node/giga-storage-migration.mdx index 15ce5b9..00d64a8 100644 --- a/node/giga-storage-migration.mdx +++ b/node/giga-storage-migration.mdx @@ -357,6 +357,34 @@ When the migration finishes, each node also emits a `migration complete` summary log line and `seidb_migration_*` OpenTelemetry counters for the migrated keys and bytes. +### Inspecting hash log archives + +If you enable the per-block hash logger, the `seidb hashlog` command group +provides read-only tools for inspecting the on-disk hash log archives it +produces, so you can read or diff them without writing Go. + +- `seidb hashlog get-block ` prints every hash recorded for a + single block. A block with more than one record indicates it was executed more + than once (for example after a rollback), and each execution's hashes are + reported separately. Add `--json` to emit JSON instead of human-readable text. + + ```bash copy + seidb hashlog get-block [--json] + ``` + +- `seidb hashlog compare ` reports blocks whose hashes + differ between two archives — useful for pinpointing where two nodes diverged. + By default only the columns that differ are shown; pass `--full` to show every + column for each differing block. Restrict the comparison to a block range with + `--low` and `--high` (both must be supplied together), cap the number of + reported diffs with `--max-diffs` (`-1` for all), and add `--json` to emit + JSON. When the result is truncated at `--max-diffs`, the command warns that + there may be more differing blocks. + + ```bash copy + seidb hashlog compare [--low N --high M] [--max-diffs N] [--full] [--json] + ``` + ### Importing EVM state from memIAVL into FlatKV The `seidb import-flatkv-from-memiavl` command populates a FlatKV store from an existing memIAVL tree. It is for nodes that move to the FlatKV EVM commit store without a state sync. Two safety properties matter: diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 95f29fb..76f6c95 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -237,6 +237,54 @@ The command accepts these flags: With `--lthash`, the command prints an `LtHash (lattice hash)` block listing each bucket's count and checksum and the `TOTAL`, followed by a verification line comparing the re-scanned total against the committed snapshot metadata. A match prints `PASS`; a mismatch prints `FAIL` and the command exits non-zero. Verification is skipped when the selected snapshot predates LtHash metadata (its committed hash then covers only replayed WAL deltas, not full state) or when no committed LtHash is recorded at that version. + + +#### Inspecting hash log archives + +The `hashlog` command group provides read-only tools for inspecting the on-disk hash log archives produced by the hashlogger. Use it to pull the hashes recorded for a single block or to diff two archives without writing any Go code. + +##### Printing a single block's hashes + +The `get-block` subcommand prints every hash recorded for a single block in a hash log archive. + +```bash +# Print the hashes recorded for a block in an archive +seidb hashlog get-block + +# Emit JSON instead of human-readable text +seidb hashlog get-block --json +``` + +If the block was executed more than once (for example, after a rollback that replayed it), the archive holds several records for that block and each execution's hashes are reported separately. A hash type that was registered but not recorded for the block prints as `` (or serializes to JSON `null`, which is distinguishable from an absent type). + +##### Comparing two archives + +The `compare` subcommand compares two hash log archives and reports the blocks whose hashes differ between them. + +```bash +# Compare two archives over their full range +seidb hashlog compare + +# Restrict the comparison to a block range (both flags required together) +seidb hashlog compare --low --high + +# Cap the number of differing blocks reported +seidb hashlog compare --max-diffs + +# Show every column for each differing block, and emit JSON +seidb hashlog compare --full --json +``` + +The command accepts these flags: + +- `--low`: The lowest block to compare (inclusive). Requires `--high`. +- `--high`: The highest block to compare (inclusive). Requires `--low`. The `--low` and `--high` flags are optional, but must be supplied together; a one-sided range fails with an error. +- `--max-diffs`: The maximum number of differing blocks to report. The default is `-1`, which reports all of them. When the output is truncated at the cap, the command warns that there may be more differing blocks. +- `--full`: Show every column of every record for each differing block. The default is a compact view that shows only the columns that differ. This is also the only sensible rendering when the record counts differ between the two sides (a rollback re-executed the block a different number of times), since there is no single pair of records to diff column by column. +- `--json`: Emit JSON instead of human-readable text. The compact-by-default and `--full` column filtering apply to JSON output as well. + +When the archives are identical over the compared range, the command reports that and exits. + The command accepts these flags: - `--backend`: The backend to read (`flatkv` or `memiavl`). From 3ca40c3b88d50969552bc5e5bf0eff4ad9265146 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:13:52 +0000 Subject: [PATCH 21/41] docs: The state-commit sc-write-mode config now accepts the legacy "cosmos_only" value from v6.4/v6.5 app.toml files, mapping it to the current "memiavl_only" routing for backward-compatible upgrades. (sei-protocol/sei-chain#3704) --- node/giga-storage-migration.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/node/giga-storage-migration.mdx b/node/giga-storage-migration.mdx index 00d64a8..5a9099f 100644 --- a/node/giga-storage-migration.mdx +++ b/node/giga-storage-migration.mdx @@ -242,6 +242,14 @@ flip target after `migrate_bank` reports complete on the node (migration version 3, all modules in FlatKV). Then restart with `sc-write-mode = "flatkv_only"` to reach the terminal steady state. + +For backward compatibility with v6.4/v6.5 `app.toml` files, the +`sc-write-mode` config field (and the `--sc-write-mode` flag) still accepts the +legacy value `"cosmos_only"`. It is parsed as equivalent to `memiavl_only`, so +an older config that pins `sc-write-mode = "cosmos_only"` resolves to the same +memIAVL-only routing and continues to work after upgrading, with no edit +required before the node starts. + A correctness bug in the WAL replay path is fixed. On replay (catchup, read-only clone, snapshot export, and state-sync restore), the bug dropped empty (zero-length) values written with no delete flag. This made the FlatKV state and From 0c2c6943d8b9de43bc2138504f9245c71077f42c Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:14:37 +0000 Subject: [PATCH 22/41] docs: LittDB adds new config fields (FlushChannelSize, ShardControlChannelSize, AutoFlushByteThreshold, KeymapManagerMaxBatchBytes, MetricsServeEndpoint) and tightens the maximum value size to 2^32-1 bytes (~4 GiB), rejecting larger values that previously could span the boundary. (sei-protocol/sei-chain#3683) --- node/advanced-config-monitoring.mdx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index 277a624..aa61cbb 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -666,7 +666,12 @@ sc-hash-logger-enable = false ## LittDB OpenTelemetry metrics -LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` instead of a private Prometheus client. When `MetricsEnabled` is set, LittDB configures a Prometheus exporter on the global provider and serves `/metrics` on `MetricsPort` (default `9101`). The `MetricsNamespace` and `MetricsRegistry` config fields were removed, and all metric names use a fixed `litt_` prefix. +LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` instead of a private Prometheus client. When `MetricsEnabled` is set, LittDB records its metrics into the global provider. How those metrics are exported depends on `MetricsServeEndpoint` (default `false`): + +- When `MetricsServeEndpoint` is `false` (the default), LittDB records into the already-configured global `MeterProvider` and leaves exporting to the embedding application. The application is responsible for standing up the Prometheus exporter and serving the registry. No exporter or `/metrics` server is created by LittDB, and `MetricsPort` is ignored. +- When `MetricsServeEndpoint` is `true`, LittDB stands up its own Prometheus exporter on the global provider and serves `/metrics` on `MetricsPort` (default `9101`). + +`MetricsPort` is ignored unless both `MetricsEnabled` and `MetricsServeEndpoint` are `true`. The `MetricsNamespace` and `MetricsRegistry` config fields were removed, and all metric names use a fixed `litt_` prefix. ### Available LittDB metrics From 908455569348c12905452f0ffc0da1339761919e Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:15:22 +0000 Subject: [PATCH 23/41] docs: The seid node app now reads pruning options from flags and applies them to baseapp, enabling pruning configuration (e.g., --pruning) to actually take effect. (sei-protocol/sei-chain#3725) --- node/node-operators.mdx | 19 +++++++++++++++++++ node/technical-reference.mdx | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/node/node-operators.mdx b/node/node-operators.mdx index d455104..19812d9 100644 --- a/node/node-operators.mdx +++ b/node/node-operators.mdx @@ -787,6 +787,25 @@ The generated comments above do not describe two behaviors. First, everything. Second, when `evm-ss-db-directory` is unset, nodes with an existing `/data/evm_ss` directory keep using that legacy path. New nodes default to `/data/state_store/evm/{backend}`. + + +### Cosmos SDK state pruning + +Separate from the SeiDB retention settings above (`min-retain-blocks`, +`ss-keep-recent`), the node also honors the standard Cosmos SDK pruning options +(#3618). These control how much historical application state the baseapp keeps +and are applied to the node via `baseapp.SetPruning`: + +- `pruning` — the pruning strategy. Accepts `default`, `nothing` (archiving + node; keep all states), `everything` (keep only the most recent state), or + `custom` (use the `pruning-keep-recent` and `pruning-interval` values below). +- `pruning-keep-recent` — with `pruning = "custom"`, the number of recent + heights of state to keep. +- `pruning-interval` — with `pruning = "custom"`, how often (in blocks) pruning + is run. + +These can be supplied as `app.toml` settings or as the equivalent `seid` +start flags (e.g. `--pruning`, `--pruning-keep-recent`, `--pruning-interval`). diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 76f6c95..0a7c093 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -470,6 +470,17 @@ ss-backend = "pebbledb" ss-keep-recent = 100000 ss-prune-interval = 600 +# Base-layer (IAVL/baseapp) pruning. These Cosmos SDK pruning settings are read +# from the start flags/config and applied to the node's baseapp, so they are +# honored when the node runs. `pruning` selects a strategy: "default", "nothing" +# (archive node, keep all states), "everything" (keep only recent states), or +# "custom" (use the pruning-keep-recent and pruning-interval values below). +pruning = "default" +# Number of recent heights to keep on disk. Used with pruning = "custom". +pruning-keep-recent = "0" +# How often (in blocks) to run a pruning pass. Used with pruning = "custom". +pruning-interval = "0" + # gRPC server configuration [grpc] enable = true From 8b59ed1a06edeb439c7d3a525a9d756bb99f8417 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:16:56 +0000 Subject: [PATCH 24/41] docs: Adds two new EVM RPC config fields (max_state_override_accounts, max_state_override_slots) that cap the size of state overrides in eth_call, eth_estimateGas, eth_estimateGasAfterCalls, and debug_traceCall, with correct go-ethereum SetStorage overlay semantics. (sei-protocol/sei-chain#3722) --- evm/reference.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evm/reference.mdx b/evm/reference.mdx index 7f9b6f8..de271da 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -1034,6 +1034,8 @@ The `filter` object applies only to `logs` subscriptions. For `newHeads`, pass t | 3 | `overrides` | object | Optional per-account state overrides (balance, code, nonce, state). | | 4 | `blockOverrides` | object | Optional block-context overrides (number, time, coinbase). | +State overrides are bounded in size. `max_state_override_accounts` (`evm.max_state_override_accounts`, default 100) caps the number of accounts in a single override, and `max_state_override_slots` (`evm.max_state_override_slots`, default 1000) caps the number of storage slots per account (applied to both `state` and `stateDiff`). Requests that exceed either cap are rejected with a 'state override has too many accounts' or 'too many slots' error. Setting a cap to 0 disables that limit. These caps apply equally to `eth_estimateGas`, `eth_estimateGasAfterCalls`, and `debug_traceCall`. + **Example request:** ```json From ba5bcb83b8d6a6544697516a35898c3b7f1824bb Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:17:39 +0000 Subject: [PATCH 25/41] docs: Prometheus telemetry is now automatically tagged with a chain_id global label and new metrics subsystems were added; internal metrics APIs were refactored to use global registries (not user-facing). (sei-protocol/sei-chain#3682) --- node/advanced-config-monitoring.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index aa61cbb..5d8a358 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -505,6 +505,24 @@ scrape_configs: - targets: ['localhost:9100'] ``` + + +## Cosmos SDK / Tendermint telemetry chain_id label + +Node telemetry automatically injects a `chain_id` global label into the Cosmos SDK / Tendermint Prometheus telemetry (the `GlobalLabels` of the `[telemetry]` configuration) based on the node's client chain ID. The label is appended only when a `chain_id` label is not already configured in `GlobalLabels`, so an explicit operator-set value always takes precedence. This is distinct from the `chain_id` constant label applied to the OpenTelemetry `sei_chain` namespace described above — it covers the separate Cosmos SDK/Tendermint telemetry path. + +Because every emitted series carries this label, update any PromQL that aggregates across series (for example `sum(...) without (...)` or `by (...)` clauses) to account for `chain_id`, and add a `chain_id` match where you want to scope a query to a single chain — useful when one Prometheus instance scrapes nodes on more than one chain. + +### New Tendermint internal metrics subsystems + +Additional Prometheus metrics are exported under the `tendermint` namespace in these new subsystems: + +| Subsystem prefix | Description | +| --- | --- | +| `tendermint_internal_autobahn_avail_*` | Autobahn availability metrics, including the commit and app road index, the commit and app global block number, proposal-to-commit latency, and commit-to-commit latency (the latter labeled by `timeouts`). | +| `tendermint_internal_autobahn_data_*` | Autobahn data latency metrics (a `latency` histogram labeled by `resource` and `stage`) tracking resource processing from production to the given stage. | +| `tendermint_internal_p2p_mux_*` | p2p mux stream metrics, including stream `latency`, `in_flight` open streams, and per-stream `send_msgs`, `recv_msgs`, `send_bytes`, and `recv_bytes` counters (labeled by `role` and `kind`). | + ## EVM RPC OpenTelemetry metrics The EVM RPC layer emits OpenTelemetry metrics through the process-wide `MeterProvider` (for example, a Prometheus exporter). It emits these metrics in parallel with the legacy `sei_*` metrics, so you can migrate dashboards incrementally. From f8ba5297adfe00347e236e759072239637f265ca Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:12:01 +0000 Subject: [PATCH 26/41] docs: The seidb evm-logical-digest command gains a new 'composite' backend, a new --memiavl-open-mode flag (snapshot/replay), and new --flatkv-dir/--memiavl-dir flags for comparing EVM logical state across backends on a migrating node. (sei-protocol/sei-chain#3711) --- node/technical-reference.mdx | 9 ++++++--- node/troubleshooting.mdx | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 0a7c093..ea17d32 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -287,11 +287,14 @@ When the archives are identical over the compared range, the command reports tha The command accepts these flags: -- `--backend`: The backend to read (`flatkv` or `memiavl`). -- `--db-dir` (`-d`): For FlatKV, the FlatKV data directory. For memIAVL, the memIAVL root directory that contains `current/` and `snapshot-*`. +- `--backend`: The backend to read (`flatkv`, `memiavl`, or `composite`). Use `composite` to digest the union of FlatKV and memIAVL rows for a node that is mid-migration, so it can be compared against a memiavl-only node at the same height. +- `--db-dir` (`-d`): For FlatKV, the FlatKV data directory. For memIAVL, the memIAVL root directory that contains `current/` and `snapshot-*`. Required unless `--backend composite` is used (composite mode uses `--flatkv-dir` and `--memiavl-dir` instead). +- `--flatkv-dir`: The FlatKV data directory in `composite` mode. Required with `--backend composite`. +- `--memiavl-dir`: The memIAVL root directory (containing `current/` and `snapshot-*`) in `composite` mode. Required with `--backend composite`. - `--height`: The target version. FlatKV WAL-replays to it, and memIAVL resolves `snapshot-/evm` (`0` selects the `current` symlink). +- `--memiavl-open-mode`: How memIAVL is read. `snapshot` (the default) is the fast path: it sequentially scans the completed snapshot kvs file and requires an on-disk snapshot at `--height` (or `--height 0` for the `current` symlink). `replay` is the slow path (roughly an order of magnitude slower): it opens a read-only DB, replays the changelog up to `--height`, then walks the mmap tree. Use `replay` only when no snapshot exists at the target height. Prefer `snapshot` whenever `--height` matches an existing snapshot boundary. - `--memiavl-normalization`: The memIAVL normalization mode. Use `semantic` or `independent` for the raw EVM key-value decoder, or `translator` for the current migration mapping. The default is `semantic`. -- `--inspect-bucket`: Inspect one normalized bucket (`account`, `code`, `storage`, or `legacy`) instead of printing the global digest. +- `--inspect-bucket`: Inspect one normalized bucket (`account`, `code`, `storage`, or `legacy`) instead of printing the global digest. It supports only `--memiavl-open-mode=snapshot`; combining it with `replay` returns an error. - `--key-offset` (inspect mode): The byte offset into the physical key, applied before `--key-prefix` or sharding. - `--key-prefix` (inspect mode): A hex prefix, relative to `--key-offset`, that filters physical keys. - `--shard-next-bytes` (inspect mode): Group matching keys by this many bytes after `--key-prefix`. diff --git a/node/troubleshooting.mdx b/node/troubleshooting.mdx index 7018597..72de811 100644 --- a/node/troubleshooting.mdx +++ b/node/troubleshooting.mdx @@ -159,7 +159,29 @@ seidb evm-logical-digest --backend memiavl \ --db-dir $HOME/.sei/data/state_commit/memiavl --height 213200000 ``` -Each run prints per-bucket `bucket_digest` values and a single `FINAL_DIGEST` line that covers the `account`, `code`, `storage`, and `legacy` buckets. Compare the `FINAL_DIGEST` lines from both backends at the same height. They should match. FlatKV can contain a FlatKV-only migration-version marker that a memIAVL-only node never owns. The command automatically omits that row from the FlatKV final result, so both results cover the same data. +The `--backend` flag accepts `flatkv`, `memiavl`, and `composite`. + +For memIAVL, `--memiavl-open-mode` controls how leaves are read: + +- `snapshot` (the default) sequentially scans the completed snapshot `kvs` file at `snapshot-/evm`. This is the fast path and requires an on-disk snapshot at that exact height (or `--height 0` for the current symlink). Prefer it whenever your target height matches an existing snapshot boundary. +- `replay` opens a read-only DB, replays the changelog up to `--height`, then walks the in-memory/mmap tree. It is roughly an order of magnitude slower than `snapshot`, so use it only when no snapshot exists at the target height (for example, when a node's snapshot rewrite lags the tip): + +```bash +# memIAVL digest for a height with no on-disk snapshot (slower): +seidb evm-logical-digest --backend memiavl --memiavl-open-mode replay \ + --db-dir $HOME/.sei/data/state_commit/memiavl --height 213205000 +``` + +On a node that is mid-migration, EVM state is split between the two backends: FlatKV holds the rows already migrated, while memIAVL still holds the rows not yet past the migration boundary. Use `--backend composite` to digest the union of both, so a migrating node can be compared against a memIAVL-only node at the same height. In `composite` mode, `--db-dir` is not required; instead provide the two backend directories with `--flatkv-dir` and `--memiavl-dir`. Because a live migrating node usually keeps memIAVL snapshots at heights outside FlatKV's retained window, pass `--memiavl-open-mode replay`: + +```bash +# Mid-migration node: digest the flatkv + memiavl union at one height: +seidb evm-logical-digest --backend composite --memiavl-open-mode replay \ + --flatkv-dir $HOME/.sei/data/state_commit/flatkv \ + --memiavl-dir $HOME/.sei/data/state_commit/memiavl --height 213200000 +``` + +Each run prints per-bucket `bucket_digest` values and a single `FINAL_DIGEST` line that covers the `account`, `code`, `storage`, and `legacy` buckets. Compare the `FINAL_DIGEST` lines from both backends at the same height. They should match. FlatKV can contain FlatKV-only migration marker rows that a memIAVL-only node never owns: the migration-version marker (present once a migration completes) and the migration-boundary cursor (present only while a migration is in flight). The command automatically omits both rows from the final result, so memIAVL, mid-migration, and completed nodes all produce comparable digests. For targeted debugging, the command can also inspect a single normalized bucket instead of printing the global digest. The [seidb tooling section of the technical reference](/node/technical-reference#comparing-evm-state-across-backends) has the full flag reference, including inspect mode, sharding, and `--find-hash`. These examples list the first 50 `account` rows with version metadata, and shard the `storage` bucket under a key prefix by the next 2 bytes: From fa86ef51f798e68e5b6027e77407bc934f473018 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:16:58 +0000 Subject: [PATCH 27/41] docs: New app.toml config fields gate which debug tracers callers may use on debug_trace* endpoints, restricting to a native allowlist and disabling request-supplied JavaScript tracers by default. (sei-protocol/sei-chain#3718) --- evm/reference.mdx | 2 ++ evm/tracing/index.mdx | 20 ++++++++++++++++++++ evm/tracing/javascript-tracers.mdx | 10 ++++++++++ evm/tracing/troubleshooting.mdx | 29 +++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/evm/reference.mdx b/evm/reference.mdx index de271da..94027d6 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -1167,6 +1167,8 @@ State overrides are bounded in size. `max_state_override_accounts` (`evm.max_sta **Sei-specific behavior:** Available over HTTP only, because the debug namespace is not registered on the WebSocket server. Supports geth tracers: callTracer, prestateTracer, flatCallTracer, and the struct (opcode) logger. TraceBaker pre-bakes and caches the results of callTracer, prestateTracer, and flatCallTracer. Requires trace-enabled or archive state for the target height. +**Tracer gating.** Caller-supplied `TraceConfig.Tracer` names are gated by `[evm]` config in `app.toml` (#3618). Only native tracer names listed in `trace_allowed_tracers` (`evm.trace_allowed_tracers`, default `["callTracer", "prestateTracer", "flatCallTracer", "4byteTracer", "noopTracer", "muxTracer"]`) may be requested; a name that is not in the list is rejected. Request-supplied JavaScript tracer source is rejected unless `trace_allow_js_tracers` (`evm.trace_allow_js_tracers`, default `false`) is enabled, and enabling it does not widen the native allowlist. When `muxTracer` is requested, its nested tracer names are validated recursively (bounded depth of 16). Omitting `tracer` uses the default struct logger, which is always available. This gating applies consistently across `debug_traceTransaction`, `debug_traceCall`, `debug_traceBlockByNumber`, `debug_traceBlockByHash`, and `debug_traceTransactionProfile`. + The max block lookback guard (`max_trace_lookback_blocks`) applies. If a request's target block is older than the configured lookback, the request is rejected with an error of the form `block number X is beyond max lookback of Y`. Each such request increments the `evmrpc_historical_debug_trace_attempts_total` metric. This guard applies consistently across all `debug_trace*` endpoints. **Parameters:** diff --git a/evm/tracing/index.mdx b/evm/tracing/index.mdx index d4cd08c..6bc2972 100644 --- a/evm/tracing/index.mdx +++ b/evm/tracing/index.mdx @@ -447,6 +447,26 @@ These `[evm]` fields in `app.toml` control trace baking: +## Tracer allowlist + +Caller-supplied `tracer` values on the `debug_trace*` endpoints are gated by `[evm]` config in `app.toml`. By default, only a fixed set of native geth tracers may be requested, and request-supplied JavaScript tracer source is rejected. This deviates from upstream geth, which accepts JavaScript tracers by default. The gate applies to `debug_traceCall`, `debug_traceTransaction`, `debug_traceBlockByNumber`/`debug_traceBlockByHash`, and `debug_traceTransactionProfile`. When no `tracer` is supplied, the default struct logger remains available. + +| Field | Default | Description | +| ----- | ------- | ----------- | +| `trace_allowed_tracers` | `["callTracer", "prestateTracer", "flatCallTracer", "4byteTracer", "noopTracer", "muxTracer"]` | Native debug tracer names that callers may request via `TraceConfig.Tracer`. Validated native-only at startup — a non-native or misspelled name fails startup. Set to `[]` to disable all named tracers. Request-supplied JavaScript tracer source can never be enabled through this list. | +| `trace_allow_js_tracers` | `false` | Opts in to request-supplied JavaScript tracer source in `TraceConfig.Tracer`. This executes untrusted code in-process; keep disabled on public/default RPC nodes. Enabling it does **not** widen `trace_allowed_tracers`: native tracer names must still be listed there to be usable. | + +Behavior notes: + +- A tracer name that is not listed in `trace_allowed_tracers` is rejected, unless it is JavaScript source and `trace_allow_js_tracers` is enabled. +- JavaScript tracer source is rejected unless `trace_allow_js_tracers` is set to `true`. +- When `muxTracer` is requested, its nested tracer names are validated recursively against the same allowlist, with a bounded nesting depth of 16. +- `trace_bake_tracers` names are held to the same native-only rule and validated at startup, so a non-native or misspelled baked tracer fails startup instead of being evaluated as JavaScript source on every block. + +The `trace_bake_tracers` note above that eligible values are standard named tracers is subsumed by this native-only startup validation: entries must be native tracer names such as `callTracer`, `prestateTracer`, or `flatCallTracer`. + + + ## Struct-logger output cap The default `debug_trace*` endpoints (`debug_traceCall`, `debug_traceTransaction`, and `debug_traceBlockByNumber`/`debug_traceBlockByHash`) use the built-in struct logger when no custom tracer is supplied. Traces that read many distinct storage slots can retain large amounts of output, so the node bounds the retained struct-logger output per traced transaction. diff --git a/evm/tracing/javascript-tracers.mdx b/evm/tracing/javascript-tracers.mdx index c09de20..effbf31 100644 --- a/evm/tracing/javascript-tracers.mdx +++ b/evm/tracing/javascript-tracers.mdx @@ -5,6 +5,16 @@ keywords: ['javascript tracers', 'custom tracers', 'evm debugging', 'tracer opti --- JavaScript tracers let you create custom debugging logic to analyze EVM transactions on Sei. You can use them for more complex analysis than the built-in tracers support. + + + + **JavaScript tracers are disabled by default on Sei.** Unlike upstream geth, Sei does not accept request-supplied JavaScript tracer source on `debug_trace*` endpoints unless a node operator explicitly opts in. To enable them, set `trace_allow_js_tracers = true` under the `[evm]` section of `app.toml`. + + This is a deliberate security hardening: a JavaScript tracer executes caller-supplied code in-process on the node, so it should only be enabled on trusted or private RPC nodes — never on public/default RPC infrastructure. Enabling `trace_allow_js_tracers` does **not** widen the native tracer allowlist (`trace_allowed_tracers`); the two settings are independent. + + If `trace_allow_js_tracers` is `false` (the default), any request supplying JavaScript tracer source to `debug_traceCall`, `debug_traceTransaction`, `debug_traceBlockBy*`, or `debug_traceTransactionProfile` is rejected before any tracer is constructed. If you only need standard analysis, prefer the built-in native tracers (`callTracer`, `prestateTracer`, `flatCallTracer`, `4byteTracer`, `noopTracer`, `muxTracer`), which require no opt-in. + + ## Interface overview Every JavaScript tracer has this structure: diff --git a/evm/tracing/troubleshooting.mdx b/evm/tracing/troubleshooting.mdx index a7f083e..63f4bbf 100644 --- a/evm/tracing/troubleshooting.mdx +++ b/evm/tracing/troubleshooting.mdx @@ -142,6 +142,35 @@ This guide covers common issues with EVM transaction tracing on Sei and gives pr ### 4. Network and connection issues + + +#### Tracer not allowed / JavaScript tracers disabled + +**Error**: `debug tracer "..." is not allowed; JavaScript tracers are disabled and only native tracers listed in evm.trace_allowed_tracers may be used` + +**Cause**: Sei gates which tracers callers may request through `TraceConfig.Tracer` on the `debug_traceCall`, `debug_traceTransaction`, `debug_traceBlockByNumber`, `debug_traceBlockByHash`, and `debug_traceTransactionProfile` endpoints. By default only the native tracers listed in the `[evm]` `trace_allowed_tracers` config are accepted, and request-supplied JavaScript tracer source is rejected. This deviates from upstream geth, which accepts JavaScript tracers by default. + +**Solutions**: + +- Use one of the allowlisted native tracers. The shipped default allowlist is: + + ```toml + [evm] + trace_allowed_tracers = ["callTracer", "prestateTracer", "flatCallTracer", "4byteTracer", "noopTracer", "muxTracer"] + ``` + +- If you are an operator and need to permit a native tracer that is not listed, add its name to `trace_allowed_tracers`. Only native geth tracer names are accepted here; a typo or JavaScript source causes the node to fail at startup. Setting the list to `[]` disables all named tracers. +- To allow request-supplied JavaScript tracer source, an operator must explicitly opt in: + + ```toml + [evm] + trace_allow_js_tracers = true + ``` + + This executes untrusted code in-process and should be kept disabled on public/default RPC nodes. Enabling it does **not** widen `trace_allowed_tracers`: native tracer names must still be listed there to be usable. + +When using `muxTracer`, the nested tracer names in `TracerConfig` are validated recursively against the same allowlist, with a bounded nesting depth of 16. A nested name that is not allowlisted produces a `nested debug tracer "..." is not allowed` error. Also note that `trace_bake_tracers` names are validated as native-only at startup, so a non-native or mistyped name there will fail node startup rather than being evaluated as JavaScript. + **Error**: `connection refused` or `network timeout` **Solutions**: From dd39c368ec78bfbc7d4ed84a3bff9612013fba7d Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:18:50 +0000 Subject: [PATCH 28/41] docs: LittDB adds a per-table Compression config option (S2 algorithm) that compresses values on disk, introduces a v4 segment metadata format, lowers the max value size to 2^32-2 bytes, and restricts secondary keys to full-value aliases on compressed tables. (sei-protocol/sei-chain#3769) --- node/advanced-config-monitoring.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index 5d8a358..87d51af 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -718,6 +718,11 @@ LittDB emits its metrics through the process-wide OpenTelemetry `MeterProvider` | `litt_chunk_cache_weight_added_bytes` | Counter | bytes | The weight of the entries added to the chunk cache. | | `litt_chunk_cache_eviction_latency_seconds` | Histogram | seconds | Eviction latency of the chunk cache. | +| `litt_compression_latency_seconds` | Histogram | seconds | Latency of compressing a batch of values before they are written. Emitted only for tables with value compression enabled. | +| `litt_compression_uncompressed_bytes` | Counter | bytes | The number of uncompressed value bytes submitted to compression since startup. | +| `litt_compression_compressed_bytes` | Counter | bytes | The number of compressed value bytes produced by compression since startup. Compared against `litt_compression_uncompressed_bytes`, this gives the aggregate compression ratio and total bytes saved. | +| `litt_compression_ratio` | Histogram | ratio | The per-batch compression ratio (compressed bytes divided by uncompressed bytes); lower is better. | + ### Attributes | Attribute | Description | From 0555a66ab369752aeeb427ef86163864933b475a Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:24:09 +0000 Subject: [PATCH 29/41] docs: Adds a new node config field `max-search-scan-budget` (default 100,000) that bounds the total KV index entries all in-flight tx_search/block_search requests may visit, returning an error when the process-wide budget is exceeded. (sei-protocol/sei-chain#3708) --- node/technical-reference.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index ea17d32..6204011 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -561,6 +561,19 @@ max-open-connections = 900 # greater than 0 it is applied as a context timeout on the request, so a # BroadcastTxCommit call will be cancelled if it does not complete within this duration. timeout-broadcast-tx-commit = "10s" +# Maximum number of results returned by tx_search and block_search. +# Set to 0 to disable the cap (not recommended on public nodes). +max-tx-search-results = 10000 +# max-search-scan-budget sets a process-wide cap on the total number of KV index +# entries that all in-flight tx_search and block_search requests may visit at +# once. It is shared across requests (not applied per-query) and bounds peak +# memory and scan CPU under broad or highly concurrent search load. When the +# budget is exhausted, in-flight searches fail with "kv indexer scan budget +# exceeded; narrow the query or retry later" rather than continuing to accumulate. +# The same budget is shared by the tx and block indexers, so the cap is +# process-wide across both. Default 100000; set to 0 to disable the cap (not +# recommended on public nodes). +max-search-scan-budget = 100000 # Mempool Configuration [mempool] From 6b4093db7ca3ee934e22b1bfe9e7e4eddf651099 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:25:05 +0000 Subject: [PATCH 30/41] docs: The autobahn consensus data layer migrates from the old WAL-based persistence to a LittDB-backed BlockDB, adding new config file fields and new CLI flags to the gen-autobahn-config command for tuning BlockDB retention and GC. (sei-protocol/sei-chain#3707) --- node/technical-reference.mdx | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 6204011..6e43d17 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -314,14 +314,37 @@ seid tendermint gen-autobahn-config [node-dirs...] --output # The --output flag may be abbreviated as -o seid tendermint gen-autobahn-config ./node0 ./node1 ./node2 -o autobahn.json -# Choose where autobahn consensus and data WALs are persisted (default: data/autobahn) +# Choose where autobahn consensus state and BlockDB are persisted (default: data/autobahn) seid tendermint gen-autobahn-config ./node0 ./node1 --output autobahn.json --persistent-state-dir data/autobahn -# Pass an empty value to disable persistence and run in-memory only +# Pass an empty value to disable persistence and run in-memory only (memblock) seid tendermint gen-autobahn-config ./node0 ./node1 --output autobahn.json --persistent-state-dir= + +# Tune BlockDB retention and GC +seid tendermint gen-autobahn-config ./node0 ./node1 --output autobahn.json --blockdb-retention 30s --blockdb-gc-period 10s +``` + +The `--persistent-state-dir` flag controls where Autobahn persists its consensus state and BlockDB across restarts. The default is `data/autobahn`, so persistence is enabled by default without any operator action. Autobahn's durable block and quorum-certificate storage now lives in a LittDB-backed BlockDB opened under `/blockdb`, replacing the previous data write-ahead logs (the old `globalblocks/` and `fullcommitqcs/` subdirectories are no longer read). At config load time, a relative path is resolved against the node's `--home` directory, and an absolute path is used as is. An empty value (`--persistent-state-dir=`) disables persistence entirely and both the consensus and data layers run in memory only (memblock). When set, the flag populates the `PersistentStateDir` field in the generated config. + + + Operators upgrading from a release that used the data WALs must be aware that Autobahn consensus state now lives under `/blockdb` (a LittDB BlockDB). The old `globalblocks/` and `fullcommitqcs/` WAL directories are no longer read. + + +Two additional flags tune the BlockDB: + +- `--blockdb-retention`: Sets the BlockDB retention TTL written into the `block_db` section of the generated config. The default is `30s` because this helper targets local/docker clusters rather than production node bring-up. Pass an empty value (`--blockdb-retention=`) to omit the field and keep littblock's production default of 24h. +- `--blockdb-gc-period`: Sets the BlockDB garbage-collection period (for example `10s`). Omit it to keep littblock's default GC period. + +When either flag is set, an optional `block_db` section is written into `autobahn.json`: + +```json +"block_db": { + "retention": "30s", + "gc_period": "10s" +} ``` -The `--persistent-state-dir` flag controls where Autobahn persists its consensus and data write-ahead logs (WALs) across restarts. The default is `data/autobahn`, so persistence is enabled by default without any operator action. The consensus and data layers write to distinct subdirectories under this shared on-disk root. At config load time, a relative path is resolved against the node's `--home` directory, and an absolute path is used as is. An empty value (`--persistent-state-dir=`) disables persistence entirely, and both the consensus and data layers run in memory only. When set, the flag populates the `PersistentStateDir` field in the generated config. +Each field is independently optional and is omitted from the JSON when empty; absent fields keep whatever littblock's default config uses. The `block_db` section overlays those defaults only when `persistent_state_dir` is set, and is ignored when persistence is disabled (memblock). When set, `retention` and `gc_period` must each be greater than zero. The command reads these files from each node directory: From 576ba510151cb4014fbd09b130cca4daf02fd814 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:11:31 +0000 Subject: [PATCH 31/41] docs: Adds eight new query precompiles (auth, authz, evidence, feegrant, mint, params, slashing, upgrade) and expands bank, distribution, and gov precompiles with many new read-only query methods, plus a new AssociatePubKey transaction on the addr precompile. (sei-protocol/sei-chain#3767) --- docs.json | 12 +- evm/precompiles/auth.mdx | 111 ++++++++++++ evm/precompiles/authz.mdx | 151 +++++++++++++++++ evm/precompiles/cosmwasm-precompiles/addr.mdx | 2 + evm/precompiles/cosmwasm-precompiles/bank.mdx | 90 ++++++++++ evm/precompiles/evidence.mdx | 85 ++++++++++ evm/precompiles/example-usage.mdx | 8 + evm/precompiles/feegrant.mdx | 96 +++++++++++ evm/precompiles/governance.mdx | 108 ++++++++++++ evm/precompiles/mint.mdx | 113 +++++++++++++ evm/precompiles/params.mdx | 69 ++++++++ evm/precompiles/slashing.mdx | 158 ++++++++++++++++++ evm/precompiles/upgrade.mdx | 132 +++++++++++++++ 13 files changed, 1133 insertions(+), 2 deletions(-) create mode 100644 evm/precompiles/auth.mdx create mode 100644 evm/precompiles/authz.mdx create mode 100644 evm/precompiles/evidence.mdx create mode 100644 evm/precompiles/feegrant.mdx create mode 100644 evm/precompiles/mint.mdx create mode 100644 evm/precompiles/params.mdx create mode 100644 evm/precompiles/slashing.mdx create mode 100644 evm/precompiles/upgrade.mdx diff --git a/docs.json b/docs.json index ce24c8a..37f6c4b 100644 --- a/docs.json +++ b/docs.json @@ -198,7 +198,15 @@ "evm/precompiles/cosmwasm-precompiles/bank", "evm/precompiles/cosmwasm-precompiles/cosmwasm" ] - } + }, + "evm/precompiles/auth", + "evm/precompiles/authz", + "evm/precompiles/evidence", + "evm/precompiles/feegrant", + "evm/precompiles/mint", + "evm/precompiles/params", + "evm/precompiles/slashing", + "evm/precompiles/upgrade" ] } ] @@ -1714,4 +1722,4 @@ "icons": { "library": "fontawesome" } -} \ No newline at end of file +} diff --git a/evm/precompiles/auth.mdx b/evm/precompiles/auth.mdx new file mode 100644 index 0000000..86c7b6b --- /dev/null +++ b/evm/precompiles/auth.mdx @@ -0,0 +1,111 @@ +--- +title: Auth Precompile +description: Query account information, auth module parameters, and the next account number from the EVM. +--- + +The Auth precompile exposes read-only access to the Cosmos SDK `auth` module, letting Solidity contracts look up account details, the auth module parameters, and the next account number. All of its methods are views and never mutate state. + +## Address + +The Auth precompile is available at the fixed address `0x000000000000000000000000000000000000100D`. + +## Solidity Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant AUTH_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100D; + +IAuth constant AUTH_CONTRACT = IAuth(AUTH_PRECOMPILE_ADDRESS); + +interface IAuth { + // Queries + + /** + * @notice Get account information for the given address + * @param addr The EVM address (must be associated with a Sei address) + * @return account Account details + */ + function account( + address addr + ) external view returns (Account memory account); + + /** + * @notice Get all accounts, paginated + * @param pageKey Pagination key (empty bytes for the first page) + * @return response Accounts response with pagination + */ + function accounts( + bytes memory pageKey + ) external view returns (AccountsResponse memory response); + + /** + * @notice Get the auth module parameters + * @return params Auth module parameters + */ + function params() external view returns (AuthParams memory params); + + /** + * @notice Get the next account number + * @return count The next account number + */ + function nextAccountNumber() external view returns (uint64 count); + + // Structs + + struct Account { + string accountAddress; + uint64 accountNumber; + uint64 sequence; + } + + struct AccountsResponse { + Account[] accounts; + bytes nextKey; + } + + struct AuthParams { + uint64 maxMemoCharacters; + uint64 txSigLimit; + uint64 txSizeCostPerByte; + uint64 sigVerifyCostEd25519; + uint64 sigVerifyCostSecp256k1; + bool disableSeqnoCheck; + } +} +``` + +## Methods + +### account + +```solidity +function account(address addr) external view returns (Account memory account); +``` + +Returns the `Account` details (address, account number, and sequence) for the Sei address associated with `addr`. The EVM address must be associated with a Sei address; if it is not, or if no account exists for the resolved address, the call reverts. + +### accounts + +```solidity +function accounts(bytes memory pageKey) external view returns (AccountsResponse memory response); +``` + +Returns a paginated list of all accounts. Pass empty `bytes` for the first page, then supply the returned `nextKey` to fetch subsequent pages. + +### params + +```solidity +function params() external view returns (AuthParams memory params); +``` + +Returns the auth module parameters, including `maxMemoCharacters`, `txSigLimit`, `txSizeCostPerByte`, `sigVerifyCostEd25519`, `sigVerifyCostSecp256k1`, and `disableSeqnoCheck`. + +### nextAccountNumber + +```solidity +function nextAccountNumber() external view returns (uint64 count); +``` + +Returns the next account number. This is a read-only view and does not increment the persisted global account number counter, so repeated calls return the same value. diff --git a/evm/precompiles/authz.mdx b/evm/precompiles/authz.mdx new file mode 100644 index 0000000..8c659cf --- /dev/null +++ b/evm/precompiles/authz.mdx @@ -0,0 +1,151 @@ +--- +title: Authz Precompile +description: Query authorization grants from the Cosmos authz module directly from EVM smart contracts on Sei. +--- + +The Authz precompile exposes read-only queries from the Cosmos `authz` module to EVM smart contracts. It lets contracts inspect authorization grants between a granter and a grantee, as well as all grants issued or received by a given account. + +## Address + +The Authz precompile is available at the fixed address: + +``` +0x000000000000000000000000000000000000100E +``` + +## Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant AUTHZ_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100e; + +IAuthz constant AUTHZ_CONTRACT = IAuthz(AUTHZ_PRECOMPILE_ADDRESS); + +interface IAuthz { + // Queries + + /** + * @notice Get grants between a granter and a grantee, optionally filtered by message type URL + * @param granter The granter's EVM address (must be associated with a Sei address) + * @param grantee The grantee's EVM address (must be associated with a Sei address) + * @param msgTypeUrl The message type URL to filter by (empty string for all) + * @param pageKey Pagination key (empty bytes for the first page) + * @return response Grants response with pagination + */ + function grants( + address granter, + address grantee, + string memory msgTypeUrl, + bytes memory pageKey + ) external view returns (GrantsResponse memory response); + + /** + * @notice Get all grants granted by a granter + * @param granter The granter's EVM address (must be associated with a Sei address) + * @param pageKey Pagination key (empty bytes for the first page) + * @return response Grant authorizations response with pagination + */ + function granterGrants( + address granter, + bytes memory pageKey + ) external view returns (GrantAuthorizationsResponse memory response); + + /** + * @notice Get all grants granted to a grantee + * @param grantee The grantee's EVM address (must be associated with a Sei address) + * @param pageKey Pagination key (empty bytes for the first page) + * @return response Grant authorizations response with pagination + */ + function granteeGrants( + address grantee, + bytes memory pageKey + ) external view returns (GrantAuthorizationsResponse memory response); + + // Structs + + struct Grant { + bytes authorization; + int64 expiration; + } + + struct GrantsResponse { + Grant[] grants; + bytes nextKey; + } + + struct GrantAuthorization { + string granter; + string grantee; + bytes authorization; + int64 expiration; + } + + struct GrantAuthorizationsResponse { + GrantAuthorization[] grants; + bytes nextKey; + } +} +``` + +## Methods + +All methods on this precompile are read-only `view` queries. They do not mutate state. + +### grants + +Returns the grants between a specific granter and grantee, optionally filtered by a message type URL. + +```solidity +function grants( + address granter, + address grantee, + string memory msgTypeUrl, + bytes memory pageKey +) external view returns (GrantsResponse memory response); +``` + +- `granter` — the granter's EVM address, which must be associated with a Sei address. +- `grantee` — the grantee's EVM address, which must be associated with a Sei address. +- `msgTypeUrl` — the message type URL to filter by. Pass an empty string to return all grants. +- `pageKey` — the pagination key; pass empty bytes to request the first page. + +Each returned `Grant` contains the `authorization` encoded as JSON bytes and an `expiration` expressed as a Unix timestamp in seconds. The `nextKey` field of the response is the pagination key for the next page (empty when the result set is exhausted). + +### granterGrants + +Returns every grant issued by a given granter. + +```solidity +function granterGrants( + address granter, + bytes memory pageKey +) external view returns (GrantAuthorizationsResponse memory response); +``` + +- `granter` — the granter's EVM address, which must be associated with a Sei address. +- `pageKey` — the pagination key; pass empty bytes to request the first page. + +Each returned `GrantAuthorization` includes the bech32 `granter` and `grantee` addresses, the `authorization` encoded as JSON bytes, and an `expiration` as a Unix timestamp in seconds. + +### granteeGrants + +Returns every grant received by a given grantee. + +```solidity +function granteeGrants( + address grantee, + bytes memory pageKey +) external view returns (GrantAuthorizationsResponse memory response); +``` + +- `grantee` — the grantee's EVM address, which must be associated with a Sei address. +- `pageKey` — the pagination key; pass empty bytes to request the first page. + +The response mirrors `granterGrants`, listing each `GrantAuthorization` with its granter, grantee, JSON-encoded authorization, and expiration. + +## Notes + +- The addresses passed to these methods must be associated with a Sei address; querying an unassociated address reverts. +- The `authorization` field is returned as JSON-encoded bytes and includes an `@type` discriminator (for example, a `SendAuthorization`). diff --git a/evm/precompiles/cosmwasm-precompiles/addr.mdx b/evm/precompiles/cosmwasm-precompiles/addr.mdx index 13ba17b..5f527d5 100644 --- a/evm/precompiles/cosmwasm-precompiles/addr.mdx +++ b/evm/precompiles/cosmwasm-precompiles/addr.mdx @@ -65,6 +65,8 @@ function associatePubKey( ) external returns (string memory seiAddr, address evmAddr); ``` + Both `associate()` and `associatePubKey()` are classified as transaction (state-mutating) methods on the precompile — the precompile's `IsTransaction` returns true for each of them — so each must be sent as a transaction rather than called as a read-only view. + ### Query functions ```solidity diff --git a/evm/precompiles/cosmwasm-precompiles/bank.mdx b/evm/precompiles/cosmwasm-precompiles/bank.mdx index 931ac12..7d5476d 100644 --- a/evm/precompiles/cosmwasm-precompiles/bank.mdx +++ b/evm/precompiles/cosmwasm-precompiles/bank.mdx @@ -30,6 +30,96 @@ function sendNative( The examples below use `balance()` with `usei`. They do not cover arbitrary Bank Module denominations. + +## Read-only query methods + +The Bank precompile also exposes a set of read-only query methods that mirror the Bank Module's gRPC queries. + +```solidity +/// Returns the spendable (unlocked) balances of an account, paginated. +function spendableBalances( + address acc, + bytes memory pageKey +) external view returns (Coin[] memory balances, bytes memory nextKey); + +/// Returns the total supply of a single denomination. +function supply( + string memory denom +) external view returns (uint256 response); + +/// Returns the total supply of every denomination, paginated. +function totalSupply( + bytes memory pageKey +) external view returns (Coin[] memory supply, bytes memory nextKey); + +/// Returns the Bank Module parameters. +function params() external view returns (Params memory params); + +/// Returns the metadata for a single denomination. +function denomMetadata( + string memory denom +) external view returns (Metadata memory metadata); + +/// Returns the metadata for every denomination, paginated. +function denomsMetadata( + bytes memory pageKey +) external view returns (Metadata[] memory metadatas, bytes memory nextKey); +``` + +`supply` and `totalSupply` are distinct: `supply(denom)` returns the total supply of a single denomination as a `uint256`, while `totalSupply(pageKey)` returns a paginated list of every denomination's supply. + +The supporting structs are: + +```solidity +struct Coin { + uint256 amount; + string denom; +} + +struct SendEnabled { + string denom; + bool enabled; +} + +struct Params { + SendEnabled[] sendEnabled; + bool defaultSendEnabled; +} + +struct DenomUnit { + string denom; + uint32 exponent; + string[] aliases; +} + +struct Metadata { + string description; + DenomUnit[] denomUnits; + string base; + string display; + string name; + string symbol; +} +``` + +Methods that accept a `pageKey` argument are paginated. Pass empty bytes (`0x`) to request the first page, then pass the returned `nextKey` to fetch the next page. An empty `nextKey` means there are no more pages. + +```typescript +const account = '0x1234567890123456789012345678901234567890'; + +const [balances, nextKey] = await readBank.spendableBalances(account, '0x'); +for (const coin of balances) { + console.log(`${coin.amount} ${coin.denom}`); +} + +const seiSupply = await readBank.supply('usei'); +console.log(`${ethers.formatUnits(seiSupply, 6)} SEI total supply`); + +const metadata = await readBank.denomMetadata('usei'); +console.log(metadata.name, metadata.symbol); +``` + + ## Setup ```bash diff --git a/evm/precompiles/evidence.mdx b/evm/precompiles/evidence.mdx new file mode 100644 index 0000000..48e4cb3 --- /dev/null +++ b/evm/precompiles/evidence.mdx @@ -0,0 +1,85 @@ +--- +title: Evidence Precompile +description: Query the evidence module from the EVM using the Evidence precompile. +--- + +The Evidence precompile exposes the Cosmos `evidence` module's read-only queries to EVM smart contracts. It lets you look up a single piece of evidence by its hash or page through all recorded evidence. Both methods are views and never mutate state. + +## Address + +The Evidence precompile is located at the following address: + +``` +0x000000000000000000000000000000000000100F +``` + +## Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant EVIDENCE_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100f; + +IEvidence constant EVIDENCE_CONTRACT = IEvidence(EVIDENCE_PRECOMPILE_ADDRESS); + +interface IEvidence { + // Queries + // Returns the JSON encoding of the evidence with the given hash. + function evidence(bytes memory evidenceHash) external view returns (bytes memory); + + // Returns all evidence, each entry JSON-encoded, with pagination. + function allEvidence(bytes memory pageKey) external view returns (AllEvidenceResponse memory response); + + // Structs + struct AllEvidenceResponse { + bytes[] evidenceList; + bytes nextKey; + } +} +``` + +## Methods + +### `evidence` + +Returns the evidence recorded under the given hash, JSON-encoded as bytes. The call reverts if no evidence exists for the provided hash. + +```solidity +function evidence(bytes memory evidenceHash) external view returns (bytes memory); +``` + +**Parameters** + +- `evidenceHash` — the hash of the evidence to look up. + +**Returns** + +- A `bytes` value containing the JSON encoding of the evidence (for example an `Equivocation` record with its `@type`, height, power, time and consensus address fields). + +### `allEvidence` + +Returns all recorded evidence, paginated. Each entry in `evidenceList` is the JSON encoding of a single piece of evidence. + +```solidity +function allEvidence(bytes memory pageKey) external view returns (AllEvidenceResponse memory response); +``` + +**Parameters** + +- `pageKey` — the pagination key returned by a previous call. Pass empty bytes (`""`) to request the first page. + +**Returns** + +- `AllEvidenceResponse` containing: + - `evidenceList` — an array of JSON-encoded evidence entries. + - `nextKey` — the pagination key for the next page, or empty bytes when the results are exhausted. + +## Structs + +### `AllEvidenceResponse` + +| Field | Type | Description | +| -------------- | --------- | ------------------------------------------------------ | +| `evidenceList` | `bytes[]` | JSON-encoded evidence entries for the current page. | +| `nextKey` | `bytes` | Pagination key for the next page (empty when exhausted).| diff --git a/evm/precompiles/example-usage.mdx b/evm/precompiles/example-usage.mdx index f182f51..70d6cdd 100644 --- a/evm/precompiles/example-usage.mdx +++ b/evm/precompiles/example-usage.mdx @@ -22,7 +22,15 @@ Sei precompiles are special smart contracts deployed at fixed addresses. They ex | Pointer view | `0x000000000000000000000000000000000000100A` | Look up pointer contract addresses | | Pointer | `0x000000000000000000000000000000000000100B` | Register pointer contracts | | Solo | `0x000000000000000000000000000000000000100C` | Claim Solo migration payloads | +| Auth | `0x000000000000000000000000000000000000100D` | Query accounts, account params, and the next account number | +| Authz | `0x000000000000000000000000000000000000100E` | Query authz grants by granter and grantee | +| Evidence | `0x000000000000000000000000000000000000100F` | Query misbehaviour evidence | +| Feegrant | `0x0000000000000000000000000000000000001010` | Query fee allowances | | P256 | `0x0000000000000000000000000000000000001011` | Verify P-256 elliptic curve signatures | +| Mint | `0x0000000000000000000000000000000000001012` | Query the mint params and minter state | +| Params | `0x0000000000000000000000000000000000001013` | Query module params by subspace and key | +| Slashing | `0x0000000000000000000000000000000000001014` | Query slashing params and validator signing info | +| Upgrade | `0x0000000000000000000000000000000000001015` | Query the current/applied upgrade plan and module versions | This page shows Bank, Staking, Governance, Distribution, JSON, and Solo. The other precompiles have their own pages: [CosmWasm](/evm/precompiles/cosmwasm-precompiles/cosmwasm), [Address](/evm/precompiles/cosmwasm-precompiles/addr), [Pointer contracts](/evm/evm-parity/examples/pointer-contracts) for both pointer precompiles, and [P256](/evm/precompiles/p256-precompile). diff --git a/evm/precompiles/feegrant.mdx b/evm/precompiles/feegrant.mdx new file mode 100644 index 0000000..018a5c0 --- /dev/null +++ b/evm/precompiles/feegrant.mdx @@ -0,0 +1,96 @@ +--- +title: Feegrant Precompile +description: Query fee allowances granted on Sei directly from EVM smart contracts using the Feegrant precompile. +--- + +The Feegrant precompile exposes read-only queries over the Cosmos `feegrant` module, letting EVM contracts inspect the fee allowances that granters have issued to grantees. + +## Address + +The Feegrant precompile is located at the fixed address: + +``` +0x0000000000000000000000000000000000001010 +``` + +## Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant FEEGRANT_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001010; + +IFeegrant constant FEEGRANT_CONTRACT = IFeegrant(FEEGRANT_PRECOMPILE_ADDRESS); + +interface IFeegrant { + // Queries + // Returns the fee allowance granted to the grantee by the granter. The + // allowance field of the returned grant is JSON-encoded. + function allowance(address granter, address grantee) external view returns (Grant memory grant); + + // Returns all the fee allowances granted to the given grantee, with pagination. + function allowances(address grantee, bytes memory pageKey) external view returns (AllowancesResponse memory response); + + // Returns all the fee allowances issued by the given granter, with pagination. + function allowancesByGranter(address granter, bytes memory pageKey) external view returns (AllowancesResponse memory response); + + // Structs + struct Grant { + string granter; + string grantee; + bytes allowance; + } + + struct AllowancesResponse { + Grant[] allowances; + bytes nextKey; + } +} +``` + +## Methods + +### `allowance` + +Returns the fee allowance granted to a grantee by a granter. + +```solidity +function allowance(address granter, address grantee) external view returns (Grant memory grant); +``` + +- `granter` — the granter's EVM address (must be associated with a Sei address). +- `grantee` — the grantee's EVM address (must be associated with a Sei address). + +The returned `Grant` contains the bech32 `granter` and `grantee` addresses, and the `allowance` field holds the grant's allowance encoded as JSON. The call reverts if no allowance exists between the two addresses, or if either address is not associated with a Sei address. + +### `allowances` + +Returns all fee allowances granted to the given grantee, with pagination. + +```solidity +function allowances(address grantee, bytes memory pageKey) external view returns (AllowancesResponse memory response); +``` + +- `grantee` — the grantee's EVM address (must be associated with a Sei address). +- `pageKey` — the pagination key from a previous response; pass empty bytes for the first page. + +The returned `AllowancesResponse` contains the list of `Grant` entries and a `nextKey` used to fetch the next page (empty when the results are exhausted). + +### `allowancesByGranter` + +Returns all fee allowances issued by the given granter, with pagination. + +```solidity +function allowancesByGranter(address granter, bytes memory pageKey) external view returns (AllowancesResponse memory response); +``` + +- `granter` — the granter's EVM address (must be associated with a Sei address). +- `pageKey` — the pagination key from a previous response; pass empty bytes for the first page. + +The returned `AllowancesResponse` follows the same shape as `allowances`. + +## Notes + +- All methods are view-only queries and must not be called with a non-zero `value`; doing so reverts. +- The `allowance` field of each `Grant` is JSON-encoded and includes a `@type` field identifying the allowance type (for example `BasicAllowance`). diff --git a/evm/precompiles/governance.mdx b/evm/precompiles/governance.mdx index 7ad3426..cb3aaca 100644 --- a/evm/precompiles/governance.mdx +++ b/evm/precompiles/governance.mdx @@ -101,6 +101,114 @@ function submitProposal( ) payable external returns (uint64 proposalID); ``` + +### Query functions + +The governance precompile also exposes read-only query methods so you can read proposal state on-chain instead of relying on Cosmos REST or RPC. The vote and deposit queries are named `getVote` and `getDeposit` (rather than overloading the `vote`/`deposit` transaction methods) because ambiguous function names break common tooling such as ethers.js. + +```solidity +struct Coin { + uint256 amount; + string denom; +} + +struct TallyResultData { + string yes; + string abstain; + string no; + string noWithVeto; +} + +struct WeightedVoteOptionData { + int32 option; + string weight; // Weight as decimal string (e.g., "0.7") +} + +struct ProposalData { + uint64 id; + int32 status; // ProposalStatus enum value + TallyResultData finalTallyResult; + int64 submitTime; // Unix seconds + int64 depositEndTime; // Unix seconds + Coin[] totalDeposit; + int64 votingStartTime; // Unix seconds + int64 votingEndTime; // Unix seconds + bool isExpedited; + bytes content; // proposal content as JSON +} + +struct VoteData { + uint64 proposalId; + string voter; // bech32 address + WeightedVoteOptionData[] options; +} + +struct DepositData { + uint64 proposalId; + string depositor; // bech32 address + Coin[] amount; +} + +struct GovParams { + uint64 votingPeriod; // seconds + uint64 expeditedVotingPeriod; // seconds + Coin[] minDeposit; + uint64 maxDepositPeriod; // seconds + Coin[] minExpeditedDeposit; + string quorum; + string threshold; + string vetoThreshold; + string expeditedQuorum; + string expeditedThreshold; +} + +/// Query a proposal by ID +function proposal( + uint64 proposalID +) external view returns (ProposalData memory proposal); + +/// Query proposals with optional filters. proposalStatus 0 = all; a zero +/// address for voter/depositor applies no filter. +function proposals( + int32 proposalStatus, + address voter, + address depositor, + bytes memory pageKey +) external view returns (ProposalData[] memory proposals, bytes memory nextKey); + +/// Query a vote cast on a proposal +function getVote( + uint64 proposalID, + address voter +) external view returns (VoteData memory vote); + +/// Query all votes cast on a proposal, paginated +function votes( + uint64 proposalID, + bytes memory pageKey +) external view returns (VoteData[] memory votes, bytes memory nextKey); + +/// Query the governance module parameters +function params() external view returns (GovParams memory params); + +/// Query a deposit made to a proposal +function getDeposit( + uint64 proposalID, + address depositor +) external view returns (DepositData memory deposit); + +/// Query all deposits made to a proposal, paginated +function deposits( + uint64 proposalID, + bytes memory pageKey +) external view returns (DepositData[] memory deposits, bytes memory nextKey); + +/// Query the current tally of votes on a proposal +function tallyResult( + uint64 proposalID +) external view returns (TallyResultData memory tallyResult); +``` + ## Using the contract ### Setup diff --git a/evm/precompiles/mint.mdx b/evm/precompiles/mint.mdx new file mode 100644 index 0000000..e5e6d40 --- /dev/null +++ b/evm/precompiles/mint.mdx @@ -0,0 +1,113 @@ +--- +title: Mint Precompile +description: Query the Sei mint module from EVM smart contracts to read inflation parameters and the current minter state. +--- + +The Mint precompile exposes read-only queries against the Sei mint module, letting EVM smart contracts inspect the chain's token release schedule and the current minter state. + +## Address + +The Mint precompile is located at the following address: + +``` +0x0000000000000000000000000000000000001012 +``` + +## Interface + +```solidity Mint.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant MINT_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001012; + +IMint constant MINT_CONTRACT = IMint(MINT_PRECOMPILE_ADDRESS); + +interface IMint { + // Queries + function params() external view returns (MintParams memory); + function minter() external view returns (Minter memory); + + // Structs + struct ScheduledTokenRelease { + string startDate; + string endDate; + uint64 tokenReleaseAmount; + } + + struct MintParams { + string mintDenom; + ScheduledTokenRelease[] tokenReleaseSchedule; + } + + struct Minter { + string startDate; + string endDate; + string denom; + uint64 totalMintAmount; + uint64 remainingMintAmount; + uint64 lastMintAmount; + string lastMintDate; + uint64 lastMintHeight; + } +} +``` + +## Methods + +All methods on the Mint precompile are read-only `view` functions. Calling them with a non-zero `value` reverts. + +### `params` + +Returns the mint module parameters, including the mint denom and the full scheduled token release schedule. + +```solidity +function params() external view returns (MintParams memory); +``` + +**Returns** + +- `MintParams` — a struct containing: + - `mintDenom` — the denomination minted by the module. + - `tokenReleaseSchedule` — an array of `ScheduledTokenRelease` entries, each with a `startDate`, `endDate`, and `tokenReleaseAmount`. + +### `minter` + +Returns the current minter state, describing the active release window and the amounts minted so far. + +```solidity +function minter() external view returns (Minter memory); +``` + +**Returns** + +- `Minter` — a struct containing: + - `startDate` — the start date of the current release period. + - `endDate` — the end date of the current release period. + - `denom` — the denomination being minted. + - `totalMintAmount` — the total amount to be minted over the current period. + - `remainingMintAmount` — the amount still to be minted in the current period. + - `lastMintAmount` — the amount minted in the most recent mint. + - `lastMintDate` — the date of the most recent mint. + - `lastMintHeight` — the block height of the most recent mint. + +## Usage Example + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Mint.sol"; + +contract MintReader { + function getMintDenom() external view returns (string memory) { + IMint.MintParams memory params = MINT_CONTRACT.params(); + return params.mintDenom; + } + + function getRemainingToMint() external view returns (uint64) { + IMint.Minter memory minter = MINT_CONTRACT.minter(); + return minter.remainingMintAmount; + } +} +``` diff --git a/evm/precompiles/params.mdx b/evm/precompiles/params.mdx new file mode 100644 index 0000000..305a3c7 --- /dev/null +++ b/evm/precompiles/params.mdx @@ -0,0 +1,69 @@ +--- +title: Params Precompile +description: Read on-chain module parameters from any x/params subspace through the Params precompile. +--- + +The Params precompile exposes the Cosmos SDK `x/params` subspaces to the EVM, letting contracts read arbitrary module parameters by subspace and key. It is a read-only precompile deployed at a fixed address. + +## Address + +The Params precompile is available at the following address: + +``` +0x0000000000000000000000000000000000001013 +``` + +## Interface + +```solidity Params.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant PARAMS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001013; + +IParams constant PARAMS_CONTRACT = IParams(PARAMS_PRECOMPILE_ADDRESS); + +interface IParams { + // Queries + function params(string memory subspace, string memory key) external view returns (string memory value); +} +``` + +## Methods + +### params + +Returns the value of the parameter identified by `subspace` and `key`. The value is returned as its JSON-encoded string representation. + +```solidity +function params( + string memory subspace, + string memory key +) external view returns (string memory value); +``` + +**Parameters** + +- `subspace` — the name of the params subspace to read from (e.g. `"staking"`). +- `key` — the parameter key within the subspace (e.g. `"MaxValidators"`). + +**Returns** + +- `value` — the JSON-encoded value of the requested parameter. + +The call reverts if the subspace or key is unknown or empty. + +## Example Usage + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IParams, PARAMS_CONTRACT} from "./Params.sol"; + +contract ParamsReader { + function getMaxValidators() external view returns (string memory) { + return PARAMS_CONTRACT.params("staking", "MaxValidators"); + } +} +``` diff --git a/evm/precompiles/slashing.mdx b/evm/precompiles/slashing.mdx new file mode 100644 index 0000000..2ba8739 --- /dev/null +++ b/evm/precompiles/slashing.mdx @@ -0,0 +1,158 @@ +--- +title: "Slashing Precompile" +description: "Query the slashing module's parameters and validator signing information directly from the EVM." +--- + +The Slashing precompile exposes the Cosmos `slashing` module's read-only queries to EVM smart contracts. It lets you inspect the module parameters that govern downtime and double-sign penalties, and read per-validator signing information such as missed block counts and jailing status. + +## Address + +The Slashing precompile is available at the fixed address: + +``` +0x0000000000000000000000000000000000001014 +``` + +## Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant SLASHING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001014; + +ISlashing constant SLASHING_CONTRACT = ISlashing(SLASHING_PRECOMPILE_ADDRESS); + +interface ISlashing { + // Queries + function params() external view returns (SlashingParams memory params); + + function signingInfo( + string memory consAddress + ) external view returns (SigningInfo memory signingInfo); + + function signingInfos( + bytes memory pageKey + ) external view returns (SigningInfosResponse memory response); + + // Structs + struct SlashingParams { + int64 signedBlocksWindow; + string minSignedPerWindow; + uint64 downtimeJailDuration; + string slashFractionDoubleSign; + string slashFractionDowntime; + } + + struct SigningInfo { + string validatorAddress; + int64 startHeight; + int64 indexOffset; + int64 jailedUntil; + bool tombstoned; + int64 missedBlocksCounter; + } + + struct SigningInfosResponse { + SigningInfo[] signingInfos; + bytes nextKey; + } +} +``` + +## Methods + +### `params` + +```solidity +function params() external view returns (SlashingParams memory params); +``` + +Returns the slashing module parameters. + +**Returns** a `SlashingParams` struct: + +| Field | Type | Description | +| ------------------------- | -------- | --------------------------------------------------------------------------- | +| `signedBlocksWindow` | `int64` | The size of the sliding window used to track validator liveness. | +| `minSignedPerWindow` | `string` | Minimum fraction of blocks a validator must sign per window (decimal string).| +| `downtimeJailDuration` | `uint64` | Duration, in seconds, a validator is jailed for downtime. | +| `slashFractionDoubleSign` | `string` | Fraction of stake slashed for a double-sign (decimal string). | +| `slashFractionDowntime` | `string` | Fraction of stake slashed for downtime (decimal string). | + +### `signingInfo` + +```solidity +function signingInfo( + string memory consAddress +) external view returns (SigningInfo memory signingInfo); +``` + +Returns the signing information for a single validator identified by its consensus address (bech32). + +**Parameters** + +| Parameter | Type | Description | +| ------------- | -------- | ----------------------------------------- | +| `consAddress` | `string` | The validator's consensus address (bech32).| + +**Returns** a `SigningInfo` struct: + +| Field | Type | Description | +| --------------------- | -------- | ---------------------------------------------------------------- | +| `validatorAddress` | `string` | The validator's consensus address (bech32). | +| `startHeight` | `int64` | Height at which the validator became bonded. | +| `indexOffset` | `int64` | Index offset into the signed block bit array. | +| `jailedUntil` | `int64` | Unix timestamp (seconds) until which the validator is jailed. | +| `tombstoned` | `bool` | Whether the validator has been tombstoned. | +| `missedBlocksCounter` | `int64` | Number of blocks the validator has missed within the window. | + +Querying a consensus address with no signing information reverts. + +### `signingInfos` + +```solidity +function signingInfos( + bytes memory pageKey +) external view returns (SigningInfosResponse memory response); +``` + +Returns the signing information for all validators, with pagination. + +**Parameters** + +| Parameter | Type | Description | +| --------- | ------- | ---------------------------------------------------------------- | +| `pageKey` | `bytes` | Pagination key from a previous response; pass empty bytes for the first page.| + +**Returns** a `SigningInfosResponse` struct: + +| Field | Type | Description | +| -------------- | --------------- | ------------------------------------------------------- | +| `signingInfos` | `SigningInfo[]` | The signing information entries for the current page. | +| `nextKey` | `bytes` | Pagination key for the next page; empty when exhausted. | + +## Example Usage + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Slashing.sol"; + +contract SlashingReader { + function getDowntimeSlashFraction() external view returns (string memory) { + ISlashing.SlashingParams memory params = SLASHING_CONTRACT.params(); + return params.slashFractionDowntime; + } + + function getMissedBlocks(string memory consAddress) external view returns (int64) { + ISlashing.SigningInfo memory info = SLASHING_CONTRACT.signingInfo(consAddress); + return info.missedBlocksCounter; + } +} +``` + + + All methods on the Slashing precompile are read-only `view` queries. Sending value to any of them reverts. + diff --git a/evm/precompiles/upgrade.mdx b/evm/precompiles/upgrade.mdx new file mode 100644 index 0000000..6ef559c --- /dev/null +++ b/evm/precompiles/upgrade.mdx @@ -0,0 +1,132 @@ +--- +title: Upgrade Precompile +description: Query the chain upgrade module from EVM smart contracts using the Upgrade precompile. +--- + +The Upgrade precompile exposes read-only queries against the chain's upgrade module to EVM smart contracts. It lets contracts inspect the currently scheduled upgrade plan, look up when a named upgrade was applied, read stored upgraded consensus state, and read the consensus versions of app modules. + +## Precompile Address + +The Upgrade precompile is available at the fixed address: + +``` +0x0000000000000000000000000000000000001015 +``` + +## Solidity Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant UPGRADE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001015; + +IUpgrade constant UPGRADE_CONTRACT = IUpgrade(UPGRADE_PRECOMPILE_ADDRESS); + +interface IUpgrade { + // Queries + // Returns the currently scheduled upgrade plan, or a zero-valued plan if + // no upgrade is scheduled. + function currentPlan() external view returns (Plan memory plan); + + // Returns the block height at which the named upgrade was applied, or 0 + // if it has not been applied. + function appliedPlan( + string memory name + ) external view returns (int64 height); + + // Returns the upgraded consensus state stored for the given last height, + // or empty bytes if none is stored. + function upgradedConsensusState( + int64 lastHeight + ) external view returns (bytes memory consensusState); + + // Returns the consensus versions of app modules. An empty moduleName + // returns all modules; a specific moduleName returns just that module. + function moduleVersions( + string memory moduleName + ) external view returns (ModuleVersion[] memory versions); + + // Structs + struct Plan { + string name; + int64 height; + string info; + } + + struct ModuleVersion { + string name; + uint64 version; + } +} +``` + +## Methods + +All methods are read-only `view` queries and are non-payable; sending value reverts the call. + +### currentPlan + +```solidity +function currentPlan() external view returns (Plan memory plan); +``` + +Returns the currently scheduled upgrade plan. If no upgrade is scheduled, a zero-valued `Plan` is returned (empty `name` and `info`, `height` of `0`). + +The returned `Plan` contains: + +- `name` — the name of the scheduled upgrade. +- `height` — the block height at which the upgrade is scheduled to run. +- `info` — arbitrary metadata attached to the plan. + +### appliedPlan + +```solidity +function appliedPlan(string memory name) external view returns (int64 height); +``` + +Returns the block height at which the upgrade identified by `name` was applied. If the upgrade has not been applied, it returns `0`. + +### upgradedConsensusState + +```solidity +function upgradedConsensusState(int64 lastHeight) external view returns (bytes memory consensusState); +``` + +Returns the upgraded consensus state stored for the given `lastHeight`. If no consensus state is stored for that height, it returns empty bytes. + +### moduleVersions + +```solidity +function moduleVersions(string memory moduleName) external view returns (ModuleVersion[] memory versions); +``` + +Returns the consensus versions of app modules. Passing an empty `moduleName` returns every module; passing a specific `moduleName` returns a single entry for that module. Querying an unknown module name reverts. + +Each `ModuleVersion` contains: + +- `name` — the module's name. +- `version` — the module's consensus version. + +## Example Usage + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IUpgrade, UPGRADE_CONTRACT} from "./Upgrade.sol"; + +contract UpgradeReader { + function getCurrentPlan() external view returns (IUpgrade.Plan memory) { + return UPGRADE_CONTRACT.currentPlan(); + } + + function getAppliedHeight(string memory name) external view returns (int64) { + return UPGRADE_CONTRACT.appliedPlan(name); + } + + function getModuleVersions() external view returns (IUpgrade.ModuleVersion[] memory) { + return UPGRADE_CONTRACT.moduleVersions(""); + } +} +``` From 9fe9770012c634bb58a3dd8ea2ecee29958ffa8d Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:13:01 +0000 Subject: [PATCH 32/41] docs: The EVM module now tracks recent block hashes in a KV store ring (last 256 blocks) plus a process-local cache, making the BLOCKHASH opcode return correct historical block hashes for the previous 256 blocks. (sei-protocol/sei-chain#3811) --- evm/differences-with-ethereum.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/evm/differences-with-ethereum.mdx b/evm/differences-with-ethereum.mdx index 2ac584c..aa376a1 100644 --- a/evm/differences-with-ethereum.mdx +++ b/evm/differences-with-ethereum.mdx @@ -101,6 +101,10 @@ state root, which is an MPT root. Sei computes the block hash from the block header in Tendermint data format. As a result, it is different from Ethereum's block hash. +The `BLOCKHASH` opcode returns the correct historical block hash for the previous 256 blocks, matching the EVM Yellow Paper's availability window. Sei maintains an on-chain ring of recent block hashes that is populated during `BeginBlock` from the parent block's hash, backed by a process-local cache, with a fallback to staking historical info. Before this change, `BLOCKHASH` for recent blocks (such as `height - 1`) returned an empty hash. Hashes outside the 256-block window, or hashes of future/current-height blocks, return zero, and `BLOCKHASH` values remain non-interchangeable with Ethereum because of the different header encoding. + +{/* For additional context, see the opcode differences table above. */} + ## Base fee & tips Sei supports all non‑blob transaction types, including the Pectra `SetCode` transaction (EIP‑7702). However, for a legacy (non EIP‑1559) From 6d0b2c2a572ca4d9d79f8731ab8302d4db6218e1 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:13:59 +0000 Subject: [PATCH 33/41] docs: Adds a new `max_log_bytes` EVM config field and changes `max_log_no_block` behavior so `eth_getLogs` now errors (instead of silently truncating) when a bounded or open-ended query exceeds the matched-log count or estimated heap-byte budget. (sei-protocol/sei-chain#3759) --- evm/evm-parity/evm-compatibility.mdx | 2 +- evm/reference.mdx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/evm/evm-parity/evm-compatibility.mdx b/evm/evm-parity/evm-compatibility.mdx index 3688eb9..a5c8f47 100644 --- a/evm/evm-parity/evm-compatibility.mdx +++ b/evm/evm-parity/evm-compatibility.mdx @@ -52,7 +52,7 @@ Standard EVM tools (viem, wagmi, ethers, Foundry, and Hardhat) work on Sei for a | Feature | Sei status | Notes | | --- | --- | --- | -| `eth_getLogs` | Supported | | +| `eth_getLogs` | Supported with differences | A single query may match at most `max_log_no_block` logs (default 10000) and `max_log_bytes` of estimated heap bytes (default 64 MiB). This applies to both bounded and open-ended block ranges. Exceeding either limit returns an error (`too many logs` / `too many log bytes`) advising you to narrow the block range or filter criteria, rather than silently truncating the result. [See RPC configuration.](/node/node-operators/rpc-configuration) | | Filter lifecycle (`eth_newFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_uninstallFilter`) | Supported | | | WebSocket subscriptions (`eth_subscribe`) | Supported | [See WebSocket Connections.](/evm/evm-parity/websocket) | diff --git a/evm/reference.mdx b/evm/reference.mdx index 94027d6..d104806 100644 --- a/evm/reference.mdx +++ b/evm/reference.mdx @@ -110,8 +110,11 @@ Because `FinalizeBlock` responses are not stored on disk under Autobahn, `/block By default, log filters (`eth_getLogs`, `eth_getFilterLogs`) have these limits: -- **Open-ended block range:** up to 10,000 logs in one response -- **Close-ended block range:** up to 2,000 blocks in one query +- **Matched-log count:** a single query may match up to 10,000 logs (`max_log_no_block`, `evm.max_log_no_block`, default 10000). This cap applies to both bounded and open-ended block ranges. Exceeding it returns a `too many logs` error rather than a truncated result. +- **Estimated log bytes:** a single query may materialize up to 64 MiB of estimated heap bytes of matched logs (`max_log_bytes`, `evm.max_log_bytes`, default 67108864 / 64 MiB). Exceeding it returns a `too many log bytes` error. A non-positive value falls back to the receipt-store default. +- **Close-ended block range:** up to 2,000 blocks in one query (`max_blocks_for_log`, `evm.max_blocks_for_log`, default 2000). + +When a query exceeds the matched-log count or the byte budget, the request now errors instead of silently truncating the result. Narrow the block range or the filter criteria (address and topics) and retry. Real-time subscriptions (`eth_subscribe` for `newHeads` and `logs`) are available over WebSocket only. For transport details, see [WebSocket connections](/evm/evm-parity/websocket). From 4f550bd1c2662f431a170ee3df8d6b27caab279b Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:14:27 +0000 Subject: [PATCH 34/41] docs: Dynamic-gas precompiles now charge gas for ABI-decoding calldata before decoding it, so calls that supply too little gas are rejected and gas costs for existing precompile calls have increased slightly. (sei-protocol/sei-chain#3737) --- evm/evm-parity/gas-and-fees.mdx | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/evm/evm-parity/gas-and-fees.mdx b/evm/evm-parity/gas-and-fees.mdx index 8eb79ed..e916638 100644 --- a/evm/evm-parity/gas-and-fees.mdx +++ b/evm/evm-parity/gas-and-fees.mdx @@ -92,6 +92,42 @@ const gas = await provider.estimateGas({ Estimate the absolute storage-write cost with a live `eth_estimateGas` call against a Sei RPC. A Foundry `forge test --gas-report --fork-url ` report forks the chain *state*, but it runs the standard EVM gas schedule of revm. It therefore shows `SSTORE` at the Ethereum cost (approximately 22,100) instead of the Sei cost (approximately 72,000). Use the report for relative profiling of your own logic. + +## Precompile calldata decode cost + +Sei's dynamic-gas precompiles (such as bank, wasmd, oracle, ibc, json, distribution, pointer, and addr) now charge gas for ABI-decoding a call's calldata *before* the arguments are decoded. The charge covers a length-proportional scan of the input plus the cost of copying any `string` payloads the decoder would materialize — a cost that can be significantly larger than the raw calldata length when a single string is referenced by many array or tuple slots. + +Two consequences for callers: + +- A precompile call that forwards too little gas to cover the decode is reverted before the precompile runs, instead of being decoded for free. +- Overall gas used for dynamic-gas precompile calls has increased slightly, since the decode is now metered. + +Because this charge depends on the exact shape and size of your calldata, do not hard-code gas limits for precompile calls. Always size them with a live `eth_estimateGas` call against a Sei RPC: + + + +```ts viem +const gas = await client.estimateGas({ + account, + to: precompileAddress, + data: encodedCalldata, +}); + +// Add a buffer for safety during high-activity periods +const gasWithBuffer = (gas * 120n) / 100n; // 20% buffer +``` + +```ts ethers +const gas = await provider.estimateGas({ + from: signer.address, + to: precompileAddress, + data: encodedCalldata, +}); +``` + + + + ## Summary | Behavior | Ethereum | Sei | From fa932d513172c23bcb2e5121abd4c558caac1feb Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:15:53 +0000 Subject: [PATCH 35/41] docs: Seed nodes now expose a Prometheus metrics endpoint (and fail startup on bind failure), the metrics server gains read/write/idle timeouts, and the max-open-connections config field's behavior is clarified. (sei-protocol/sei-chain#3819) --- node/advanced-config-monitoring.mdx | 42 +++++++++++++++++++++++++++++ node/node-operators.mdx | 22 +++++++++++++++ node/node-types.mdx | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index 87d51af..e57a897 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -507,6 +507,48 @@ scrape_configs: +## Seed node Prometheus metrics + +Seed nodes now expose a Prometheus `/metrics` endpoint, so they can be scraped like any other node. Because a seed serves no RPC, this endpoint is the only observability surface it has — peer reachability and peer headroom both come from its p2p metrics (for example `tendermint_p2p_peers`). + +To enable it, set the usual instrumentation fields in `config.toml` under `[instrumentation]`: + +```toml +[instrumentation] +prometheus = true +prometheus-listen-addr = ":26660" +``` + +Every exposed series is stamped with the node's `chain_id` label, matching the behavior of full nodes. + + +For a seed node, a failure to bind the Prometheus listener (for example, the port is already in use or the address is unparseable) is **fatal**: the seed fails to start. This is deliberate — a seed's value is only realized when it is observable, so an unambiguous crash is preferable to a seed that discovers peers while blind. + +A full node behaves differently: a bind failure is **non-fatal**. The full node logs the error and continues running, because it still serves RPC as an in-band way to observe its health. + + +The `tendermint_p2p_peers` gauge is present from node startup rather than appearing only after the first refresh interval, so an alert comparing peer count against the connection cap matches immediately rather than sitting unmatched during the startup window. + +### Metrics server timeouts + +The Prometheus metrics HTTP server enforces request timeouts to bound how long a slow reader can hold a request slot: + +| Timeout | Value | +| --- | --- | +| `ReadHeaderTimeout` | 10s | +| `ReadTimeout` | 10s | +| `WriteTimeout` | 30s | +| `IdleTimeout` | 60s | + +### `max-open-connections` behavior + +The `max-open-connections` field under `[instrumentation]` is not a socket limit despite the name. It becomes promhttp's `MaxRequestsInFlight`: the maximum number of scrapes served concurrently. Requests past the limit receive an HTTP `503` rather than being queued, and a slow reader occupies a slot until it finishes or hits the `WriteTimeout`. A value of `0` means unlimited. + +Keep this value above the number of scrapers that can overlap — for example, an HA Prometheus pair, blackbox probes, and a human running `curl` — so a burst of concurrent scrapes does not cause a healthy node to report as unscrapeable. + + + + ## Cosmos SDK / Tendermint telemetry chain_id label Node telemetry automatically injects a `chain_id` global label into the Cosmos SDK / Tendermint Prometheus telemetry (the `GlobalLabels` of the `[telemetry]` configuration) based on the node's client chain ID. The label is appended only when a `chain_id` label is not already configured in `GlobalLabels`, so an explicit operator-set value always takes precedence. This is distinct from the `chain_id` constant label applied to the OpenTelemetry `sei_chain` namespace described above — it covers the separate Cosmos SDK/Tendermint telemetry path. diff --git a/node/node-operators.mdx b/node/node-operators.mdx index 19812d9..6e5c04b 100644 --- a/node/node-operators.mdx +++ b/node/node-operators.mdx @@ -789,6 +789,28 @@ everything. Second, when `evm-ss-db-directory` is unset, nodes with an existing to `/data/state_store/evm/{backend}`. + + +The `[instrumentation] max-open-connections` field in `config.toml` is not a +socket limit despite its name. It is passed to promhttp as +`MaxRequestsInFlight` and caps the number of Prometheus scrapes served +concurrently; requests past the cap receive an HTTP `503` rather than being +queued. Keep the value above the number of scrapers that can overlap — an HA +Prometheus pair, blackbox probes, and the occasional human `curl` — because a +slow reader occupies a slot until it finishes or hits the write timeout. `0` +means unlimited. + +The Prometheus metrics HTTP server enforces a read timeout of 10s, a write +timeout of 30s, and an idle timeout of 60s. These bound how long a slow reader +can hold a request slot and how long idle keep-alive connections persist. + +Seed nodes also serve Prometheus metrics (for example the +`tendermint_p2p_peers` gauge) when instrumentation is enabled. Because a seed +serves no RPC, Prometheus is its only observability surface, so a failure to +bind the metrics listener is fatal and causes seed startup to fail — unlike a +full node, which logs the error and continues serving. + + ### Cosmos SDK state pruning Separate from the SeiDB retention settings above (`min-retain-blocks`, diff --git a/node/node-types.mdx b/node/node-types.mdx index f306190..24d5b99 100644 --- a/node/node-types.mdx +++ b/node/node-types.mdx @@ -23,7 +23,7 @@ The `seid` binary uses these TCP ports. Change their settings to match your envi - `9090`: The default port for gRPC communication. This port is used for high-performance communication with the node. - `8545`: The default port for EVM HTTP RPC. This port is used for Ethereum JSON-RPC calls. It must be open if you want to interact with EVM-compatible applications. The [`frozen-rpc-router`](/node/technical-reference#frozen-rpc-router) binary also listens on `127.0.0.1:8545` by default. If you run the router on the same host as a live node, move one of them to a different port. The linked example keeps the router on `8545` and moves the two co-located nodes to `9545` and `9546`. - `8546`: The default port for EVM WebSocket RPC. EVM applications that need WebSocket connections use this port for real-time communication. -- `26660`: The default port for the Prometheus database, which you can use to monitor the environment. In the default configuration, this port is not open. +- `26660`: The default port for the Prometheus metrics endpoint, which you can use to monitor the environment. It is served only when instrumentation is enabled (`prometheus = true` under `[instrumentation]` in `config.toml`); in the default configuration it is not open. Seed nodes also serve Prometheus metrics when instrumentation is enabled — exposing p2p metrics such as `tendermint_p2p_peers` — which is their only observability surface since they serve no RPC. Note that on a seed node a failure to bind the Prometheus listener is fatal and crashes startup, whereas a full node logs the failure and continues. You can change all of these ports in `$HOME/.sei/config/config.toml` and `$HOME/.sei/config/app.toml`. From 7cc9ac2292c3af6ff52ac1a39ca8ec68dd2ebe5a Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:17:01 +0000 Subject: [PATCH 36/41] docs: The json, p256, and pointerview precompiles switched to a dynamic gas model, changing how gas is metered and charged for these on-chain precompile calls (notably p256 now uses a fixed 3450 gas verification cost per RIP-7212). (sei-protocol/sei-chain#3813) --- evm/evm-parity/gas-and-fees.mdx | 8 +++++++- evm/precompiles/json.mdx | 2 +- evm/precompiles/p256-precompile.mdx | 6 ++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/evm/evm-parity/gas-and-fees.mdx b/evm/evm-parity/gas-and-fees.mdx index e916638..1ec9109 100644 --- a/evm/evm-parity/gas-and-fees.mdx +++ b/evm/evm-parity/gas-and-fees.mdx @@ -95,7 +95,13 @@ const gas = await provider.estimateGas({ ## Precompile calldata decode cost -Sei's dynamic-gas precompiles (such as bank, wasmd, oracle, ibc, json, distribution, pointer, and addr) now charge gas for ABI-decoding a call's calldata *before* the arguments are decoded. The charge covers a length-proportional scan of the input plus the cost of copying any `string` payloads the decoder would materialize — a cost that can be significantly larger than the raw calldata length when a single string is referenced by many array or tuple slots. +Sei's dynamic-gas precompiles (such as bank, wasmd, oracle, ibc, json, distribution, pointer, pointerview, addr, and p256) now charge gas for ABI-decoding a call's calldata *before* the arguments are decoded. The charge covers a length-proportional scan of the input plus the cost of copying any `string` payloads the decoder would materialize — a cost that can be significantly larger than the raw calldata length when a single string is referenced by many array or tuple slots. + +The `json`, `p256`, and `pointerview` precompiles use this dynamic gas model as well. Their metering works as follows: + +- **`json`** — charges 100 gas per byte of the JSON payload being parsed, consumed against the call's gas meter. +- **`pointerview`** — self-meters based on the state reads performed during pointer lookups, rather than a flat charge. +- **`p256`** — charges a fixed **3,450 gas** verification cost, matching RIP-7212's `P256VERIFY`. Because this flat charge is levied outside the verification's panic recovery, a call that forwards too little gas to cover it fails the transaction with an out-of-gas error rather than reverting the call. Two consequences for callers: diff --git a/evm/precompiles/json.mdx b/evm/precompiles/json.mdx index e336b87..2f3b007 100644 --- a/evm/precompiles/json.mdx +++ b/evm/precompiles/json.mdx @@ -713,7 +713,7 @@ async function handleMissingKeys(jsonPrecompile: ethers.Contract, data: any) { - **Boolean values:** Use the integer 0 for false and 1 for true. - **Key paths:** Extract parent objects first, then parse them manually. Dot notation may not be supported. - **Arrays:** Object-keyed arrays use `extractAsBytesList()`. JSON arrays by index use `extractAsBytesFromArray()`. -- **Gas costs:** Large JSON objects need higher gas limits. +- **Gas costs:** The JSON precompile uses dynamic gas metering. It charges 100 gas per byte of the JSON payload (the first argument) against the call's gas meter, so larger JSON inputs consume proportionally more gas and need higher gas limits. - **Encoding:** Always use UTF-8 encoding with `ethers.toUtf8Bytes()`. - **Error handling:** Always implement fallback values for production applications. diff --git a/evm/precompiles/p256-precompile.mdx b/evm/precompiles/p256-precompile.mdx index 3b571ef..99b35d1 100644 --- a/evm/precompiles/p256-precompile.mdx +++ b/evm/precompiles/p256-precompile.mdx @@ -162,11 +162,13 @@ Total length: 160 bytes ## Gas costs -The precompile is much more gas efficient than Solidity implementations. The exact gas cost per byte of verified data is set to `GasCostPerByte = 300`. The results are: +The precompile is much more gas efficient than Solidity implementations. Verification is a constant-work, stateless operation, so it is priced as a flat charge against the call's gas meter rather than per byte of input, matching RIP-7212's `P256VERIFY`: -- **Total cost**: 300 × 160 = **48,000 gas** per verification +- **Total cost**: a fixed `P256VerifyGas = 3450` gas per verification - **Efficiency**: Up to 60x more efficient than pure Solidity implementations +The fixed verification cost is charged before the signature check runs. If the call is not funded for this cost, the resulting out-of-gas condition propagates and fails the transaction rather than being caught and downgraded to a reverted call. + ## Real-world use cases ### WebAuthn/passkeys authentication From d5c3b355ed38faed333870e5a4a0ce3ce68f35b8 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:17:43 +0000 Subject: [PATCH 37/41] docs: Cosmos transaction ante handlers now validate provided public keys (including recursive validation of nested multisig keys with depth and signature-count limits) before deriving addresses or persisting them, rejecting malformed pubkeys and deeply nested multisigs. (sei-protocol/sei-chain#3791) --- evm/transactions.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/evm/transactions.mdx b/evm/transactions.mdx index 203c18c..e14f685 100644 --- a/evm/transactions.mdx +++ b/evm/transactions.mdx @@ -290,6 +290,25 @@ Before Sei accepts an EVM transaction, it performs strict semantic validation of - [Transaction types](/evm/evm-parity/transaction-types#access-list-and-auth-list-entry-validation) is the canonical field-level reference. It covers signature-value byte caps and zero-padding rules, access-list entries, and EIP-7702 authorization lists. - [Differences with Ethereum](/evm/differences-with-ethereum#evm-transaction-envelope-restrictions) covers envelope-level restrictions: no Cosmos wrapper fields, canonical protobuf encoding, and whole-block rejection on decode failure. + + +### Cosmos transaction public-key validation + +For Cosmos transactions, the ante handlers now validate every public key provided in a transaction before deriving addresses from it or persisting it to account state. This validation runs during both `CheckTx` and `DeliverTx`, so a transaction carrying a malformed key is rejected rather than silently storing an unusable key on the account. + +A public key is rejected when: + +- A `secp256k1` key has the wrong byte size or fails to parse as a valid curve point — returns `ErrInvalidPubKey`. +- A multisig key has a threshold of zero, or a threshold greater than the number of member keys — returns `ErrInvalidPubKey`. +- A multisig key is nested more than 5 levels deep — returns `ErrInvalidPubKey`. +- The number of member keys consumed exceeds the per-pubkey signature budget derived from the auth module's `TxSigLimit` — returns `ErrTooManySignatures`. + +The recursive work budget is kept local to each provided public key, so one multisig tree is bounded on its own without changing the aggregate `TxSigLimit` behavior applied to keys persisted to account state. + + + If you build and sign Cosmos transactions manually (for example, assembling multisig keys), ensure member keys are well formed and that multisig nesting stays within 5 levels to avoid rejection. + + ### Receipts for nonce-bumping failed transactions Some EVM transactions pass basic validation and bump the sender's nonce, but still fail during state transition. One example is a transaction whose gas limit clears the intrinsic-gas check but falls short of the EIP-7623 floor-data-gas requirement. This can occur in normal operation after Pectra. Because these transactions bump the nonce, they are considered to have happened and therefore produce a receipt. From c2dc390b62bb1f5a98c64c90706a6c9f6ed4c8dd Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:18:20 +0000 Subject: [PATCH 38/41] docs: Distribution reward queries (DelegationRewards / DelegationTotalRewards) are now read-only and no longer mutate validator-period state as of v6.7.0, making them idempotent. (sei-protocol/sei-chain#3738) --- evm/precompiles/distribution.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/evm/precompiles/distribution.mdx b/evm/precompiles/distribution.mdx index 360ff67..0399c1f 100644 --- a/evm/precompiles/distribution.mdx +++ b/evm/precompiles/distribution.mdx @@ -262,6 +262,10 @@ require(success, "Failed to withdraw commission"); Returns the reward information for a delegator across all validators. + + +**Read-only as of v6.7.0 (#)**: The distribution reward query — including this `rewards()` precompile, the gRPC `DelegationRewards`/`DelegationTotalRewards` queries, and the legacy querier — is read-only and idempotent as of v6.7.0. It computes outstanding rewards without mutating validator-period state, so repeated calls return identical results and leave distribution state unchanged. Before v6.7.0 the query incremented the validator period on each call. The old mutating behavior is reproduced only when re-tracing pre-v6.7.0 blocks via `debug_trace*`. + ```solidity function rewards(address delegatorAddress) external view returns (Rewards rewards); ``` From 4311939248e85ce71953655fa63e8bcaa7b4c078 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:19:04 +0000 Subject: [PATCH 39/41] docs: The SEI_CONFIG_MANAGER=v2 environment variable now activates a sei-config-backed manager that performs advisory config validation (logging warnings for invalid fields) while booting identically to legacy; previously v2 was an unimplemented stub that errored. (sei-protocol/sei-chain#3678) --- node/technical-reference.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node/technical-reference.mdx b/node/technical-reference.mdx index 6e43d17..cbc4c35 100644 --- a/node/technical-reference.mdx +++ b/node/technical-reference.mdx @@ -42,7 +42,7 @@ seid query node info `seid` reads the experimental `SEI_CONFIG_MANAGER` environment variable to select which configuration manager resolves a node's configuration at startup. The value is matched exactly — it is not trimmed or case-folded. - Unset or `legacy`: uses the existing legacy config loader. This is the default and leaves the configuration path unchanged. -- `v2`: selects the new sei-config-backed manager. This manager is not yet implemented and fails fast with a hard error (`SEI_CONFIG_MANAGER=v2 not yet implemented`) rather than silently falling back to the legacy path, so a `v2` invocation is observable. +- `v2`: selects the sei-config-backed manager. This manager boots the node identically to the legacy path — it re-enters the legacy reader on your original `config.toml` and `app.toml`, and never rewrites those files or refuses boot. In addition, it runs an advisory config-validation pass that logs diagnostics at warn level (for example, a missing `chain.min_gas_prices`) without changing the boot outcome. Validation is non-fatal: a diagnostic is informational only. Because the pass reads the on-disk config before the node generates its own files, a brand-new node is not validated on its first boot; diagnostics first appear from the second start onward. - Any other value: `seid` refuses to start and reports an `invalid SEI_CONFIG_MANAGER` error naming the legal tokens (unset, `legacy`, or `v2`). There is no silent fallback. ```bash @@ -52,7 +52,8 @@ seid start # Equivalent to the default SEI_CONFIG_MANAGER=legacy seid start -# Selects the v2 manager, which currently returns a hard error +# Selects the v2 manager, which boots identically to legacy while emitting +# advisory config-validation warnings to the logs SEI_CONFIG_MANAGER=v2 seid start ``` From d30f9c63254b9f70ecd1a5ce272aebfb2858d437 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:19:34 +0000 Subject: [PATCH 40/41] docs: The slashing precompile adds a new unjail() transaction method allowing a validator operator to unjail their validator via the EVM precompile at the slashing precompile address. (sei-protocol/sei-chain#3825) --- evm/precompiles/example-usage.mdx | 2 +- evm/precompiles/slashing.mdx | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/evm/precompiles/example-usage.mdx b/evm/precompiles/example-usage.mdx index 70d6cdd..5d336f7 100644 --- a/evm/precompiles/example-usage.mdx +++ b/evm/precompiles/example-usage.mdx @@ -29,7 +29,7 @@ Sei precompiles are special smart contracts deployed at fixed addresses. They ex | P256 | `0x0000000000000000000000000000000000001011` | Verify P-256 elliptic curve signatures | | Mint | `0x0000000000000000000000000000000000001012` | Query the mint params and minter state | | Params | `0x0000000000000000000000000000000000001013` | Query module params by subspace and key | -| Slashing | `0x0000000000000000000000000000000000001014` | Query slashing params and validator signing info | +| Slashing | `0x0000000000000000000000000000000000001014` | Query slashing params and validator signing info, and unjail a validator via `unjail()` | | Upgrade | `0x0000000000000000000000000000000000001015` | Query the current/applied upgrade plan and module versions | This page shows Bank, Staking, Governance, Distribution, JSON, and Solo. The other precompiles have their own pages: [CosmWasm](/evm/precompiles/cosmwasm-precompiles/cosmwasm), [Address](/evm/precompiles/cosmwasm-precompiles/addr), [Pointer contracts](/evm/evm-parity/examples/pointer-contracts) for both pointer precompiles, and [P256](/evm/precompiles/p256-precompile). diff --git a/evm/precompiles/slashing.mdx b/evm/precompiles/slashing.mdx index 2ba8739..f4f0673 100644 --- a/evm/precompiles/slashing.mdx +++ b/evm/precompiles/slashing.mdx @@ -109,6 +109,26 @@ Returns the signing information for a single validator identified by its consens Querying a consensus address with no signing information reverts. + + +### `unjail` + +```solidity +function unjail() external returns (bool success); +``` + +Unjails the validator whose operator address is the caller's associated Sei address. This is a state-mutating transaction, so the caller's EVM address must be associated with a Sei address, and that Sei address must be a validator operator. + +**Returns** + +| Field | Type | Description | +| --------- | ------ | ---------------------------------------- | +| `success` | `bool` | `true` when the validator was unjailed. | + +The call reverts if the caller has no associated Sei address, if that address is not a validator operator, or if the validator does not currently satisfy the conditions required to be unjailed. + +Because this method acts on behalf of the caller, it is non-payable and cannot be invoked via `delegatecall` or `staticcall`; such calls revert. + ### `signingInfos` ```solidity From 0b15a48a1d1e5e73047c2cab3f64317c05b75a87 Mon Sep 17 00:00:00 2001 From: "seidroid[bot]" <257742136+seidroid[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:21:40 +0000 Subject: [PATCH 41/41] docs: EVM WebSocket plane (:8546) now shares HTTP admission-control config (max_request_body_bytes, max_concurrent_request_bytes) and adds a new ws_admission_timeout config field, with the WS frame size default dropping from 10 MiB to 5 MiB requiring operator action. (sei-protocol/sei-chain#3818) --- evm/evm-parity/websocket.mdx | 20 ++++++++++++++++++++ node/advanced-config-monitoring.mdx | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/evm/evm-parity/websocket.mdx b/evm/evm-parity/websocket.mdx index f8f84ea..3a680cb 100644 --- a/evm/evm-parity/websocket.mdx +++ b/evm/evm-parity/websocket.mdx @@ -115,6 +115,26 @@ const unwatch = client.watchEvent({ - Pending transaction subscriptions (`newPendingTransactions`) are supported at the RPC level, but Sei does not guarantee Ethereum-style pending state visibility. +## Frame size and concurrency limits + +The WebSocket plane shares its admission-control settings with the HTTP JSON-RPC plane through the `[evm]` section of `app.toml`. Two limits apply to every WebSocket connection: + +### Frame size (`max_request_body_bytes`) + +Each inbound WebSocket frame is capped by `[evm].max_request_body_bytes`, the same knob that bounds HTTP request bodies. The default is 5 MiB (`5242880`). Frames larger than this limit cause the connection to close with WebSocket close code `1009` (message too big); no JSON-RPC error response is returned. + + + Earlier node releases used a hardcoded 10 MiB frame cap on the WebSocket plane. Both planes now share `max_request_body_bytes` with a 5 MiB default. Operators whose WebSocket clients send frames in the 5–10 MiB range (for example large `eth_sendRawTransaction` batches or wide filter payloads) should set `max_request_body_bytes = 10485760` in `app.toml` before upgrading. Note that this also raises the HTTP body limit to 10 MiB. + + +### Concurrent request budget (`max_concurrent_request_bytes` and `ws_admission_timeout`) + +`[evm].max_concurrent_request_bytes` bounds the total size of JSON-RPC request bytes admitted for processing concurrently. The WebSocket plane and the HTTP plane each get their own independent budget, so peak in-flight request bytes process-wide can reach twice this value. + +When a WebSocket connection cannot be admitted immediately because the budget is exhausted, it blocks and waits for budget to free up. `[evm].ws_admission_timeout` bounds how long it waits (default `30s`; zero or negative values use the go-ethereum default of 30s). If the wait expires, the peer receives JSON-RPC error `-32005` ("timed out waiting for concurrent request-byte budget") and the connection is closed, dropping any active subscriptions along with it. + + + ## `newHeads` under Autobahn consensus diff --git a/node/advanced-config-monitoring.mdx b/node/advanced-config-monitoring.mdx index e57a897..431c22b 100644 --- a/node/advanced-config-monitoring.mdx +++ b/node/advanced-config-monitoring.mdx @@ -583,7 +583,7 @@ Every OpenTelemetry metric series exported through the Prometheus exporter (the | `evmrpc_websocket_connects_total` | Counter | Number of new WebSocket connections. | | `evmrpc_redirected_requests_total` | Counter | Number of EVM RPC requests forwarded to another validator. Labeled by `endpoint` and `connection`. | | `evmrpc_historical_debug_trace_attempts_total` | Counter | Number of `debug_trace*` requests that target historical blocks beyond the configured max block lookback. Labeled by `endpoint` and `connection`. | -| `evmrpc_requests_rejected_total` | Counter | Number of HTTP JSON-RPC requests rejected by pre-decode admission control. Labeled by `reason`, which is `oversize` when a request body exceeds the configured `max_request_body_bytes` cap (rejected with HTTP 413) or `busy` when the `max_concurrent_request_bytes` budget is exhausted (rejected with HTTP 429). | +| `evmrpc_requests_rejected_total` | Counter | Number of JSON-RPC requests rejected by admission control. Labeled by `protocol` (`http` or `ws`) and `reason` (`oversize` or `busy`). On HTTP (:8545), an `oversize` request body exceeds the configured `max_request_body_bytes` cap and is rejected with HTTP 413, while `busy` means the `max_concurrent_request_bytes` budget is exhausted and the request is rejected with HTTP 429. On WebSocket (:8546), `oversize` frames exceed `max_request_body_bytes` and close the connection with WebSocket close code 1009 (no JSON-RPC error response), while `busy` means the connection waited for concurrent-byte budget longer than `ws_admission_timeout` and the peer receives JSON-RPC error `-32005` before the connection is closed (#3818). | The `evmrpc_request_latency_seconds` histogram carries these labels: