Skip to content

DEM-771: self-healing validator seed on boot + ./run --reset#943

Merged
tcsenpai merged 5 commits into
stabilisationfrom
feat/dem-771-ensure-validator-seed
Jun 17, 2026
Merged

DEM-771: self-healing validator seed on boot + ./run --reset#943
tcsenpai merged 5 commits into
stabilisationfrom
feat/dem-771-ensure-validator-seed

Conversation

@Shitikyan

@Shitikyan Shitikyan commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Problem

findGenesisBlock short-circuits past generateGenesisBlock once block 0 is in the DB, and the validator seed lives exclusively inside generateGenesisBlock. If the validators table is ever emptied while block 0 is present, the chain silently stalls — getShard returns 0 peers, no quorum is reached, /health still reports OK, but confirm() returns 401. The operator only finds out by manually calling getValidators and seeing zero. We've hit this twice on devnet (DEM-728, DEM-770).

There is also a pre-existing bug in the legacy "no snapshot" path of chainGenesis.ts: genesis-baked validators are intentionally NOT seeded (comment in the code says so). Dev/empty-chain boot has always produced a dead consensus after the first run.

Change

src/libs/blockchain/routines/ensureValidatorSeed.ts (new) — runs after findGenesisBlock. Conservative reconciler:

  • validators.count > 0 → no-op (never overwrite dynamically-staked rows, never resurrect unstaked addresses)
  • validators.count === 0 + genesis has validators[] → UPSERT (ON CONFLICT DO NOTHING) in a single transaction
  • validators.count === 0 + genesis empty → throw ConsensusInvariantError with the exact remediation command, so the operator gets a fail-loud signal instead of silent quorum death

Wired into src/index.ts immediately after findGenesisBlock / loadGenesisIdentities.

Operator UX:

  • ./run --docker --reset — wipe + reboot mainnet/testnet stack
  • ./run --docker --devnet --reset — same, targeting the devnet stack

Underneath: extends scripts/wipe-and-reboot.sh with a --devnet flag (uses demos_*_devnet volume names and the demos-devnet compose project). ./run --reset without --docker is rejected with a friendly hint.

Verification

Locally verified end-to-end against docker-compose.devnet.yml:

Scenario Expected Result
Fresh boot, empty DB [BOOT] re-seeding 4 entries log; validators=4 PASS
Restart with healthy state No re-seed log; validators=4 PASS
TRUNCATE validators + restart [BOOT] re-seeding 4 entries; validators=4 PASS
./run --docker --devnet --reset (wipe + boot) Re-seed on new boot; validators=4 PASS

Unit tests: 11/11 pass under bun test tests/blockchain/ensureValidatorSeed.test.ts. Project tsc --noEmit reports zero new errors in the touched files.

Safety notes

  • count > 0 gate plus ON CONFLICT DO NOTHING is defence-in-depth: a dynamically-staked validator can never be overwritten by a genesis-baked row, even if the gate ever stops gating correctly.
  • The original seedValidators (genesis-init path) and its validators table empty preflight are not modified — this PR adds a separate routine alongside, leaving the genesis-init path bit-for-bit identical.
  • No P2P validator-set sync in this PR. That is a separate architectural change and does not block the current operator pain.

Design memo

test-reports/dem-771-devnet-bootstrap-design.md carries the full root-cause analysis, what we explicitly do not do, and the rollout plan.

Test plan

  • CI green
  • Manual: deploy to dev VPS node3 (block 0, 0 validators), restart, confirm self-heal log + validators populated
  • Manual: ./run --docker --devnet --reset --yes on a dev box, confirm wipe + clean reboot

Summary by CodeRabbit

  • New Features
    • Added --reset mode to the launcher with --docker-gated behavior and automatic confirmation.
    • Added --devnet flag support for stack-aware wipe-and-reboot operations.
    • Added boot-time validator-table reconciliation to reseed from available snapshots (or genesis when appropriate) when the table is empty.
  • Documentation
    • Added design/spec documentation for deterministic validator recovery and the operator workflow.
  • Tests
    • Expanded Jest coverage for reseeding precedence, validation, and “refuse-to-boot” failure scenarios.

The chain's only validator seed lives inside `generateGenesisBlock`,
which `findGenesisBlock` short-circuits past once block 0 is present.
If the `validators` table is ever emptied while block 0 exists, the
chain silently stalls: `getShard` returns 0 peers, no quorum is
reached, `/health` still reports OK. The operator only learns the
chain is dead by manually calling `getValidators` and seeing zero.

`ensureValidatorSeed` runs after `findGenesisBlock` and reconciles
the validators table: no-op when it has any rows (so dynamically
staked entries are never overwritten, and legitimately unstaked
addresses are never resurrected), and an UPSERT
(ON CONFLICT DO NOTHING) re-seed from `data/genesis.json` when it
is empty. When the table is empty and there is nothing to seed, the
boot throws with the exact remediation command.

Also fixes the legacy "no-snapshot" path in `chainGenesis.ts` that
intentionally skipped genesis-baked validator seeding — dev/empty
chain boot was always producing a dead consensus after first run.

Operator UX:
  ./run --docker --reset            wipe + reboot the mainnet/testnet stack
  ./run --docker --devnet --reset   wipe + reboot the devnet stack

Locally verified against `docker-compose.devnet.yml`:
  1. Fresh boot — re-seed log + 4 validators in DB
  2. Healthy restart — no-op, no re-seed log
  3. TRUNCATE validators + restart — self-heals to 4 rows
  4. `./run --docker --devnet --reset --yes` — wipe + boot reseeds
  11/11 unit tests pass under `bun test`.

Design memo: test-reports/dem-771-devnet-bootstrap-design.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds an idempotent ensureValidatorSeed boot-time routine that re-populates the validators table from data/genesis.json when empty, prioritizing snapshot-derived validators and falling back to genesis only at block 0, integrates it into node startup, extends scripts/wipe-and-reboot.sh with --devnet stack-aware volume and compose project handling, and exposes the reset flow via a new --reset flag in the run launcher script.

Changes

DEM-771: Validator Re-seed and Reset Tooling

Layer / File(s) Summary
ensureValidatorSeed contracts, types, and validation schema
src/libs/blockchain/routines/ensureValidatorSeed.ts
Defines GenesisValidatorSeed interface, EnsureValidatorSeedResult with source field tracking recovery source, ConsensusInvariantError, and validateSeedEntry function enforcing strict schema for genesis seeds: address hex format, non-empty status string, positive BigInt staked_amount, optional non-empty connection_url, and non-negative integer first_seen/valid_at.
ensureValidatorSeed helper functions
src/libs/blockchain/routines/ensureValidatorSeed.ts
Implements countValidators (row count query), extractGenesisSeed (defensive extraction from genesisData), genesisToRow and snapshotToRow (mappers converting seed formats to database row shape), and upsertValidators (batched TypeORM orIgnore inserts with batch size 100, nulling unstake timestamp fields).
Snapshot validator loading
src/libs/blockchain/routines/ensureValidatorSeed.ts
Implements loadSnapshotValidators to load snapshot metadata, verify presence of validators.jsonl, stream snapshot validator rows, and return empty array on load/stream errors or missing manifest.
ensureValidatorSeed main orchestration
src/libs/blockchain/routines/ensureValidatorSeed.ts
Implements ensureValidatorSeed orchestrating recovery: early-exit on non-empty table, load lastBlock, attempt snapshot-based seeding (transactional upsert with post-check), enforce block-0-only rule for genesis fallback (throw on advanced chains), extract/validate genesis seeds, insert transactionally, and return source. Includes assertSeededAndReturn helper to re-count validators post-insert and throw ConsensusInvariantError if table remains empty.
Boot-time validator reconciliation
src/index.ts, src/libs/blockchain/routines/ensureValidatorSeed.ts
Adds import of ensureValidatorSeed and integrates into preMainLoop: reads and parses data/genesis.json, tolerates read/parse failures by logging and passing null seed data, then unconditionally awaits ensureValidatorSeed(genesisData) after genesis lookup and identity loading.
ensureValidatorSeed test setup and fixtures
tests/blockchain/ensureValidatorSeed.test.ts
Establishes Jest mocks for logger, datasource transactions and repository count, chain head, and snapshot loader/stream. Defines helper factories for snapshot and genesis seed test input generation with override support.
ensureValidatorSeed test cases: core flows and failure modes
tests/blockchain/ensureValidatorSeed.test.ts
Tests no-op when validators table non-empty, snapshot-first reseeding (including advanced block scenarios), refusal to boot at block > 0 without snapshot, genesis fallback at block 0, invariant failures when no data available or post-insert count is zero, snapshot load/stream errors treated as unavailable, and snapshot manifest without validators.jsonl.
ensureValidatorSeed test cases: per-field genesis validation
tests/blockchain/ensureValidatorSeed.test.ts
Tests validation of address format, validators array entry types (null/primitives rejected), status string (empty/non-string rejected), staked_amount as positive BigInt, valid_at as non-negative integer, and connection_url as null or non-empty string. Includes positive test confirming successful reseeding with valid connection_url.
wipe-and-reboot.sh devnet stack configuration and usage
scripts/wipe-and-reboot.sh
Adds --devnet argument parsing and derives stack-specific configuration (STACK_LABEL, DOCKER_RUN_ARGS, VOLUMES list, VOLUME_RE regex, COMPOSE_PROJECT name) branching on devnet vs mainnet/testnet. Updates header comments and help text to document the new flag and valid combinations.
wipe-and-reboot.sh stack-aware execution phases
scripts/wipe-and-reboot.sh
Uses stack-specific configuration throughout: stop phase runs ./scripts/docker-run with devnet when applicable, volume removal driven by VOLUMES array, leftover detection uses stack-specific VOLUME_RE with sibling filtering, rebuild/boot pass ./scripts/docker-run args, and log tailing uses docker compose -p $COMPOSE_PROJECT when set.
run launcher --reset entry point
run
Scans CLI arguments for --reset flag; validates --docker is first argument when --reset is present (otherwise exits status 2 with usage guidance). Strips --docker, removes --reset, injects --yes (unless already present), and execs scripts/wipe-and-reboot.sh with constructed args, bypassing normal docker-run/run branching.
DEM-771 design document
test-reports/dem-771-devnet-bootstrap-design.md
New markdown documenting empty-validators restart failure mode, recovery ordering (snapshot preferred, genesis only at block 0), integrated fail-loud self-check, CLI/script rollout changes (--devnet flag, --reset exposure), operator workflow, and multi-PR rollout plan centered on PR #943.

Sequence Diagram(s)

sequenceDiagram
  participant preMainLoop
  participant filesystem
  participant ensureValidatorSeed
  participant database
  participant snapshot
  preMainLoop->>filesystem: readFileSync data/genesis.json
  filesystem-->>preMainLoop: raw JSON or error
  preMainLoop->>ensureValidatorSeed: ensureValidatorSeed(genesisData)
  ensureValidatorSeed->>database: countValidators()
  alt table non-empty
    ensureValidatorSeed-->>preMainLoop: {reseeded: false, source: null}
  else table empty
    ensureValidatorSeed->>database: getLastBlockNumber()
    ensureValidatorSeed->>snapshot: loadSnapshotValidators()
    alt snapshot has validators
      snapshot-->>ensureValidatorSeed: stream rows
      ensureValidatorSeed->>database: upsertValidators(transaction, orIgnore)
      ensureValidatorSeed->>database: countValidators() post-insert
      alt count > 0
        ensureValidatorSeed-->>preMainLoop: {reseeded: true, source: snapshot, count: N}
      else count == 0
        ensureValidatorSeed-->>preMainLoop: throw ConsensusInvariantError
      end
    else snapshot empty or unavailable
      alt lastBlock > 0
        ensureValidatorSeed-->>preMainLoop: throw ConsensusInvariantError
      else lastBlock == 0
        ensureValidatorSeed->>ensureValidatorSeed: extractGenesisSeed + validateSeedEntry
        ensureValidatorSeed->>database: upsertValidators(transaction, orIgnore)
        ensureValidatorSeed->>database: countValidators() post-insert
        alt count > 0
          ensureValidatorSeed-->>preMainLoop: {reseeded: true, source: genesis, count: N}
        else count == 0
          ensureValidatorSeed-->>preMainLoop: throw ConsensusInvariantError
        end
      end
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • kynesyslabs/node#868: Establishes the initial scripts/wipe-and-reboot.sh helper that this PR extends with --devnet stack configuration and integration into the run --reset flow.

Poem

🐇 Hippity hop, the validators were gone,
The table sat empty from dusk until dawn.
Now genesis seeds us when the rows lie bare,
And --devnet and --reset float fresh through the air!
The stack knows its name, the volumes their fate—
A bunny reboots and the chain starts up great. 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: validator seed reconciliation on boot (DEM-771) and the new ./run --reset command for resetting the stack.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dem-771-ensure-validator-seed

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a silent consensus failure where emptying the validators table while block 0 is present leaves the node running but unable to reach quorum. It adds a boot-time reconciliation routine (ensureValidatorSeed) that re-seeds from snapshot or genesis on an empty table, and extends the ./run / wipe-and-reboot.sh operator tooling with --reset and --devnet flags.

  • ensureValidatorSeed.ts — conservative reconciler: no-op when count > 0, snapshot-first recovery, genesis fallback only at block 0, ConsensusInvariantError with remediation command for unrecoverable states; 14-case unit test suite covers all branches including stream-failure tolerance and field-level validation.
  • run / wipe-and-reboot.sh--reset routes to wipe-and-reboot.sh with auto---yes; --devnet flag targets the correct compose project and namespaced volumes, with a double-filter to prevent mainnet regex matching devnet siblings.

Confidence Score: 5/5

Safe to merge; the reconciliation routine is conservative (count > 0 is a hard no-op, ON CONFLICT DO NOTHING is defence-in-depth) and the three previously flagged issues (silent catch, stake-guard split, null-element handling) are all addressed in this version.

The core logic path — check count, try snapshot, try genesis at block 0, throw loudly otherwise — is straightforward and well-covered by 14 unit tests. The shell changes are surgical and the argument-routing logic handles all documented flag combinations correctly. The one finding (tail log filter missing BOOT) is a documentation/observability gap, not a correctness issue.

scripts/wipe-and-reboot.sh — the tail filter and expected-output banner don't yet reflect the new [BOOT] log lines, so operators verifying a reset won't see re-seed confirmation in the live tail.

Important Files Changed

Filename Overview
src/libs/blockchain/routines/ensureValidatorSeed.ts New boot-time validator reconciliation routine; conservative count > 0 gate, snapshot-first recovery, genesis-only-at-block-0 fallback, and fail-loud ConsensusInvariantError for unrecoverable states — logic is sound, validation is thorough, ON CONFLICT DO NOTHING provides defence-in-depth
src/index.ts Wires ensureValidatorSeed after findGenesisBlock; IO/parse errors from genesis.json are now logged before being swallowed, addressing the previously flagged silent-catch issue
scripts/wipe-and-reboot.sh Adds --devnet flag with correct stack-specific volume names and COMPOSE_PROJECT; tail log filter does not include BOOT, so the new [BOOT] re-seed confirmation lines are invisible to operators using this script
run Adds --reset dispatch that gates on --docker being first arg, auto-injects --yes, strips --reset and duplicate --yes/-y flags before forwarding to wipe-and-reboot.sh; edge cases (--no-rebuild, --devnet) handled correctly
tests/blockchain/ensureValidatorSeed.test.ts Comprehensive 14-case Jest suite covering no-op, snapshot preference, genesis fallback, block-height guard, stream failure tolerance, and all validateSeedEntry rejection paths

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Boot: preMainLoop]) --> B[findGenesisBlock]
    B --> C[loadGenesisIdentities]
    C --> D[ensureValidatorSeed]

    D --> E{validators.count > 0?}
    E -->|yes| F[no-op ✓]

    E -->|no| G[loadSnapshotValidators]
    G -->|rows > 0| H[UPSERT from snapshot\nON CONFLICT DO NOTHING]
    H --> I[assertSeededAndReturn\nsource=snapshot]

    G -->|rows = 0| J{lastBlock > 0?}
    J -->|yes| K[❌ ConsensusInvariantError\nrestore snapshot or --reset]

    J -->|no block=0| L[extractGenesisSeed]
    L -->|seed empty| M[❌ ConsensusInvariantError\nfix genesis.json]
    L -->|seed present| N[validateSeedEntry × N]
    N --> O[UPSERT from genesis\nON CONFLICT DO NOTHING]
    O --> P[assertSeededAndReturn\nsource=genesis]

    I --> Q([Boot continues])
    P --> Q
    F --> Q
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A([Boot: preMainLoop]) --> B[findGenesisBlock]
    B --> C[loadGenesisIdentities]
    C --> D[ensureValidatorSeed]

    D --> E{validators.count > 0?}
    E -->|yes| F[no-op ✓]

    E -->|no| G[loadSnapshotValidators]
    G -->|rows > 0| H[UPSERT from snapshot\nON CONFLICT DO NOTHING]
    H --> I[assertSeededAndReturn\nsource=snapshot]

    G -->|rows = 0| J{lastBlock > 0?}
    J -->|yes| K[❌ ConsensusInvariantError\nrestore snapshot or --reset]

    J -->|no block=0| L[extractGenesisSeed]
    L -->|seed empty| M[❌ ConsensusInvariantError\nfix genesis.json]
    L -->|seed present| N[validateSeedEntry × N]
    N --> O[UPSERT from genesis\nON CONFLICT DO NOTHING]
    O --> P[assertSeededAndReturn\nsource=genesis]

    I --> Q([Boot continues])
    P --> Q
    F --> Q
Loading

Reviews (6): Last reviewed commit: "feat(seed): prefer snapshot over genesis..." | Re-trigger Greptile

Comment thread src/index.ts
Comment thread src/libs/blockchain/routines/ensureValidatorSeed.ts
Typing --reset is already an explicit destructive opt-in; requiring
the operator to ALSO type --yes (or interactively confirm by typing
the word "wipe") is just friction. The interactive prompt in
scripts/wipe-and-reboot.sh stays for direct invocations of that
script — only the curated ./run entry point auto-yeses.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/blockchain/ensureValidatorSeed.test.ts (1)

145-194: 💤 Low value

Consider adding a test for invalid status field.

The test suite covers most validation paths but doesn't explicitly test the status validation (empty string or non-string type). While the current tests provide good coverage, adding a test for status validation would ensure complete field coverage.

💡 Suggested additional test case
it("rejects empty status", async () => {
    repoCount.mockResolvedValue(0)

    await expect(
        ensureValidatorSeed({
            validators: [makeSeed({ status: "" })],
        }),
    ).rejects.toThrow(/status/)
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/blockchain/ensureValidatorSeed.test.ts` around lines 145 - 194, Add a
test case for status field validation in the ensureValidatorSeed test suite.
Following the pattern of the existing tests (like "rejects zero stake" and
"rejects negative valid_at"), create a new test called "rejects empty status"
that calls ensureValidatorSeed with a validator seed where the status field is
set to an empty string, and verify that it rejects with an error matching
/status/. This ensures complete field validation coverage for the
ensureValidatorSeed function.
src/libs/blockchain/routines/ensureValidatorSeed.ts (1)

171-199: TypeORM orIgnore() is officially supported—no API changes needed.

The orIgnore() method is documented in TypeORM 0.3.17 and correctly translates to ON CONFLICT DO NOTHING for PostgreSQL/SQLite. Unit tests verify the method is called. Consider adding an integration test that runs against actual SQLite/PostgreSQL instances to ensure the generated SQL executes correctly with real database constraints, but the current implementation is correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libs/blockchain/routines/ensureValidatorSeed.ts` around lines 171 - 199,
The current implementation of upsertValidators in ensureValidatorSeed.ts
correctly uses orIgnore() which is officially supported in TypeORM, but lacks
integration test coverage to verify the generated SQL executes correctly against
real database instances. Add integration tests that run the upsertValidators
function against actual SQLite and PostgreSQL databases to ensure the ON
CONFLICT DO NOTHING behavior works as expected and properly handles real
database constraints when duplicate address values are encountered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test-reports/dem-771-devnet-bootstrap-design.md`:
- Line 5: The design document contains four interconnected discrepancies with
the actual unified implementation and requires updates across multiple sections.
At test-reports/dem-771-devnet-bootstrap-design.md line 5 (anchor), change the
status field from "Proposal (no code merged)" to "Implemented" to reflect that
the code is already merged. At lines 58-72 (sibling), update the pseudo-code
section 4.1 to show that ensureValidatorSeed throws ConsensusInvariantError
directly when the seed is empty rather than returning silently; include the
actual error message thrown. At lines 52-101 (sibling), revise the architecture
description for sections 4.1-4.2 to clarify that 4.2 is no longer a separate
post-call check in src/index.ts but is now conceptually bundled as an integrated
self-check within the ensureValidatorSeed function itself; remove references to
two distinct steps. At lines 145-156 (sibling), update the rollout plan to
reflect the unified implementation: replace the three-PR proposal with a two-PR
plan where PR-1 includes ensureValidatorSeed with integrated self-check plus
tests, and PR-2 includes reset tooling and the --reset entry point; remove the
separate PR for section 4.2 throwing.

---

Nitpick comments:
In `@src/libs/blockchain/routines/ensureValidatorSeed.ts`:
- Around line 171-199: The current implementation of upsertValidators in
ensureValidatorSeed.ts correctly uses orIgnore() which is officially supported
in TypeORM, but lacks integration test coverage to verify the generated SQL
executes correctly against real database instances. Add integration tests that
run the upsertValidators function against actual SQLite and PostgreSQL databases
to ensure the ON CONFLICT DO NOTHING behavior works as expected and properly
handles real database constraints when duplicate address values are encountered.

In `@tests/blockchain/ensureValidatorSeed.test.ts`:
- Around line 145-194: Add a test case for status field validation in the
ensureValidatorSeed test suite. Following the pattern of the existing tests
(like "rejects zero stake" and "rejects negative valid_at"), create a new test
called "rejects empty status" that calls ensureValidatorSeed with a validator
seed where the status field is set to an empty string, and verify that it
rejects with an error matching /status/. This ensures complete field validation
coverage for the ensureValidatorSeed function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da8fdb51-0465-4b33-9342-fd36d3e875b1

📥 Commits

Reviewing files that changed from the base of the PR and between 9094001 and fafa759.

📒 Files selected for processing (6)
  • run
  • scripts/wipe-and-reboot.sh
  • src/index.ts
  • src/libs/blockchain/routines/ensureValidatorSeed.ts
  • test-reports/dem-771-devnet-bootstrap-design.md
  • tests/blockchain/ensureValidatorSeed.test.ts

Comment thread test-reports/dem-771-devnet-bootstrap-design.md Outdated
Comment thread src/libs/blockchain/routines/ensureValidatorSeed.ts
Shitikyan and others added 2 commits June 16, 2026 20:10
- src/index.ts: log the actual fs.readFileSync/JSON.parse error
  before passing null to ensureValidatorSeed, so the operator sees
  the real cause rather than a misleading "carries no validator set"
  message when genesis.json was deleted post-findGenesisBlock.
- ensureValidatorSeed.ts: split the BigInt parse from the > 0 check
  so the operator gets a distinct, actionable message for each
  failure mode (matches seedValidators.ts::validateSeed exactly).
- tests: add empty-status + non-string-status cases; tighten the
  zero/non-bigint stake assertions to the new precise messages.
- design memo: status updated to "Implemented in PR #943", §4.1 +
  §4.2 unified into the single-routine shape, rollout reduced to
  one PR, follow-ups split into their own tickets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Greptile P1: a stray `null` (or any non-object) in
data/genesis.json's validators[] would leak a native
`Cannot read properties of null` TypeError instead of the
ConsensusInvariantError every other failure path goes through.
Add an object guard as the first check in validateSeedEntry +
two new tests covering null and primitive elements.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Shitikyan

Copy link
Copy Markdown
Contributor Author

Bot review threads on this PR are stale relative to the latest commits — all findings have been addressed in code. Re-triggering the bots so the resolved threads reflect the actual state.

@coderabbitai review
@greptileai review

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

@Shitikyan: Sure! I'll kick off a fresh review of the latest commits on this PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Auto-reseeding from data/genesis.json on a chain that has advanced
past block 0 is unsafe: it would resurrect founders that legitimately
unstaked AND silently lose every validator that staked dynamically
after genesis. Mainnet operators would need a full wipe to recover
from any wipe of the validators table, which is unacceptable.

Recovery order is now:

  1. data/snapshot/validators.jsonl when present (any block height) —
     snapshots are the authoritative state-at-snapshot-time and
     include every dynamic stake event up to that point.
  2. data/genesis.json::validators[] but ONLY when lastBlock === 0
     (cold-start dev / fresh chain bootstrap).
  3. Refuse to boot with a remediation message otherwise — the
     operator must restore a snapshot from a healthy peer or accept
     a full reset.

Snapshot rows go straight through to UPSERT without re-validation —
the snapshot loader already enforces the wider schema and
re-validating with the genesis-strict schema would reject legitimate
nullable-column rows.

20/20 unit tests pass under `bun test`, including new coverage for:
- snapshot preference at advanced block heights
- refuse-to-boot at block > 0 without snapshot
- snapshot load / stream failures gracefully fall through

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/libs/blockchain/routines/ensureValidatorSeed.ts (1)

146-146: 💤 Low value

Wrap callback to prevent accidental parameter leakage.

forEach passes three arguments (element, index, array) to its callback. If validateSeedEntry is later modified to accept a third parameter, it would silently receive the entire array.

Suggested fix
-    seed.forEach(validateSeedEntry)
+    seed.forEach((entry, i) => validateSeedEntry(entry, i))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/libs/blockchain/routines/ensureValidatorSeed.ts` at line 146, The forEach
callback at the seed.forEach(validateSeedEntry) call passes the function
directly, which means if validateSeedEntry is later modified to accept a third
parameter, it would accidentally receive the entire array as that parameter due
to forEach passing (element, index, array) to callbacks. Wrap validateSeedEntry
in an arrow function like (entry) => validateSeedEntry(entry) to explicitly
control which parameters are passed and prevent silent bugs from future
modifications to the validateSeedEntry function signature.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/libs/blockchain/routines/ensureValidatorSeed.ts`:
- Line 146: The forEach callback at the seed.forEach(validateSeedEntry) call
passes the function directly, which means if validateSeedEntry is later modified
to accept a third parameter, it would accidentally receive the entire array as
that parameter due to forEach passing (element, index, array) to callbacks. Wrap
validateSeedEntry in an arrow function like (entry) => validateSeedEntry(entry)
to explicitly control which parameters are passed and prevent silent bugs from
future modifications to the validateSeedEntry function signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d645c991-a1a8-47fa-8940-dd777ecdf2e2

📥 Commits

Reviewing files that changed from the base of the PR and between 2f911ff and de86c22.

📒 Files selected for processing (3)
  • src/libs/blockchain/routines/ensureValidatorSeed.ts
  • test-reports/dem-771-devnet-bootstrap-design.md
  • tests/blockchain/ensureValidatorSeed.test.ts
✅ Files skipped from review due to trivial changes (1)
  • test-reports/dem-771-devnet-bootstrap-design.md

@tcsenpai
tcsenpai merged commit 3999d52 into stabilisation Jun 17, 2026
4 of 5 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.

2 participants