Skip to content

feat: chain fork support as genesis ceremony extension#46

Merged
bdchatham merged 10 commits intomainfrom
feat/chain-fork-v2
Apr 2, 2026
Merged

feat: chain fork support as genesis ceremony extension#46
bdchatham merged 10 commits intomainfrom
feat/chain-fork-v2

Conversation

@bdchatham
Copy link
Copy Markdown
Collaborator

Summary

Minimal fork support for SeiNodeGroup — fork an existing chain's state into a private network.

Design

Fork is an extension of the existing genesis ceremony, not a separate flow. The genesis planner selects the assembler task based on which condition is set:

  • GenesisCeremonyNeededassemble-and-upload-genesis (existing)
  • ForkGenesisCeremonyNeededassemble-fork-genesis (new sidecar task)

CRD

type ForkConfig struct {
    SourceChainID string `json:"sourceChainId"`
}

Added as Fork *ForkConfig on GenesisCeremonyConfig. That's it — the exported state location and assembled genesis output use the existing platform S3 conventions.

Example

genesis:
  chainId: private-fork-1
  fork:
    sourceChainId: pacific-1

Changes

  • ForkConfig type + Fork field on GenesisCeremonyConfig
  • GenesisCeremonyNeeded and ForkGenesisCeremonyNeeded conditions
  • detectGenesisCeremonyNeeded replaces separate detection methods
  • genesisGroupPlanner branches on condition to select assembler task
  • AssembleForkGenesisParams + Deserialize registration

Companion

Seictl assemble-fork-genesis handler (PR coming)

🤖 Generated with Claude Code

Minimal ForkConfig on GenesisCeremonyConfig with just sourceChainId.
The genesis planner selects the assembler task based on which
condition is set:
  - GenesisCeremonyNeeded → assemble-and-upload-genesis (existing)
  - ForkGenesisCeremonyNeeded → assemble-fork-genesis (new)

No new planner — the existing genesisGroupPlanner handles both.
detectGenesisCeremonyNeeded sets the appropriate condition based
on whether genesis.Fork is configured.

The assembled fork genesis writes to the same platform S3 path
convention as any genesis: {chainId}/{groupName}/genesis.json.
The exported state is expected at {sourceChainId}/exported-state.json
in the platform genesis bucket.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bdchatham and others added 2 commits April 1, 2026 16:57
…mplete

- Rename task type from assemble-fork-genesis to assemble-genesis-fork
  to match seictl handler registration
- Set GenesisCeremonyComplete condition in completePlan for genesis/fork
  plans to prevent infinite plan rebuild loop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends the fork flow with automated state export:
- ForkConfig gains SourceImage and ExportHeight fields
- reconcileSeiNodes creates a temporary exporter SeiNode (plain FullNode
  with BootstrapImage + TargetHeight, same config pattern as replayers)
- Exporter bootstraps through the standard SeiNode pipeline
- Group plan prepends: await-exporter-running → submit-export-state →
  teardown-exporter before assemble-genesis-fork

The exporter is excluded from IncumbentNodes (filtered by sei.io/role
label). seid export --height N reads committed state at exactly that
height, making the export deterministic regardless of node progress.

Also fixes SnapshotUploadTask field removal for seictl v0.0.27 compat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bdchatham added a commit to sei-protocol/seictl that referenced this pull request Apr 2, 2026
## Summary

New sidecar task `assemble-genesis-fork` that assembles genesis from an
existing chain's exported state.

### Flow
1. Download exported state from `{sourceChainId}/exported-state.json` in
platform genesis bucket
2. Rewrite chain_id, initial_height, genesis_time; clear validators
3. Strip old validator state (staking/slashing/distribution/evidence)
4. Download new validator gentxs, add missing accounts
5. Run collect-gentxs
6. Upload genesis.json + peers.json to `{newChainId}/`

### Design
Follows the same patterns as the existing `GenesisAssembler` — same S3
conventions, same collect-gentxs flow, same marker file idempotency. The
only difference is the starting point: exported state instead of fresh
genesis.

### Companion
Controller: sei-protocol/sei-k8s-controller#46

### Note
Controller PR uses task type `assemble-fork-genesis` — needs renaming to
`assemble-genesis-fork` to match this PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace string literals with sidecar.TaskTypeAssembleGenesisFork and
sidecar.TaskTypeExportState from the seictl client package.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bdchatham and others added 3 commits April 2, 2026 08:40
…rter to plan task

Address PR feedback:

- Replace 31-case switch statement in Deserialize with a map-based
  registry. New sidecarTask[T] generic helper creates deserializers
  for sidecar-backed tasks. Eliminates cyclomatic complexity issue.

- Move exporter SeiNode creation from reconcileSeiNodes into a
  create-exporter plan task. The group plan now owns the full
  exporter lifecycle: create → await → export → teardown.
  reconcileSeiNodes stays focused on the N validator nodes.

- Fix submit-export-state to use real sidecar client (sidecarClientForNode)
  instead of TODO placeholders.

- Remove unnecessary "sidecar task" comment in Deserialize.

- Improve completePlan comment explaining genesis completion detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The genesis ceremony completion check read group.Status.Deployment == nil
after the deployment finalization block had already set it to nil, causing
deployment plan completions to spuriously set GenesisCeremonyComplete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bump seictl to v0.0.29
- Fix awaitExporterRunning: NotFound returns Running (cache lag), not Complete
- Fix submitExportState: recompute sidecar taskID deterministically in
  Status() so it survives re-deserialization across reconcile boundaries
- Fix createExporter: delete Failed exporter so next reconcile can
  create a fresh one (prevents infinite plan-fail loop)
- Fix peer DNS: use {name}-0.{name}.{ns}.svc.cluster.local to match
  StatefulSet headless Service pod DNS
- Add nil guard in group planner for Fork spec
- Add 6-hour timeout on export-state via exporter CreationTimestamp
- Add fork plan test (7-task plan with correct types and ordering)
- Add fork task execution tests (create, await, submit, teardown)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bdchatham and others added 3 commits April 2, 2026 10:00
The create-exporter task has already completed by this point, so the
exporter should exist. NotFound means something is genuinely wrong,
not cache lag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bdchatham bdchatham marked this pull request as ready for review April 2, 2026 17:06
@bdchatham bdchatham merged commit 442f93b into main Apr 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant