feat(seeds): ship Sei Labs seeds as the default bootstrap-peers - #3885
feat(seeds): ship Sei Labs seeds as the default bootstrap-peers#3885monty-sei wants to merge 7 commits into
Conversation
PR SummaryMedium Risk Overview A new
Note: Reviewed by Cursor Bugbot for commit 6666ff7. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3885 +/- ##
==========================================
+ Coverage 59.48% 60.75% +1.27%
==========================================
Files 2323 2256 -67
Lines 198554 190268 -8286
==========================================
- Hits 118106 115597 -2509
+ Misses 69240 64447 -4793
+ Partials 11208 10224 -984
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Small, well-documented change that defaults p2p.bootstrap-peers to the Sei Labs seeds at seid init; the data package and its tests are solid and the wiring is correctly scoped (empty-value only, exact chain-id match, no runtime mutation). No blockers — the notes are about the untested production wiring, an inline comment that AGENTS.md would have as a named step, and two small test/API cleanups.
Findings: 0 blocking | 9 non-blocking | 5 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- No test exercises
InitCmd'sRunE, so nothing covers the new wiring (shared finding with Codex).cmd/seid/cmd/init_test.gore-simulates config construction (tmcfg.DefaultConfig()+SetTendermintConfigByMode+WriteConfigFile) rather than running the command, so deleting init.go:128-130 leaves every test in the tree passing. Theapp/seedstests only cover the data. The manual verification table in the PR description is the only evidence the feature works; extracting the step into a named helper (see the inline note) makes it assertable without standing up the whole command. - The Cursor second-opinion pass produced no output —
cursor-review.mdis empty. Only the Codex pass contributed findings (its single P2 item is folded in above). - The PR description says "Operator value always wins," which is true for the value but worth stating precisely in release notes:
seid initbuildstmConfigfromtmcfg.DefaultConfig(), so there is no way for an operator to supplybootstrap-peerstoinitat all, andinit --overwriterewritesconfig.tomlwholesale. The behavior change there is that an operator's hand-editedbootstrap-peersis now replaced by the Sei seeds instead of by""— not a regression, but not "the operator value survives" either. - Verified against REVIEW_GUIDELINES.md and testutil/configtest: no configtest row is owed here. The suite pins configuration reads, and this change is an init-time write of a default;
p2p.bootstrap-peersis not in the tendermint precedence manifest (onlyp2p.persistent-peersis), so nothing in that suite fails or needs updating. - 5 suggestion(s)/nit(s) flagged inline on specific lines.
| cmd.Flags().BoolP(FlagOverwrite, "o", false, "overwrite the genesis.json and existing config files (config.toml, app.toml)") | ||
| cmd.Flags().Bool(FlagRecover, false, "provide seed phrase to recover existing key instead of creating") | ||
| cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will use sei") | ||
| cmd.Flags().String(flags.FlagChainID, "", "chain-id to initialise for (required), e.g. pacific-1 or atlantic-2") |
There was a problem hiding this comment.
[nit] Good fix to the help text. Since the drive-by is specifically about the mismatch between "if left blank will use sei" and the code's behavior, the other half is still there: line 120 panic("chain-id is required, ...") prints a Go stack trace for what is plain operator error. RunE already returns errors for the invalid-mode case a few lines up — return fmt.Errorf("chain-id is required, please set using --chain-id") would match that and give a clean message. Fine to leave for a separate PR.
A fresh `seid init --chain-id pacific-1` (or atlantic-2) now writes a config.toml with bootstrap-peers already populated, so a node bootstraps peer discovery with no operator configuration. Previously the field defaulted to "" and operators had to source a peer list out of band. Adds app/seeds, mirroring the existing app/genesis pattern for well-known chain data, and consults it in InitCmd after the chain-id is resolved. - An operator-supplied bootstrap-peers always wins; we only fill an empty value. An unrecognised chain-id is a no-op, so private and local chains are unaffected. - Seeds go in bootstrap-peers rather than persistent-peers: they seed the address book via PEX and may then be dropped, and pinning operators to long-lived connections against them is wrong for them and a load multiplier for us. - arctic-1 is deliberately excluded. It is a devnet with no Cosmos chain-registry entry, and a devnet is the most likely network to be reset or re-keyed — the worst case for an address baked permanently into a release. Also corrects the --chain-id flag help, which claimed "if left blank will use sei" while the code panics on an empty value. Verified end to end: init on pacific-1 and atlantic-2 yields three seeds each, arctic-1 and unknown chain-ids yield "".
ec0fff5 to
4c49b2b
Compare
There was a problem hiding this comment.
A small, well-scoped change that pre-populates bootstrap-peers at seid init for pacific-1/atlantic-2, with a genuinely thoughtful package doc and decent unit tests. No correctness or security blockers — the pinned NodeIDs mean a hijacked DNS name yields a rejected dial rather than a MITM — but the wiring in InitCmd is untested, and a couple of comments describe invariants the code doesn't actually establish.
Findings: 0 blocking | 9 non-blocking | 5 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- No test drives
InitCmd'sRunE, so the new wiring is uncovered (shared with Codex).TestInitModeConfigurationre-implements init's steps (tmcfg.DefaultConfig()→SetTendermintConfigByMode→WriteConfigFile) rather than invoking the command, so deleting the seeds block atcmd/seid/cmd/init.go:123-130would not fail a single test. Given this repo's characterization-test culture for configuration (seetestutil/configtest/AGENTS.md), a test that runs the command forpacific-1/atlantic-2/arctic-1/ a private chain-id and assertsp2p.bootstrap-peersin the writtenconfig.tomlwould pin exactly the four rows the PR description verified by hand. - Security posture is sound and worth recording: the seed list is the only new attack surface, and each address is bound to a pinned NodeID verified by the secret-connection handshake, so DNS compromise of
*.platform.sei.ioproduces a failed dial rather than a MITM; seeds only populate the address book via PEX and gate nothing in consensus. - The Cursor second-opinion file (
./cursor-review.md) is empty — that pass produced no output, so this review merges only Claude's and Codex's findings. - Optional, adjacent to the drive-by flag-help fix:
cmd/seid/cmd/init.go:120stillpanics on an empty chain-id inside aRunEthat returnserror. Now that the help text advertises the flag as required, returning an error (or marking the flag required via cobra) would give operators a clean message instead of a stack trace. Out of scope if you'd rather keep the diff tight. - 5 suggestion(s)/nit(s) flagged inline on specific lines.
Extracts the inline bootstrap-peers block into applyDefaultBootstrapPeers, so RunE reads as a sequence of named steps and the rationale lives in a doc comment rather than mid-flow. The extraction is what makes the wiring testable. TestInitCmdWrites- DefaultBootstrapPeers now executes InitCmd against a temp home and asserts the written config.toml; previously the wiring had no coverage at all — deleting the call left the whole suite green. Test hardening in app/seeds: - Hold NodeIDs against types.NodeID.Validate instead of a local regex, so the check cannot drift from CometBFT's definition (the old pattern also accepted a five-digit port). - Check NodeID and host uniqueness across the whole table rather than per chain. The likeliest copy/paste error is a pacific-1 entry pasted into the atlantic-2 block, which a per-chain check could not catch. Drops the exported Chains(), whose only caller was a same-package test that can range over chainSeeds directly. Also documents the actual --overwrite behaviour on the helper: init has no bootstrap-peers flag, so the empty check is defensive rather than an operator-precedence mechanism, and `init --overwrite` replaces a hand-edited value with the seeds (previously with "").
There was a problem hiding this comment.
Clean, well-tested change: app/seeds ships the Sei Labs seed list and applyDefaultBootstrapPeers wires it into seid init, with tests covering both the data and the call site. No blocking issues found; remaining notes are operational (verifying the baked-in addresses, release-notes coverage for init --overwrite) plus two nits.
Findings: 0 blocking | 7 non-blocking | 3 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- The Cursor second-opinion pass produced no output (
cursor-review.mdis empty); Codex reported no material issues. This review is therefore effectively a single-reviewer pass plus Codex. init --overwritenow replaces a hand-editedbootstrap-peerswith the Sei seeds instead of"". The PR description calls this out correctly and nothing in the repo documents it — worth making sure it actually lands in the release notes, since the only in-tree record is the doc comment onapplyDefaultBootstrapPeers.- The
--chain-idhelp text now says(required)while the code stillpanic()s on an empty value, printing a Go stack trace for plain operator error next to aRunE-returned error for the invalid-mode case. The author explicitly deferred thepanic→return fmt.Errorf(...)change to a tracked follow-up; noting only so it isn't lost, not asking for it here. - Verification note rather than a defect: the correctness of the six seed addresses cannot be checked from this repo — the tests validate form (NodeID validity,
:26656, uniqueness) but a well-formed wrong host or ID passes. Given the stated one-way permanence, a dial check against all six from outside the Sei network before the release is cut would be the thing that catches it. - 3 suggestion(s)/nit(s) flagged inline on specific lines.
| // sei-protocol/platform (the SeiNode's externalAddress plus its NodeID). | ||
| var chainSeeds = map[string][]string{ | ||
| "pacific-1": { | ||
| "0cd5f57c249b5aca815710338e1fe7a14797585d@seed-0-p2p.pacific-1.prod.platform.sei.io:26656", |
There was a problem hiding this comment.
[nit] Naming asymmetry worth a second look: seed-1/seed-2 carry an explicit cell suffix (prod-euw1, prod-use2) while seed-0 is bare prod, so the doc comment's "one per cell (eu-central-1, eu-west-1, us-east-2)" is only readable as three cells if you already know prod == eu-central-1. Not something the tests can catch (host uniqueness passes either way), and these strings are one-way once released — worth confirming against clusters/<cell>/<chain>/seeds/ that the unsuffixed name really is the eu-central-1 cell for both chains.
There was a problem hiding this comment.
Integration tests would be awesome to have where we assert those endpoints are connectable and are usable as seed, i.e. new peers are discovered from them. Those tests should be disabled by default and only run on CI since they would be making remote calls.
Excited to get this moving! 🙌
…h p2p Collapses app/seeds to the address table plus BootstrapPeers, the only thing anything outside the package uses. ForChain had no caller beyond this package, so exporting it (and defensively copying the slice for callers that did not exist) was API for nobody. With it gone, BootstrapPeers reads straight off the map: a missing chain yields nil, and strings.Join renders that as "". Re-exports ParseNodeAddress from sei-tendermint/config so the tests validate addresses with the parser the router actually dials with, rather than string-slicing and re-encoding the format locally. The parser lives under sei-tendermint/internal and is unreachable from app/seeds; config already surfaces p2p types (AutobahnValidator.NodeKey) and does not import back into it, so the re-export costs nothing. Test changes follow from that: - parse each address instead of splitting on "@", which also covers the port without asserting a specific one — 26656 is our convention, not a protocol rule - assert the seed list is non-empty rather than exactly three; the count is an artefact of how many we happen to run - drop the no-spaces assertion: SplitAndTrimEmpty trims the cutset, so the parser tolerates spaces and the assertion tested nothing - assert empty rather than nil for unknown chains, which is the contract - chain names as constants, testify to match app/genesis and app/params - drop the copy-semantics test, moot now the copy is gone Adds a build-tagged integration suite (go test -tags=integration) that dials every published seed and requires it to send its preface. A seed that accepts TCP and then says nothing is otherwise invisible: the pod reports Ready and seed mode publishes no metrics.
There was a problem hiding this comment.
Ships Sei Labs seed nodes as default bootstrap-peers at seid init time; the wiring, data format, and test coverage are sound (addresses parse with the same p2p.ParseNodeAddress the router uses, and the InitCmd end-to-end test closes the mutation gap). No correctness or security blockers — remaining notes are godoc-style violations of AGENTS.md, a port assertion the tests claim but don't make, and integration tests that are neither run nor compile-checked in CI.
Findings: 0 blocking | 11 non-blocking | 5 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- The Cursor second-opinion file (
./cursor-review.md) is empty — that pass produced no output, so this review merges only Claude's and Codex's findings. TestSeedAddressesAreDialableForm(integration) largely duplicatesTestSeedAddressesParseAndAreUnique(unit), but the hostname-shape and NodeID-length assertions live only in the tagged copy that never runs. Consider moving the pure-parsing assertions into the untagged test so they actually execute, leaving only the network calls behind the tag.require.Lenf(t, string(addr.NodeID), 40, ...)is redundant:ParseNodeAddress->NodeAddress.Validate->NodeID.Validatealready enforces exactly 40 lowercase hex chars, so the precedingrequire.NoErrorfsubsumes it.cmd/seid/cmd/bootstrap_peers_test.gomixes assertion styles — rawt.Errorfin the first three tests,testify/requirein the rest. Pick one for the file.- The
--overwritebehaviour change (a hand-editedbootstrap-peersis now replaced by the Sei seeds rather than by"") is correctly identified in the PR description; make sure it actually lands in the release notes, since nothing in the tree records it outside theapplyDefaultBootstrapPeersdoc comment. - No prompt-injection or instruction-like content was found in the diff, commit messages, or PR description.
- 5 suggestion(s)/nit(s) flagged inline on specific lines.
| @@ -0,0 +1,71 @@ | |||
| //go:build integration | |||
There was a problem hiding this comment.
[suggestion] (Also raised by Codex.) Nothing invokes go test -tags=integration ./app/seeds/... — not .github/workflows/, not the Makefile, not integration-test-matrix.json (which uses the unrelated yaml_integration tag).
Worse than just not running: .golangci.yml sets build-tags: [codeanalysis] and tests: false, and go build / go vet skip tagged files by default, so this file is never even compile-checked in CI. It can break against a config API change and stay green indefinitely.
A scheduled workflow (these assert live endpoint health, so cron fits better than per-PR) plus a make target would give the reachability check a way to actually fire. At minimum, add it to a compile-only step (go vet -tags=integration ./app/seeds/...) so it can't rot silently.
There was a problem hiding this comment.
Good question, and I went and checked: no, integration is a new tag. The repo currently uses norace, linux, inprocess, rocksdb, dummy, cgo, darwin, mock_balances, ledger, littdb_wip, windows, and the existing integration suite runs under a different one, yaml_integration, driven by .github/workflows/integration-test-matrix.json.
I looked at reusing that tag and decided against it: that suite is docker based (docker exec sei-node-0 ...) against a locally spun chain, whereas this one dials live public endpoints. Folding them together means either the docker matrix starts making external calls, or this gets dragged into a container lifecycle it does not need.
So for now I have gone the honest route rather than the half wired one: the file header now says plainly that nothing runs or compile checks it and that it is an on demand tool rather than coverage, and I have raised a follow up to add a scheduled (cron plus workflow_dispatch) job.
Scheduled rather than a PR gate, since it depends on live external infrastructure and would otherwise block unrelated PRs whenever a seed hiccups or CI egress is flaky. Sequencing it after the two silent seeds are fixed as well, because wiring it today means the job is red on its first run, which is the quickest way to train everyone to ignore it. Happy to bring it into this PR instead if you would rather it did not land separately!
|
Hey @masih! This is the comment I was most keen to action, so I've added a build tagged suite ( Worth flagging that it currently fails on two of the six, both in the prod cell, and that's a real finding rather than a flaky test. Those endpoints complete the TCP handshake and then send nothing at all, even holding the connection open for three minutes, while the ones in prod-euw1 and prod-use2 respond in under a second. I'm still digging into why and @bdchatham is looking into it as well, it needs a look from inside the cluster to tell whether the pod is serving at all or whether it's something in the load balancer path. Because seed mode publishes no metrics and the pods report Ready, there's currently nothing that would have told us, which I think makes a pretty good case for exactly the tests you're asking for! I'd like to get to the bottom of those two and have the full suite green before we merge, so the seeds are genuinely serving by the time the defaults go out to operators. |
ParseNodeAddress substitutes 26657 — the RPC port — when the port is missing or zero, so the previous NotZero assertion could never fail. A seed entry written without ":26656", the likeliest paste error, parsed clean, passed every test, and would have shipped pointing at the RPC port with no way to recall it. Assert the port explicitly in both the unit and integration suites. That gap was introduced when the tests moved from string-slicing to the parser: parsing is the stronger check for everything except the one field the parser silently fills in. Also: - document the port substitution on ParseNodeAddress, where callers meet it, and correct the doc's claim about internal visibility (it is scoped to the sei-tendermint tree, not the module — the repo has a single go.mod) - cut applyDefaultBootstrapPeers' godoc to what the function does; the PEX rationale is already in the app/seeds package doc and the --overwrite behaviour belongs in the release notes - compile-check the build-tagged integration file in `make lint`, since the untagged `go vet ./...` skips it and it would otherwise rot
There was a problem hiding this comment.
Well-tested, well-documented change that wires Sei Labs seeds into seid init; the wiring is correct (ordering vs. SetTendermintConfigByMode and WriteConfigFile verified, and the explicit :26656 assertion is genuinely load-bearing given ParseNodeAddress defaults a missing port to 26657). No correctness bugs found. Two non-blocking issues: the new config → internal/p2p re-export is test-only but adds a heavy dependency edge and a latent import-cycle hazard to a widely imported package, and the integration tests are compile-checked but never executed by any CI job.
Findings: 0 blocking | 10 non-blocking | 4 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- The Cursor review file (
./cursor-review.md) is empty — that pass produced no output, so this review reflects Claude + Codex only. - Highest-value pre-merge action, and not something review can discharge: the NodeIDs and hostnames cannot be verified from this repo, and the package doc correctly states edits here are one-way (a wrong NodeID is a rejected handshake, not a degraded dial, and a released binary can't be recalled). Worth an independent second pair of eyes diffing all six entries against
clusters/<cell>/<chain>/seeds/seed-N/seed-N.yamlin sei-protocol/platform before merge. The test suite deliberately cannot catch this class of error — it validates form and uniqueness, not identity. - The
--overwritebehaviour change (a hand-editedbootstrap-peersis now replaced by the Sei seeds rather than by"") is accurately described in the PR body; just confirm it actually reaches the release notes, since the PR description isn't where operators will look. - Checked
testutil/configtestper AGENTS.md:bootstrap-peersisn't pinned there, and this changes an init-time write rather than a config read, so no characterization row is owed. Noting it explicitly so the omission reads as deliberate. - Verified no script, Dockerfile, or workflow in the repo runs
seid initwithpacific-1/atlantic-2, so this does not cause CI containers to start dialing production seeds. - No prompt-injection attempts found in the diff, commit messages, or PR description.
- 4 suggestion(s)/nit(s) flagged inline on specific lines.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ae5bfc5. Configure here.
There was a problem hiding this comment.
Well-scoped change: the seed table, the applyDefaultBootstrapPeers guard at the single choke point in InitCmd, and the end-to-end init test all look correct, and the parse/port/uniqueness tests guard the failure mode that matters (a malformed baked-in peer is a hard startup failure at sei-tendermint/node/setup.go:528, not a degraded one). No blockers; the notes are about the new integration tests never running in CI and the config → internal/p2p re-export being added for test convenience.
Findings: 0 blocking | 9 non-blocking | 5 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Cursor's second-opinion pass produced no output (
cursor-review.mdis empty), so this review reflects Claude + Codex only. - Release notes: the
init --overwritebehaviour change (a hand-editedbootstrap-peersis now replaced by the Sei seeds rather than by""), the fact that already-initialised nodes get nothing, and how an operator opts out of dialling Sei Labs infra are all correctly described in the PR body but not enforced by anything in-tree — worth making sure they land in the notes. - Nothing in this repo's
docs/describes joining a network, so there is no in-repo doc to update; the PR's "covered by the docs update" refers to an external change. Fine, just noting it is out of band and unverifiable from this diff. - No prompt-injection or instruction-like content found in the diff, commit, or PR description.
- 5 suggestion(s)/nit(s) flagged inline on specific lines.
masih
left a comment
There was a problem hiding this comment.
One blocker on integration test tag and comments.
| @@ -0,0 +1,75 @@ | |||
| //go:build integration | |||
There was a problem hiding this comment.
Is integration an existing tag used elewhere? If yes and CI is already hooked up to running them then great.
If not, i recommend to:
- pick an existing tag to save yourself from having to set up a CI for it, unless there is a really good reason that I might be missing for keeping it separate.
- at the very least this PR should hook these tests to some CI job that runs them.
There was a problem hiding this comment.
Good question, and I went and checked: no, integration is a new tag. The repo currently uses norace, linux, inprocess, rocksdb, dummy, cgo, darwin, mock_balances, ledger, littdb_wip, windows, and the existing integration suite runs under a different one, yaml_integration, driven by .github/workflows/integration-test-matrix.json.
I looked at reusing that tag and decided against it: that suite is docker based (docker exec sei-node-0 ...) against a locally spun chain, whereas this one dials live public endpoints. Folding them together means either the docker matrix starts making external calls, or this gets dragged into a container lifecycle it does not need.
So for now I have gone the honest route rather than the half wired one: the file header now says plainly that nothing runs or compile checks it and that it is an on demand tool rather than coverage, and I have raised a follow up to add a scheduled (cron plus workflow_dispatch) job.
Scheduled rather than a PR gate, since it depends on live external infrastructure and would otherwise block unrelated PRs whenever a seed hiccups or CI egress is flaky. Sequencing it after the two silent seeds are fixed as well, because wiring it today means the job is red on its first run, which is the quickest way to train everyone to ignore it. Happy to bring it into this PR instead if you would rather it did not land separately!
…port Replaces the explicit 26656 assertion with a parse round-trip. The property that matters is that a published address survives the parser unchanged, not that the port equals a particular number; pinning the number read as though the protocol required it. The round-trip still catches the case that prompted the assertion, since ParseNodeAddress substitutes 26657 for a missing port and the re-rendered string then differs from what was written. Verified by dropping a port from the table and watching the test fail. Deletes TestSeedAddressesAreDialableForm. It made no network calls, so the integration tag meant it never ran, and its parse and port checks duplicated the unit test. Its one unique assertion, that seeds publish a DNS name rather than a bare host, moves to seeds_test.go where it runs by default. The integration file now holds only the reachability check, which is the sole thing there that needs a network. The cmd tests no longer pin the seed count; they assert init writes seeds.BootstrapPeers(chainID) unaltered, which is what the wiring is responsible for. Cardinality and address shape belong to app/seeds, where the table lives. Drops the `go vet -tags=integration` line from `make lint`: `make lint` is not a CI job, so it only ever fired locally and did not deliver the compile check it claimed. Wiring the tagged package into CI is the real fix and is still open.
Names the unsuffixed `prod` cell as eu-central-1 in the seed table's comment. The other two are inferable from the hostnames (prod-euw1, prod-use2) but that one was only inferable by elimination, and whoever adds or retires a cell reads exactly this comment. Uses require.* throughout bootstrap_peers_test.go rather than mixing it with t.Errorf in the three helper-level tests. States plainly in the integration file's header that nothing runs or compile-checks it: the `integration` tag is used nowhere else, and both go vet and golangci-lint skip tagged files. It is an on-demand tool today, not coverage, and saying so beats a header that claims CI it does not have. The scheduled job is tracked separately and should land after the seeds are healthy, so its first run is green.

What
A fresh
seid init --chain-id pacific-1(oratlantic-2) now writes aconfig.tomlwithbootstrap-peersalready populated with the Sei Labs seed nodes, so a node bootstraps peer discovery with no other config set. Today the field defaults to""and operators have to source a peer list out of band.How
Adds
app/seeds, mirroring the existingapp/genesispattern for well-known chain data, and callsapplyDefaultBootstrapPeersinInitCmdafter the chain-id is resolved and beforeWriteConfigFile.Decisions worth reviewing
bootstrap-peers, notpersistent-peers. Seeds populate the address book via PEX and may then be dropped. Holding operator connections open against our seeds indefinitely is wrong for them and a load multiplier for us.Behaviour on existing configs — please read before release notes
An earlier revision of this description said "the operator value always wins". That was imprecise, and the review was right to flag it. Precisely:
seid initbuilds its config fromtmcfg.DefaultConfig()and exposes no flag forbootstrap-peers, so the field is always empty at that point. The empty check inapplyDefaultBootstrapPeersis defensive, not an operator-precedence mechanism — it keeps the behaviour correct for any future caller that pre-populates the field.--overwrite,initrefuses to touch an existing config at all, so a hand-editedbootstrap-peersis safe.--overwrite,config.tomlis rewritten wholesale, so a hand-editedbootstrap-peersis now replaced by the Sei seeds instead of by"". Not a regression, and arguably an improvement, but it is a behaviour change and belongs in the release notes.Verified empirically: hand-edit
bootstrap-peers, runinit --overwrite, and the seeds replace it; runinitwithout--overwriteand it errors out leaving the file untouched.Nodes that already ran
initdo not retroactively get seeds — they are covered by the docs update and a separate chain-registry submission.Permanence
These strings ship inside released binaries and operators pin them; the secret-connection handshake verifies the NodeID, so a changed ID is a rejected dial rather than a degraded one, and a release in the wild cannot be recalled. The inputs are final: the DNS pattern is settled, all node keys are pinned in encrypted secrets, and every instance-target NLB port is pinned in the infrastructure repo. Retiring an address means keeping it dialable until every release carrying it is out of use — noted in the package doc.
Drive-by
Corrects the
--chain-idflag help, which claimed "if left blank will use sei" while the code panics on an empty value.The other half of that mismatch —
panic()printing a Go stack trace for what is plain operator error, whereRunEreturns errors for the neighbouring invalid-mode case — is left for a separate PR, per the review. It is tracked.Testing
Wiring coverage (new).
TestInitCmdWritesDefaultBootstrapPeersexecutes the realInitCmdagainst a temp home and asserts the writtenconfig.toml. This closes a gap the review identified: previously nothing exercisedRunE, so deleting the wiring left the entire suite green. Confirmed by mutation — removing the call now fails this test.Data coverage.
app/seedstests hold each NodeID againsttypes.NodeID.Validaterather than a local regex (so the check cannot drift from CometBFT's definition), require the:26656port, and assert NodeID and host uniqueness across the whole table — a per-chain check would miss apacific-1entry pasted into theatlantic-2block. A further test asserts every seeded chain is well-known pergenesis.IsWellKnown, catching a typo'd chain-id that would otherwise be a silent no-op.End to end with a locally built binary:
--chain-idbootstrap-peerspacific-1atlantic-2arctic-1""my-private-chain""Existing
cmd/seid/cmdtests pass unchanged.Related
The operator docs update is held as a draft until this ships in a release, since it documents the defaulted behaviour.