docs(harbor-dev): sender-owner routing — enableEvmProxy: false + seiload shardRouting from autobahn.json (PLT-1283) - #456
Conversation
… + seiload shardRouting from autobahn.json (PLT-1283) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
PR SummaryLow Risk Overview The Reviewed by Cursor Bugbot for commit d0e4527. Bugbot is set up for automated code reviews on this repo. Configure here. |
| | "Run a load test against chain X" / "bench it" / "stress test chain X" | **PR-based bench** (see Procedure: spin up a load test). Live-fetch chain rpc per-pod URLs, substitute into the profile JSON, render Job + ConfigMap from the templates in `references/sei-load-bench.md`, open a PR against `harbor-engineering-workspace` at `engineers/<alias>/bench-<RUN_ID>/`. Merge → Flux applies → seiload runs → the uploader sidecar pushes the results to S3. When the host lists the `sei-load` MCP tools, call them for the scenario menu and the profile verdict. Fall back to the docker path when it does not, and never block on them (`references/sei-load-bench.md`, *`seiload mcp`*). | | ||
| | "Compare PR 3399 to main on sei-chain" / "bench A against B" / "diff the perf of these two commits" | **PR-based comparative bench** (see Procedure: comparative bench). Renders two ephemeral chains (each running its own seid image) + two sei-load Jobs (identical profile + duration) into a single PR. After merge, watches both chains in parallel and polls both Jobs to terminal. Then fetches both reports from S3 and surfaces a side-by-side metrics table (TPS / latency / success rate / errors). Lives at `engineers/<alias>/compare-<COMPARE_RUN_ID>/`. | | ||
| | "Spin up a giga chain" / "enable the giga executor" / "autobahn network" / "evm-only chain" / "reproduce the autobahn README on harbor" | **Tier check first** (see `references/autobahn-giga.md`). Giga executor/storage = `spec.configValues` on a normal chain. Autobahn consensus and EVM-only = `spec.consensus: {engine: Autobahn, evmOnly}` on the SeiNetwork (`seictl network apply --consensus-engine Autobahn [--evm-only]`) — **gate on the CRD carrying `spec.consensus`** (sei-k8s-controller#553) before rendering; on an older cell offer the Giga subset and ask for a controller pin advance (file it as a PLT-1259 follow-up). Never set `evm-only`, `autobahn-config-file` or the RPC/API/gRPC listener keys through `configValues`: the controller owns them and refuses the entry. | | ||
| | "Spin up a giga chain" / "enable the giga executor" / "autobahn network" / "evm-only chain" / "proxy off" / "route to the owning validator" / "reproduce the autobahn README on harbor" | **Tier check first** (see `references/autobahn-giga.md`). Giga executor/storage = `spec.configValues` on a normal chain. Autobahn consensus and EVM-only = `spec.consensus: {engine: Autobahn, evmOnly}` on the SeiNetwork (`seictl network apply --consensus-engine Autobahn [--evm-only]`) — **gate on the CRD carrying `spec.consensus`** (sei-k8s-controller#553) before rendering; on an older cell offer the Giga subset and ask for a controller pin advance (file it as a PLT-1259 follow-up). Never set `evm-only`, `autobahn-config-file` or the RPC/API/gRPC listener keys through `configValues`: the controller owns them and refuses the entry. | |
There was a problem hiding this comment.
Descriptive sentence has 27 words. ASD-STE100 allows 25. Split it.
| | "Run a load test against chain X" / "bench it" / "stress test chain X" | **PR-based bench** (see Procedure: spin up a load test). Live-fetch chain rpc per-pod URLs, substitute into the profile JSON, render Job + ConfigMap from the templates in `references/sei-load-bench.md`, open a PR against `harbor-engineering-workspace` at `engineers/<alias>/bench-<RUN_ID>/`. Merge → Flux applies → seiload runs → the uploader sidecar pushes the results to S3. When the host lists the `sei-load` MCP tools, call them for the scenario menu and the profile verdict. Fall back to the docker path when it does not, and never block on them (`references/sei-load-bench.md`, *`seiload mcp`*). | | ||
| | "Compare PR 3399 to main on sei-chain" / "bench A against B" / "diff the perf of these two commits" | **PR-based comparative bench** (see Procedure: comparative bench). Renders two ephemeral chains (each running its own seid image) + two sei-load Jobs (identical profile + duration) into a single PR. After merge, watches both chains in parallel and polls both Jobs to terminal. Then fetches both reports from S3 and surfaces a side-by-side metrics table (TPS / latency / success rate / errors). Lives at `engineers/<alias>/compare-<COMPARE_RUN_ID>/`. | | ||
| | "Spin up a giga chain" / "enable the giga executor" / "autobahn network" / "evm-only chain" / "reproduce the autobahn README on harbor" | **Tier check first** (see `references/autobahn-giga.md`). Giga executor/storage = `spec.configValues` on a normal chain. Autobahn consensus and EVM-only = `spec.consensus: {engine: Autobahn, evmOnly}` on the SeiNetwork (`seictl network apply --consensus-engine Autobahn [--evm-only]`) — **gate on the CRD carrying `spec.consensus`** (sei-k8s-controller#553) before rendering; on an older cell offer the Giga subset and ask for a controller pin advance (file it as a PLT-1259 follow-up). Never set `evm-only`, `autobahn-config-file` or the RPC/API/gRPC listener keys through `configValues`: the controller owns them and refuses the entry. | | ||
| | "Spin up a giga chain" / "enable the giga executor" / "autobahn network" / "evm-only chain" / "proxy off" / "route to the owning validator" / "reproduce the autobahn README on harbor" | **Tier check first** (see `references/autobahn-giga.md`). Giga executor/storage = `spec.configValues` on a normal chain. Autobahn consensus and EVM-only = `spec.consensus: {engine: Autobahn, evmOnly}` on the SeiNetwork (`seictl network apply --consensus-engine Autobahn [--evm-only]`) — **gate on the CRD carrying `spec.consensus`** (sei-k8s-controller#553) before rendering; on an older cell offer the Giga subset and ask for a controller pin advance (file it as a PLT-1259 follow-up). Never set `evm-only`, `autobahn-config-file` or the RPC/API/gRPC listener keys through `configValues`: the controller owns them and refuses the entry. | |
There was a problem hiding this comment.
Descriptive sentence has 42 words. ASD-STE100 allows 25. Split it.
| | `image-resolution.md` | **Canonical image-resolution recipes** for sei-chain (ECR) and sei-load (GHCR). PR/commit/branch input → full SHA → expected tag → registry probe → trigger + watch the build workflow if missing | | ||
| | `state-sync-bootstrap.md` | **Read this if the engineer wants a node that bootstraps via state sync from their own chain.** The `rpcServers` witness recipe: witnesses-vs-snapshot-providers model, preconditions, spec shape, `StateSyncReady` gate semantics, failure table (incl. the wrong-chain witness footgun) | | ||
| | `autobahn-giga.md` | **Read this if the engineer says giga, autobahn, evm-only, flatkv, or cites `sei-chain/integration_test/autobahn/README.md`.** Three tiers (Giga via `configValues`; Autobahn and EVM-only via `spec.consensus`, gated on the controller carrying #553), the `app.toml` key set for executor + storage + receipts with the `sc-write-mode` enum, the README-to-CRD mapping with the controller-owned keys `configValues` must not touch, `spec.genesis.consensusParams` for `max_gas`, the follower/seed/freeze rules, and every assumption elsewhere in this skill that EVM-only breaks (two-method RPC, validators serve EVM, height 0 idle, no funding) | | ||
| | `autobahn-giga.md` | **Read this if the engineer says giga, autobahn, evm-only, flatkv, or cites `sei-chain/integration_test/autobahn/README.md`.** Three tiers (Giga via `configValues`; Autobahn and EVM-only via `spec.consensus`, gated on the controller carrying #553), the `app.toml` key set for executor + storage + receipts with the `sc-write-mode` enum, the README-to-CRD mapping with the controller-owned keys `configValues` must not touch, `spec.genesis.consensusParams` for `max_gas`, the follower/seed/freeze rules, sender-owner routing (`spec.consensus.autobahn.enableEvmProxy: false` paired with a seiload `shardRouting` block built from the validators' `autobahn.json`), and every assumption elsewhere in this skill that EVM-only breaks (two-method RPC, validators serve EVM, height 0 idle, no funding) | |
There was a problem hiding this comment.
Descriptive sentence has 78 words. ASD-STE100 allows 25. Split it.
|
|
||
| ## Sender-owner routing — `enableEvmProxy: false` + seiload `shardRouting` | ||
|
|
||
| Autobahn shards the EVM mempool by sender: `sha256(address) mod total_power` over the validators sorted by consensus key picks the one validator that sequences that sender (`sei-tendermint` `Committee.EvmShard`). With `enable_evm_proxy` on (the default) a validator forwards a foreign sender's transaction to its owner — a synchronous hop the bench then measures. `spec.consensus.autobahn.enableEvmProxy: false` (controller #560, `kubectl explain seinetwork.spec.consensus.autobahn.enableEvmProxy` is the gate) removes the hop, and then a transaction that reaches the wrong validator is not forwarded. seiload's default `endpoints` spread — sender address modulo endpoint count — is a different rule, so with proxying off most of its transactions land on a non-owner and the run measures rejections. The profile's `shardRouting` block (sei-load #117) makes seiload compute the same owner and dial it directly: |
There was a problem hiding this comment.
Descriptive sentence has 31 words. ASD-STE100 allows 25. Split it.
|
|
||
| ## Sender-owner routing — `enableEvmProxy: false` + seiload `shardRouting` | ||
|
|
||
| Autobahn shards the EVM mempool by sender: `sha256(address) mod total_power` over the validators sorted by consensus key picks the one validator that sequences that sender (`sei-tendermint` `Committee.EvmShard`). With `enable_evm_proxy` on (the default) a validator forwards a foreign sender's transaction to its owner — a synchronous hop the bench then measures. `spec.consensus.autobahn.enableEvmProxy: false` (controller #560, `kubectl explain seinetwork.spec.consensus.autobahn.enableEvmProxy` is the gate) removes the hop, and then a transaction that reaches the wrong validator is not forwarded. seiload's default `endpoints` spread — sender address modulo endpoint count — is a different rule, so with proxying off most of its transactions land on a non-owner and the run measures rejections. The profile's `shardRouting` block (sei-load #117) makes seiload compute the same owner and dial it directly: |
There was a problem hiding this comment.
📝 [vale] <AgenticWriting.STE-NounCluster> reported by reviewdog 🐶
Noun cluster of more than three nouns: 'sender address modulo endpoint'. Break it with 'of' or 'for'.
| - Engineer asks for Autobahn or EVM-only on a cell whose CRD gate (above) fails → offer Tier 1 (Giga on CometBFT) as the deployable subset, ask the platform team to advance the controller pin. Never render `evm-only = true` or `autobahn-config-file` through `configValues` — on a new controller it is refused at plan build, on an old one it is a pod that never reads Ready. | ||
| - Engineer asks for a non-default `autobahn.json` field → `spec.consensus.autobahn.{blockInterval, allowEmptyBlocks, maxTxsPerBlock}` in the manifest (controller #555; `kubectl explain seinetwork.spec.consensus.autobahn` is the gate, and there is no seictl flag). Create-only: a change is a new chain. Any other `autobahn.json` field is not expressible; say so rather than editing the file in the pod. `maxTxsPerBlock` above 2000 is refused by the CRD; the protocol clamps there anyway. | ||
| - Follower requested on an EVM-only chain → ask what it is for; the validators serve `:8545` and a follower serves nothing the bench reads. | ||
| - `enableEvmProxy: false` requested with a profile that has no `shardRouting` block, or `shardRouting` requested on a seiload image predating sei-load #117 → refuse to render the bench; the first measures rejections, the second fails at the strict decoder. Both fixes are in *Sender-owner routing* above. |
There was a problem hiding this comment.
Descriptive sentence has 34 words. ASD-STE100 allows 25. Split it.
| - Engineer asks for a non-default `autobahn.json` field → `spec.consensus.autobahn.{blockInterval, allowEmptyBlocks, maxTxsPerBlock}` in the manifest (controller #555; `kubectl explain seinetwork.spec.consensus.autobahn` is the gate, and there is no seictl flag). Create-only: a change is a new chain. Any other `autobahn.json` field is not expressible; say so rather than editing the file in the pod. `maxTxsPerBlock` above 2000 is refused by the CRD; the protocol clamps there anyway. | ||
| - Follower requested on an EVM-only chain → ask what it is for; the validators serve `:8545` and a follower serves nothing the bench reads. | ||
| - `enableEvmProxy: false` requested with a profile that has no `shardRouting` block, or `shardRouting` requested on a seiload image predating sei-load #117 → refuse to render the bench; the first measures rejections, the second fails at the strict decoder. Both fixes are in *Sender-owner routing* above. | ||
| - `enableEvmProxy` requested on a cell where `kubectl explain seinetwork.spec.consensus.autobahn.enableEvmProxy` fails → the CRD prunes it silently and the chain boots with the proxy on; ask the platform team to advance the controller pin past #560 rather than rendering. |
There was a problem hiding this comment.
Descriptive sentence has 33 words. ASD-STE100 allows 25. Split it.
| ### Profile shape | ||
|
|
||
| - **Envelope** (`config.LoadConfig`): `chainId`, `seiChainID` (the Go tag is `seiChainID`; `encoding/json` matches keys case-insensitively, so `seiChainId` in an older profile is accepted too — write `seiChainID`, as the nightly profiles do), `endpoints` (required; sending shards across them), `receiptEndpoint`, `accounts` (`config.AccountConfig`: `count`, `newAccountRate`), `scenarios[]`, `mockDeploy`, `settings`, `funding`, `reportPath`, `seed`. `gasFeeCapWei` is *not* a field — the fee cap resolves from the chain at startup. | ||
| - **Envelope** (`config.LoadConfig`): `chainId`, `seiChainID` (the Go tag is `seiChainID`; `encoding/json` matches keys case-insensitively, so `seiChainId` in an older profile is accepted too — write `seiChainID`, as the nightly profiles do), `endpoints` (required; sending shards across them), `receiptEndpoint`, `accounts` (`config.AccountConfig`: `count`, `newAccountRate`), `scenarios[]`, `mockDeploy`, `settings`, `funding`, `reportPath`, `seed`, `shardRouting` (sei-load #117: `{validators: [{consensusPubKey, power, endpoint}]}` — present, sends dial the Autobahn validator that owns each sender instead of spreading over `endpoints`; required on an `enableEvmProxy: false` chain, built from the validators' `autobahn.json` per `autobahn-giga.md` *Sender-owner routing*; `generate_profile` does not emit it, merge it with `jq` after generation and validate again). `gasFeeCapWei` is *not* a field — the fee cap resolves from the chain at startup. |
There was a problem hiding this comment.
Descriptive sentence has 66 words. ASD-STE100 allows 25. Split it.
| ### Profile shape | ||
|
|
||
| - **Envelope** (`config.LoadConfig`): `chainId`, `seiChainID` (the Go tag is `seiChainID`; `encoding/json` matches keys case-insensitively, so `seiChainId` in an older profile is accepted too — write `seiChainID`, as the nightly profiles do), `endpoints` (required; sending shards across them), `receiptEndpoint`, `accounts` (`config.AccountConfig`: `count`, `newAccountRate`), `scenarios[]`, `mockDeploy`, `settings`, `funding`, `reportPath`, `seed`. `gasFeeCapWei` is *not* a field — the fee cap resolves from the chain at startup. | ||
| - **Envelope** (`config.LoadConfig`): `chainId`, `seiChainID` (the Go tag is `seiChainID`; `encoding/json` matches keys case-insensitively, so `seiChainId` in an older profile is accepted too — write `seiChainID`, as the nightly profiles do), `endpoints` (required; sending shards across them), `receiptEndpoint`, `accounts` (`config.AccountConfig`: `count`, `newAccountRate`), `scenarios[]`, `mockDeploy`, `settings`, `funding`, `reportPath`, `seed`, `shardRouting` (sei-load #117: `{validators: [{consensusPubKey, power, endpoint}]}` — present, sends dial the Autobahn validator that owns each sender instead of spreading over `endpoints`; required on an `enableEvmProxy: false` chain, built from the validators' `autobahn.json` per `autobahn-giga.md` *Sender-owner routing*; `generate_profile` does not emit it, merge it with `jq` after generation and validate again). `gasFeeCapWei` is *not* a field — the fee cap resolves from the chain at startup. |
There was a problem hiding this comment.
Passive voice: 'is accepted'. ASD-STE100 asks for the active voice.
| - `spec.dataVolume.storage.volumeAttributesClassName` — the storage performance selection for each pool validator, a sibling of the size path above. Names a platform-managed VolumeAttributesClass, which carries the gp3 IOPS and throughput. `network apply` resolves the name from the `--iops` and `--throughput` pair you supply. Unset means the standard tier. **Create-only.** | ||
| - `spec.consensus` — `{engine: Tendermint|Autobahn, evmOnly: bool}` (`--consensus-engine`, `--evm-only`), copied into every validator child. Omitted resolves to Tendermint. `evmOnly: true` requires `engine: Autobahn` (CEL). **Create-only on its effective value** — changing the engine or `evmOnly` is a new chain. Under Autobahn the ceremony publishes `autobahn.json` beside `genesis.json` and the controller owns `config.toml` `autobahn-config-file`/`evm-only` and the listener toggles; a `configValues` entry on those keys fails plan build. See `autobahn-giga.md`. | ||
| - `spec.consensus.autobahn` — `{blockInterval, allowEmptyBlocks, maxTxsPerBlock}`, the operator-settable slice of the ceremony's `autobahn.json`; requires `engine: Autobahn` (CEL). Omitted fields keep `gen-autobahn-config` defaults (`400ms`, `false`, `2000`). `blockInterval` is a positive Go duration; `maxTxsPerBlock` is `1..2000` — 2000 is the protocol ceiling, so only lowering it changes anything. **Create-only.** No seictl flag yet; set it in the manifest. See `autobahn-giga.md`. | ||
| - `spec.consensus.autobahn` — `{blockInterval, allowEmptyBlocks, maxTxsPerBlock, enableEvmProxy}` (the last from #560: `false` stops validators forwarding foreign senders' EVM transactions, so the load generator must route to the owner itself — `autobahn-giga.md` *Sender-owner routing*), the operator-settable slice of the ceremony's `autobahn.json`; requires `engine: Autobahn` (CEL). Omitted fields keep `gen-autobahn-config` defaults (`400ms`, `false`, `2000`). `blockInterval` is a positive Go duration; `maxTxsPerBlock` is `1..2000` — 2000 is the protocol ceiling, so only lowering it changes anything. **Create-only.** No seictl flag yet; set it in the manifest. See `autobahn-giga.md`. |
There was a problem hiding this comment.
Descriptive sentence has 31 words. ASD-STE100 allows 25. Split it.
|
Settled by the scouts (codex) without a review turn: every one of them read the diff, found nothing, and reported it inert -- comments, documentation or whitespace only, with no executable surface changed. seidroid review · decision Findings: 0 blocking | 0 non-blocking | 0 posted inline |
Summary
Teaches
/harbor-devthe one thing PLT-1283 left unwired on the Harbor side: benching an Autobahn chain withspec.consensus.autobahn.enableEvmProxy: false(sei-k8s-controller #560) needs the seiload profile to carryshardRouting(sei-load #117) so seiload sends each transaction straight to the validator that owns the sender. Without it seiload'saddr mod len(endpoints)spread lands most sends on a non-owner and the run measures rejections.No controller, S3, IAM or init-container work: every input already exists on the cluster once the network is
Ready. The new Sender-owner routing section inreferences/autobahn-giga.mdreadsautobahn.jsonoff a validator pod (the file the existing verification loop already checks for) and shapes it:Rules carried with consequences:
power: 1is exact only while every gentx stakes the samestakingAmount(the presets do);endpointsstays on followers (Tier 2) or the same validators (Tier 3) for the trackers; never pass--nodeswithshardRouting(seiload refuses); image must carry sei-load #117 (older strict decoder rejects the key);enableEvmProxyis create-only with the rest ofspec.consensus.Also:
enableEvmProxyadded to the Tier 2/3 YAML example andseinetwork-crd.md;shardRoutingadded to the envelope list insei-load-bench.md; two new halt conditions (proxy off withoutshardRouting,enableEvmProxyon a CRD that prunes it); intent-table triggers "proxy off" / "route to the owning validator" inSKILL.md.Checks:
skill-package-checks.sh --skill-dir .claude/skills/harbor-devunchanged (only the pre-existing T2/A1 findings); vale reports no errors on the touched files.Link to Devin session: https://app.devin.ai/sessions/60cb09e09f2f45e08c4e1dba208e1074
Open in Devin Desktop: https://app.devin.ai/desktop/session/60cb09e09f2f45e08c4e1dba208e1074?variant=devin
Requested by: @bdchatham