Skip to content

feat(seidb): composite + replay digest modes and migration-boundary omission for evm-logical-digest#3711

Open
blindchaser wants to merge 7 commits into
mainfrom
yiren/digest-omit-migration-boundary
Open

feat(seidb): composite + replay digest modes and migration-boundary omission for evm-logical-digest#3711
blindchaser wants to merge 7 commits into
mainfrom
yiren/digest-omit-migration-boundary

Conversation

@blindchaser

Copy link
Copy Markdown
Contributor

Summary

Ports the evolved seidb evm-logical-digest tooling to main so mid-migration nodes can be compared correctly:

  • --backend composite (--flatkv-dir / --memiavl-dir): digests the full mid-migration EVM logical view = flatkv rows ∪ memiavl rows not yet migrated past the boundary. Lets an in-progress migrate_evm node be compared against a memiavl-only truth node (the flatkv-only backend alone would miss EVM keys still in memiavl).
  • --memiavl-open-mode snapshot|replay: snapshot (default, fast — sequential scan of the completed snapshot kvs file; requires an on-disk snapshot at --height) vs replay (opens a read-only DB, replays the changelog to --height, then walks the mmap tree; ~10x slower — for heights whose snapshot rewrite lags the tip). Doc/flag help spell out the trade-off and when to prefer each.
  • migration-boundary marker omission: the FlatKV-only migration/migration-boundary cursor (present only while a migration is in flight) is now XORed out of the legacy bucket for the final comparison, mirroring the existing migration/migration-version handling. Without this, an in-progress composite node shows a spurious +1 legacy entry vs a completed / memiavl-only node. Extracted legacyForCompare() and added a regression test covering clean / completed / in-progress shapes.

Context

Validated against the live pacific-1 flatkv fleet: at height 218270000, account/code/storage digests matched byte-for-byte across a memiavl-only node, a completed composite node, and two in-progress composite nodes; the only difference was the unfiltered boundary row, which this change removes.

Test plan

  • go test ./sei-db/tools/cmd/seidb/operations/
  • go build ./sei-db/tools/cmd/seidb/...

Made with Cursor

…mission for evm-logical-digest

Port the evolved evm-logical-digest tooling to main:

- `--backend composite` (with `--flatkv-dir` / `--memiavl-dir`): digests the
  full mid-migration EVM logical view as the union of flatkv rows plus the
  memiavl rows not yet migrated past the boundary, so an in-progress node can be
  compared against a memiavl-only truth node.
- `--memiavl-open-mode snapshot|replay`: snapshot (default, fast: sequential
  scan of the completed snapshot kvs file) vs replay (opens a read-only DB,
  replays the changelog to --height, walks the mmap tree) for heights with no
  on-disk snapshot. Documented the speed trade-off and when to prefer each.
- Omit the FlatKV-only `migration/migration-boundary` cursor from the legacy
  bucket comparison, mirroring the existing migration-version handling, so an
  in-progress (composite) node compares apples-to-apples against completed /
  memiavl-only nodes. Extracted legacyForCompare() and added a regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJul 9, 2026, 2:51 PM

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 10.24845% with 289 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.35%. Comparing base (e7c5eca) to head (25c153a).

Files with missing lines Patch % Lines
...b/tools/cmd/seidb/operations/evm_logical_digest.go 10.24% 284 Missing and 5 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3711      +/-   ##
==========================================
- Coverage   59.88%   58.35%   -1.53%     
==========================================
  Files        2283     2187      -96     
  Lines      189429   178165   -11264     
==========================================
- Hits       113431   103965    -9466     
+ Misses      65906    64932     -974     
+ Partials    10092     9268     -824     
Flag Coverage Δ
sei-chain-pr 29.60% <10.24%> (?)
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...b/tools/cmd/seidb/operations/evm_logical_digest.go 22.91% <10.24%> (-1.44%) ⬇️

... and 213 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b7cea4f. Configure here.

Comment thread sei-db/tools/cmd/seidb/operations/evm_logical_digest.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7cea4f9c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 665 to 666
if err := d.consume(k, iter.Value()); err != nil {
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter FlatKV scans back to EVM rows

When FlatKV contains any non-EVM module rows, this loop now feeds every RawGlobalIterator row into d.consume; RawGlobalIterator walks all data DB keys, while the memiavl side only scans the evm tree. Those bank/staking/etc. rows are folded into the legacy bucket, so FINAL_DIGEST can diverge for identical EVM state; the removed shouldIncludeFlatKVEVMLogicalDigestKey filter was the guard here, and the new composite FlatKV scan follows the same unfiltered pattern.

Useful? React with 👍 / 👎.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR adds composite and replay digest modes plus migration-boundary marker omission to the seidb evm-logical-digest diagnostic tool. The changes are well-documented and covered by regression tests; no blocking correctness issues found. One asymmetry (raised by Codex) is worth noting but is non-blocking for the tool's intended migrate_evm use.

Findings: 0 blocking | 3 non-blocking | 0 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Legacy-bucket cross-backend asymmetry (Codex, re-scoped): removing shouldIncludeFlatKVEVMLogicalDigestKey makes digestFlatKV/inspectFlatKV consume ALL FlatKV rows, and FlatKV's legacyDB routes non-EVM modules (bank/staking/etc.) per CommitStore.routePhysicalKey. The memiavl side still scans only the EVM tree, so on a node where non-EVM modules have been migrated into FlatKV the legacy bucket digest/count would be polluted and no longer comparable to memiavl (the marker-adjustment lines become meaningless). This does NOT affect the primary account/code/storage comparison, and in the migrate_evm deployment this PR targets FlatKV holds only EVM data (consistent with the PR's byte-for-byte validation), so I disagree with Codex's blocker framing for a diagnostic tool. Suggest either re-adding an EVM-scope guard to the legacy bucket or documenting that the legacy comparison is only valid when FlatKV contains EVM state exclusively.
  • Test coverage: the deleted TestShouldIncludeFlatKVEVMLogicalDigestKey is not replaced by any test exercising digestFlatKV's handling of non-EVM/legacy rows, so the behavior change above is untested.
  • Second-opinion passes: cursor-review.md is empty (Cursor produced no output); REVIEW_GUIDELINES.md is empty (no repo-specific guidelines applied).

blindchaser and others added 2 commits July 6, 2026 23:57
Collapse the snapshot vs replay and semantic vs translator memiavl digest
duplication behind a shared evmLeafSource abstraction (one snapshot kvs-file
scanner + one replay-tree scanner) and two shared cores
(runMemiavlSemanticDigest / runMemiavlTranslatorDigest). The inspect memiavl
paths and the composite unmigrated-tail consume are folded onto the same
scanners. Behavior is unchanged except the snapshot translator progress log now
carries `mode=translator` for consistency with the other modes; FINAL_DIGEST /
bucket output is identical. File shrinks ~190 lines.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restore the EVM-module ingestion filter on the FlatKV scan paths (digestFlatKV,
consumeCompositeFlatKV, inspectFlatKV). FlatKV's RawGlobalIterator yields every
module's rows and routes non-EVM modules into the legacy bucket, while a memiavl
node walks the EVM tree alone; without the filter the legacy bucket and
FINAL_DIGEST diverge from a memiavl-only node once FlatKV holds non-EVM Cosmos
state. The FlatKV-only migration markers are still let through and XORed out in
legacyForCompare. Addresses Cursor Bugbot review.

Co-authored-by: Cursor <cursoragent@cursor.com>

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A well-scoped enhancement to the seidb evm-logical-digest diagnostic tool (composite + replay modes, migration-boundary marker omission) that refactors the memiavl leaf-scanning into a shared evmLeafSource abstraction and adds regression tests. No correctness or security issues found; one non-blocking usability limitation carried over from the Codex pass.

Findings: 0 blocking | 5 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Composite mode requires a valid memiavl snapshot/replay even for a completed migration where no memiavl rows are consumed (see inline comment on line 451) — completed composite nodes should ideally be digestible from FlatKV alone. Reported by Codex; non-blocking for a diagnostic tool.
  • The Cursor second-opinion review file (cursor-review.md) was empty, so no Cursor findings were incorporated.
  • The Codex pass noted it could not run the tests locally (sandbox lacked the Go 1.25.6 toolchain; local Go was 1.24.13), so the added tests were not executed by that reviewer.
  • Consider a brief note in the flag help / docs that --backend composite in snapshot mode fails fast if snapshot-<height>/evm is absent even when the migration is already complete, so operators know to pick a height with an on-disk snapshot or use replay mode.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

var memReplayDB *memiavl.DB
var memEvmSnapshotDir string
var memVersion int64
switch memiavlOpenMode {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This switch opens/validates the memiavl snapshot (resolve dir + read version) or replay DB unconditionally, before the boundary.Status() != migration.MigrationComplete guard at line 484. For a completed EVM migration no memiavl rows are ever consumed, yet the command still fails here if --memiavl-dir lacks snapshot-<height>/evm or cannot replay that height. Consider skipping the memiavl open when the boundary is already MigrationComplete so completed composite nodes can be digested from FlatKV alone. (Raised by Codex.)

- Extract memiavl-open-mode / normalization string literals (snapshot, replay,
  semantic, independent, translator) into named constants (goconst).
- Change inspectAccumulator.listed to int and drop the uint64->int conversion
  in the list-limit check (gosec G115).

Co-authored-by: Cursor <cursoragent@cursor.com>

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A well-scoped, thoroughly-documented refactor of the seidb evm-logical-digest diagnostic tool: adds a composite backend, a replay memiavl open mode, and folds the FlatKV-only migration-boundary marker out of the legacy comparison. The change is a read-only debugging tool (non-app-hash-breaking) with sound abstractions and good test coverage for the marker/merge logic; only minor UX and follow-up notes.

Findings: 0 blocking | 5 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion review file (cursor-review.md) was empty — that pass produced no output. REVIEW_GUIDELINES.md was also empty, so no repo-specific standards were applied beyond AGENTS.md.
  • Balance handling (semanticAccountDigestState.balance + logicalPayload copying it) is a consistency change, not a bug: the EVM keyspace has no balance key, so the memiavl semantic path leaves balance zero, and the pre-existing digestFlatKV path already included ad.GetBalance(). It's worth a one-line note in the code that this makes the composite/flatkv account payload consistent with digestFlatKV; if FlatKV ever begins storing non-zero EVM balances, composite/flatkv account digests would diverge from the zero-balance memiavl-semantic truth node (a pre-existing property of digestFlatKV, not newly introduced here).
  • In digestCompositeMigrateEVM, for a completed migration (boundary.Status()==MigrationComplete) the memiavl side is never scanned, yet the code still resolves the snapshot dir / reads the snapshot version (snapshot mode) or fully opens a read-only replay DB (replay mode) beforehand. For a completed node in replay mode this opens an expensive memiavl DB that is never used — consider deferring the memiavl open until after the completeness check.
  • No end-to-end test exercises the new composite or replay paths (they require a real on-disk memiavl DB/snapshot). The added unit tests cover legacyForCompare and mergeCompositeFlatKVAccount well; an integration-style test would be nice-to-have but is understandably heavy for this tooling.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

memiavlNormalization, _ := cmd.Flags().GetString("memiavl-normalization")
memiavlOpenMode, _ := cmd.Flags().GetString("memiavl-open-mode")
if inspectBucket != "" {
if memiavlOpenMode != memiavlOpenModeSnapshot {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] This guard rejects any non-snapshot --memiavl-open-mode before the backend is known, so --backend flatkv --inspect-bucket ... --memiavl-open-mode replay fails even though the memiavl open mode is irrelevant to a FlatKV inspect (matches Codex's finding). The default is snapshot, so this only bites users who explicitly pass the flag alongside --inspect-bucket, but scoping the check to the memiavl/composite inspect paths (or gating on backend == "memiavl") would avoid the spurious rejection. Low priority.

Document the mid-migration workflow (flatkv migrated rows ∪ memiavl
not-yet-migrated rows) and when to use --memiavl-open-mode replay, which was
missing from the Usage block despite composite being the tool's main mode for
migrating nodes.

Co-authored-by: Cursor <cursoragent@cursor.com>

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A well-structured refactor of the offline seidb evm-logical-digest tool that adds composite (mid-migration) and memiavl replay modes plus migration-boundary marker omission, with good tests and documentation. The one substantive concern (shared with Codex) is that replay mode does not assert the DB actually reached the requested height; it is an offline diagnostic and the reached version is printed, so this is non-blocking but worth fixing.

Findings: 0 blocking | 4 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion review (cursor-review.md) was empty — that pass produced no output. REVIEW_GUIDELINES.md was also empty, so no repo-specific standards were applied.
  • Test coverage regression: TestShouldIncludeFlatKVEVMLogicalDigestKey was deleted, even though shouldIncludeFlatKVEVMLogicalDigestKey was moved and extended to also let migrationBoundaryPhysKey through. Consider re-adding a test that asserts both marker keys (version + boundary) and an EVM key are included while non-EVM module rows are excluded.
  • Balance is now plumbed into semanticAccountDigestState.logicalPayload from the flatkv side in composite mode, while the pure-memiavl semantic path leaves it zero. This is consistent today only because the migrate_evm keyspace carries no balance in memiavl (so flatkv-migrated balances are also zero); if that ever changes, a completed/composite node's account digest would diverge from a memiavl-only node. A brief comment noting this invariant would help future readers.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

return err
return nil, fmt.Errorf("open memiavl read-only replay: %w", err)
}
return db, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Replay mode does not verify the DB reached the requested height. memiavl.OpenDB(height, …) seeks the nearest snapshot ≤ height and calls MultiTree.Catchup, which returns nil (no error) when the retained WAL tail is behind height (multitree.go:388-391). So for a positive height whose changelog has been truncated/retained short of it, this returns a DB at an older version, and both digestMemIAVLReplay and composite replay will silently digest a lower height than requested — producing a misleading cross-node comparison. The report does print requestedHeight and the actual version, which mitigates it, but consider adding an explicit guard here for height > 0, e.g. if height > 0 && db.Version() != height { db.Close(); return nil, fmt.Errorf("memiavl replay reached version %d, requested height %d", db.Version(), height) }. (Also raised by Codex as P1.)

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A well-structured extension of the seidb evm-logical-digest debug tool adding a composite backend, memiavl replay open-mode, and migration-boundary marker omission, with a large shared-scanner refactor. The logic is correct and the change is non-consensus (tooling only); the only notable gap is missing end-to-end test coverage for the composite path.

Findings: 0 blocking | 4 non-blocking | 0 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Test coverage gap (also raised by Codex): the new composite path has no end-to-end test. The added tests cover only legacyForCompare marker omission and flatkv+memiavl account-fragment merging; the boundary IsMigrated filtering, memiavl-unmigrated inclusion, marker omission within a composite run, and final digest composition are untested and could regress silently. Non-blocking since this is a debug/ops tool that was validated manually against the pacific-1 fleet, but a table test building a partial-migration boundary and asserting flatkv ∪ unmigrated memiavl == memiavl-only digest would lock in the core invariant.
  • Second-opinion inputs: cursor-review.md is empty (Cursor produced no output) and REVIEW_GUIDELINES.md is empty/missing (no repo-specific review standards were available to apply).
  • Snapshot-mode composite on a completed migration (boundary Status == MigrationComplete) still calls resolveMemIAVLEvmSnapshotDir + readMemIAVLSnapshotVersion even though the memiavl side is never scanned; on a completed node whose memiavl snapshot at --height has been pruned this errors out spuriously. Minor, since a completed node is better digested with --backend flatkv.
  • The new balance field in semanticAccountDigestState / logicalPayload is currently inert: there is no EVM balance key kind yet (flatkv writes nil balance, memiavl has no balance leaf), so all paths keep balance zero. Harmless and forward-compatible, but worth a one-line comment noting it only becomes meaningful once a balance key kind exists, to avoid future confusion about cross-backend account-bucket equality.

@blindchaser blindchaser added this pull request to the merge queue Jul 9, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 9, 2026
@blindchaser blindchaser added this pull request to the merge queue Jul 9, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 9, 2026

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A well-structured refactor of the diagnostic evm-logical-digest tool adding composite/replay modes and migration-boundary marker omission, with good tests. One legitimate correctness gap (composite mode never enforces that the two backends are read at the same version) is worth guarding but is non-blocking for a diagnostic tool.

Findings: 0 blocking | 4 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Second-opinion coverage was partial: cursor-review.md and REVIEW_GUIDELINES.md are both empty (no Cursor findings and no repo-specific guidelines to apply). Codex produced exactly one finding, incorporated below.
  • Snapshot mode requires a memiavl snapshot at --height even when the flatkv migration is already complete (boundary.Status()==MigrationComplete), a case where memiavl is never scanned (see the skip at line 504). resolveMemIAVLEvmSnapshotDir/readMemIAVLSnapshotVersion run unconditionally at lines 473-480, so a completed node with no memiavl snapshot at that height fails needlessly. Consider resolving the snapshot lazily only when the boundary is incomplete.
  • Behavior change: logicalPayload now includes the 32-byte balance (previously forced to zero). This is only populated on the flatkv side (mergeCompositeFlatKVAccount); memiavl semantic leaves never set balance, so it stays zero there. Since EVM-store account balance is effectively always zero in this keyspace this keeps digests consistent, but it's worth confirming a real flatkv node never carries a non-zero GetBalance() that would then diverge from a pure-memiavl digest.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

default:
return fmt.Errorf("unknown --memiavl-open-mode %q (want snapshot|replay)", memiavlOpenMode)
}
printDigestStart(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Composite mode reads memVersion (from the snapshot or the replay DB) but never compares it against opened.Version() before unioning flatkv rows with memiavl rows. With --height 0 (flatkv resolves to its latest version, memiavl to the current symlink) or when a node's snapshot rewrite lags the tip, the two backends can be at different chain heights, silently producing a digest for a state that never existed at a single height — which defeats the tool's apples-to-apples comparison purpose. Suggest rejecting a mismatch here (e.g. if opened.Version() != memVersion { return fmt.Errorf(...) }) before printDigestStart, or at minimum documenting that the operator must verify the two version fields in the output match.

@blindchaser blindchaser enabled auto-merge July 9, 2026 14:58
@blindchaser blindchaser added this pull request to the merge queue Jul 9, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants