Skip to content

feat: emit and seed the evo snapshot section in dumptxoutset and snapshot load - #7601

Open
PastaPastaPasta wants to merge 3 commits into
dashpay:developfrom
PastaPastaPasta:feat/evo-snapshot-load
Open

PastaPastaPasta wants to merge 3 commits into
dashpay:developfrom
PastaPastaPasta:feat/evo-snapshot-load

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Aug 13, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Stacked on #7698 and #7593 — the first two commits belong to those PRs; this PR adds only the final commit. Rebased onto develop post-#7592. Final PR of the AssumeUTXO M4 decomposition (#7579 — series map there): the lifecycle wiring that makes the format real. dumptxoutset emits the canonical evo section alongside the UTXO set; snapshot load decodes, validates, and seeds it; completion cross-checks everything the earlier PRs promised (retained-section CbTx verification, historical MN-list reconstruction, seeded-modifier integrity) and the assumeutxo prune lock keeps the base block readable until that happens.

What was done?

  • dumptxoutset appends the marker/version/payload/hash evo section; load reads it (AutoFile::size() bounds the decode), validates context-free and against chain, and seeds EvoDB through the feat: build, chain-validate, and seed evo snapshot state #7593 seeding surface without publishing into shared caches. The section is retained in EvoDB (EVODB_SNAPSHOT_EVO_SECTION) for the deferred completion-time CbTx cross-check.
  • Background validation records MN-list hashes only at the snapshot base and the bounded set of required historical work blocks; completion compares them against payload-derived reconstruction — this is what turns the M3-era marker scaffolding into a real integrity check.
  • Runtime seeded-state mismatches (GetAllQuorumMembers) route into the controlled invalid-snapshot shutdown (HandleSnapshotStateMismatch), completing the plumbing feat: build, chain-validate, and seed evo snapshot state #7593 deliberately left unwired.
  • The assumeutxo prune lock (ProtectSnapshotBaseFromPruning/release, BlockManager::DeletePruneLock) — the remainder of the original B6 commit, placed with its consumer as discussed in backport: assumeutxo M4 — evo snapshot format v3 and LLMQ reconstruction #7579 review.
  • The chain-facing helpers this PR adds (seeding, base-block CbTx verification) live in the header-less evo/snapshot_chain.cpp unit from feat: build, chain-validate, and seed evo snapshot state #7593, keeping the codec cycle-free. Keeping ChainstateManager code in validation.cpp makes validation depend on evo/snapshot.h, whose value-type members pull in creditpool/mnhftx/commitment — those three cycles are added to EXPECTED_CIRCULAR_DEPENDENCIES, the same accepted class as the existing validation hub entries.
  • Per that review, every ChainstateManager body stays in validation.cpp — the previously proposed evo/snapshot_load.cpp file is gone from the series; chain-facing helpers are declared in evo/snapshot.h and implemented where upstream keeps the logic.
  • Tests: feature_assumeutxo_dash.py (dump → load → background-validate → complete, plus recovery/invalid paths), rpc_dumptxoutset.py update, and the unit-test deltas for the lifecycle (prune-lock survival, soft-fail base detection, EvoDB retention).

Build-system note: evo/snapshot_chain.cpp is listed in libdashkernel_la_SOURCES here because validation.cpp (a kernel source) now calls the chain-aware helpers; without it the kernel library and dash-chainstate fail to link.

How Has This Been Tested?

Full unit suite green on a --enable-werror build; feature_assumeutxo_dash.py, rpc_dumptxoutset.py, and feature_reindex.py pass locally. The stack beneath it carries its own sanitizer verification.

Breaking Changes

None. dumptxoutset output gains the evo section (new format version); old snapshots without it are rejected at load on DIP3-active chains, which is the intended security posture — there is no legacy Dash snapshot format in the wild.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@thepastaclaw

thepastaclaw commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

⛔ Final review complete — 5 blocking finding(s) (commit 23d04e1) · triage: critical · Phase 2 only (queue backlog)

PastaPastaPasta and others added 2 commits September 18, 2026 16:06
…tate

dashpay#7437 extended the ExtAddr CDeterministicMNState encoding with the collateral shares and early-exit penalty fields, and dashpay#7592 merged on top of it without re-running the test that pins the canonical v3 snapshot encoding. The synthetic snapshot carries an ExtAddr masternode, so its canonical hash moved and populated_v3_golden_value now fails on develop. Pin the new value; every other case in the suite passes unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Second code PR of the assumeutxo M4 series: BuildEvoSnapshot() collects commitments, rotation snapshots, historical MN-list diffs, and exact score modifiers from chain state; ValidateEvoSnapshotAgainstChain() verifies a decoded snapshot against the block index, deployments, and quorum reconstruction; and the EvoDB seeding surface writes exactly the state reconstruction later reads. The chain-aware implementations live in the header-less evo/snapshot_chain.cpp unit so the codec in evo/snapshot.cpp never includes validation.h (no evo/snapshot -> validation cycle); ChainstateManager code is untouched here and stays in validation.cpp later in the series.

Parameter-derived quorum counts are carried as maxima rather than exact requirements: a young chain, a freshly activated type, or a rotation type whose horizon predates activation legitimately has less history, and requiring the full horizon made dumptxoutset fail on valid chain state (flagged as blocking by review on the original M4 PR). Completeness is anchored by the completion-time CbTx quorum merkle root, the historical-diff and modifier tallies, and completion-time quorum reconstruction; two regression tests fail against exact-count enforcement. GetHashModifier() prefers a seeded exact modifier when work-block data is unavailable and cross-checks it when present; the mismatch error stays unreachable until the load integration seeds modifiers. GetDeterministicMNListHash() switches to the canonical codec hash (dev-channel-only marker-compat caveat in the PR description).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Rebased onto the rebased #7593 (f6cb0708b1, itself on develop ae042da711 + #7698) and pushed as 63992756ea. Only this PR's own commit is new; the 14 stacked commits are replaced by #7698's one-liner and #7593's single commit. Marked ready for review.

git range-diff shows the one commit as !; the differences are:

  • ConnectTip/DisconnectTip: develop rewrote the benchmark timing to SteadyClock. The try { ... } catch (evo::SnapshotStateMismatchError&) wrap around the EvoDB transaction block is unchanged in what it encloses; only the timing lines around it are develop's.
  • ProtectSnapshotBaseFromPruning/ReleaseSnapshotPruneLock now sit after develop's new Flatten()/ChainstateManager ctor block.
  • Type renames from feat: evo snapshot v3 — canonical bounded codec and context-free validation #7592's final review (CEvoSnapshotEvoSnapshot) in validation.cpp, rpc/blockchain.cpp, evo/snapshot.h, evo/snapshot_chain.cpp.
  • lint-circular-dependencies.py: the evo/mnhftx -> validation -> evo/snapshot -> evo/mnhftx entry is dropped; the linter reports that cycle no longer exists on the new base. The other two new entries remain.
  • evo/snapshot_chain.cpp added to libdashkernel_la_SOURCES, since validation.cpp is a kernel source and now calls into it. Noted in the PR description.

Verified locally on 63992756ea: full --enable-werror build, all 1,038 test_dash cases pass, feature_assumeutxo_dash.py, rpc_dumptxoutset.py, and feature_reindex.py pass, clang-format-diff (clang 18) clean, and the whitespace/includes/assertions/circular-dependency/logs/format-string linters pass.


🤖 Posted autonomously by Claude on behalf of pasta.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Potential PR merge conflicts

This is advisory only. It does not block CI, but it marks PRs that will likely need a rebase depending on merge order.

If this PR merges first

These open PRs will likely need a rebase:

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8a19ab80-c8e3-4ebb-807e-8c54df36ef73

📥 Commits

Reviewing files that changed from the base of the PR and between 6399275 and 23d04e1.

📒 Files selected for processing (1)
  • src/validation.cpp

Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review.


Walkthrough

The PR adds chain-aware Evo snapshot construction, validation, serialization, and state seeding. AssumeUTXO loading validates Evo data, records required historical MN-list work, handles state mismatches, and protects snapshot blocks from pruning. dumptxoutset emits Evo snapshot data and reports its hash and masternode count. New APIs seed and invalidate EvoDB-backed state for MN lists, quorum data, credit pools, and MNHF signals. Tests cover reconstruction, rollback, validation, pruning, marker cleanup, and functional snapshot output.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant dumptxoutset
  participant CreateUTXOSnapshot
  participant BuildEvoSnapshot
  participant EvoDB
  dumptxoutset->>CreateUTXOSnapshot: create UTXO snapshot
  CreateUTXOSnapshot->>BuildEvoSnapshot: build Evo state at snapshot height
  BuildEvoSnapshot->>EvoDB: read Evo chain state
  CreateUTXOSnapshot-->>dumptxoutset: return Evo hash and masternode count
Loading

Possibly related PRs

  • dashpay/dash#7553 — Adds the AssumeUTXO lifecycle and EvoDB marker APIs that this PR extends.
  • dashpay/dash#7579 — Adds the Evo snapshot format and reconstruction APIs that this PR wires into chain validation and snapshot handling.

Merge Risk: ⚪ Minimal · up to 23d04

The snapshot lifecycle changes have no identified merge-blocking issue in the supplied evidence.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 143 functions across 38 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: emitting and seeding the Evo snapshot section during snapshot creation and loading.
Description check ✅ Passed The description directly explains the Evo snapshot lifecycle, validation, seeding, pruning protection, build changes, and tests included in the changeset.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

…shot load

Final PR of the assumeutxo M4 series: dumptxoutset appends the canonical evo section (marker, version, payload, hash) after the UTXO records; snapshot activation decodes it, validates it context-free and against the chain, seeds EvoDB through the seeding surface, retains the section for the deferred completion-time CbTx cross-check, and records background MN-list hashes only for the snapshot base and the bounded set of historical work blocks. The completion path cross-checks reconstructed historical MN lists, verifies the retained section against the base CbTx, and routes runtime seeded-state mismatches (GetAllQuorumMembers) into the controlled invalid-snapshot shutdown. The assumeutxo prune lock keeps the base block's data available until that check completes.

Per review on the original M4 PR, every ChainstateManager body stays in validation.cpp: the previously proposed evo/snapshot_load.cpp move is gone, with the chain-facing helpers declared in evo/snapshot.h and the load/build/completion logic living where upstream keeps it. feature_assumeutxo_dash.py exercises the dump/load/complete cycle end to end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Pushed 23d04e1ef3 (amends the single commit; git range-diff 63992756ea..23d04e1ef3 is one hunk in Chainstate::RecordBackgroundMNListHash).

While restacking the follow-up loadtxoutset work on this head, its chainstatemanager_snapshot_init flow (which reconnects the pre-DIP3 base block in the background chainstate) hit EVO_STATE_MISMATCH at completion. Root cause: ProcessSpecialTxsInBlock only fills MNListUpdates once DIP3 is active, so for a pre-DIP3 base the recorder was hashing a default-constructed list (null block hash, height -1) while the activation-time capture and the payload both hash the canonical empty list bound to the base block. The recorder now binds the empty list to the block when MNListUpdates::new_list is unbound, so all three sides share one identity. On this PR's own tests the path was latent because the SnapshotTestSetup flows never reconnect the base in the background chainstate.

Verified on 23d04e1ef3: full build, all 1,038 test_dash cases, feature_assumeutxo_dash.py, rpc_dumptxoutset.py, feature_reindex.py.


🤖 Posted autonomously by Claude on behalf of pasta.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 2 only (queue backlog)

The snapshot lifecycle has five confirmed blocking defects affecting cold-start continuation, quorum reconstruction, exception routing, and recovery from rejected snapshots. The commit also overstates the functional test's coverage. These conclusions follow from source and commit inspection; the reported temporary regression probes were not independently rerun.

🔴 5 blocking | 🟡 1 suggestion(s)

1 finding(s) not shown inline (the lines are not part of this PR's diff)

🟡 Suggestion: Reword the commit body's end-to-end test claim
<commit:23d04e1ef3d>:1

The commit body states that feature_assumeutxo_dash.py exercises the dump/load/complete cycle end to end, but the script only calls dumptxoutset and checks returned metadata and the emitted marker. Its docstring explicitly says loading is added in M5. Reword the commit body to describe the emission checks actually present; the PR description repeats the same inaccurate coverage claim and should be corrected as well. This is a reporting correction, not a request to expand the test's scope.

source: gpt-6-astra (phase2-reviewer: general, dash-core-commit-history)

Review provenance

Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: dash-core-commit-history); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: critical by gpt-6-astra (effort low) — The intricate snapshot lifecycle changes in src/validation.cpp and src/evo/snapshot_chain.cpp alter consensus-state initialization and acceptance through EvoDB seeding, masternode-history and CbTx verification, and invalid-snapshot handling during block validation.
  • Phase 1 reviewers: not run (skipped for throughput: 22 PRs queued, above the 10 limit)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — dash-core-commit-history (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/evo/snapshot_chain.cpp`:
- [BLOCKING] src/evo/snapshot_chain.cpp:469-470: Seed the credit-pool expiry history needed after the base
  Seeding only the base CCreditPool does not support cold snapshot continuation. On a mature v20-active chain, connecting base+2 calls GetCreditPool(base+1), whose ConstructCreditPool() reads the block at base+1-CreditPoolPeriodBlocks() to determine the amount leaving the withdrawal window. If background validation has not supplied that pre-base block, GetCreditDataFromBlock() throws `failed-getcbforblock-read`, and special-transaction processing converts the failure into a consensus rejection of the valid post-base block. The historical read occurs even when there were no withdrawals. Carry and seed the per-height expiry data, or obtain the required historical blocks before allowing continuation; the aggregate latelyUnlocked value cannot determine future expirations.
- [BLOCKING] src/evo/snapshot_chain.cpp:137-145: Include work-block state for quorums not yet mined at the base
  The builder registers work blocks only for mined commitments and carried historical rotation snapshots. For a v20-active base at H+5, where H is a DKG cycle boundary and the current mining window has not started, no commitment registers H-WORK_DIFF_DEPTH. The rotation horizons start at H-C, so they do not supply this work block either. A cold consumer validating the first post-base commitment for H requests that pre-base MN list through GetAllQuorumMembers(), but neither a seeded list nor its ordinary diff chain exists; GetListForBlockInternal() throws instead of allowing the valid chain to advance. Include the pre-base work lists and modifiers required by upcoming quorum processing, even before commitments are mined, and update ValidateEvoSnapshotAgainstChain() so its required-work set accepts and requires that state.

In `src/validation.cpp`:
- [BLOCKING] src/validation.cpp:3207-3211: Allow snapshot mismatches to reach the chainstate exception boundary
  This catch cannot handle mismatches raised through special-transaction processing because intervening handlers consume them. GetAllQuorumMembers() rethrows SnapshotStateMismatchError while the EvoDB transaction is active, but CheckSpecialTxInner() and ProcessSpecialTxsInBlock() both catch std::exception and translate it into consensus-invalid state (`failed-check-special-tx` or `failed-procspectxsinblock`). ConnectTip() then calls InvalidBlockFound() rather than rejecting the snapshot through HandleSnapshotStateMismatch(). Add explicit passthrough handling for SnapshotStateMismatchError in the intervening catch layers so the transaction unwinds before this boundary handles the local-state failure.

In `src/llmq/utils.cpp`:
- [BLOCKING] src/llmq/utils.cpp:340-341: Use the seeded modifier when constructing rotation snapshots
  Passing nullptr bypasses the seeded modifier when writing a reconstructed rotation snapshot. BuildNewQuorumQuarterMembers() receives the snapshot-aware modifier, but BuildQuorumSnapshot() recalculates its ordering here without the snapshot manager. When the historical work block is unavailable and its coinbase contained a non-null ChainLock, GetNonNullCoinbaseChainlock() returns no value, so this calculation uses the block-hash fallback instead of the seeded ChainLock-derived modifier. The activeQuorumMembers bitmap is consequently indexed in a different order from the order used by GetQuorumQuarterMembersBySnapshot(). This can conflict with an existing seeded snapshot or persist incorrect membership state. Pass the already-resolved modifier into BuildQuorumSnapshot() rather than recalculating it.

In `src/evo/evodb.cpp`:
- [BLOCKING] src/evo/evodb.cpp:275-277: Remove rejected snapshot-derived state as well as lifecycle markers
  Snapshot activation commits seeded records into the shared EvoDB keyspace, but DiscardSnapshotMarkers() removes only lifecycle bookkeeping and the retained section. A rejected `llmq_M3` modifier remains readable through GetSeededQuorumModifier() after the default identity returns to NORMAL. Once ordinary validation encounters that work block, it can raise the same mismatch again, now without an active snapshot for HandleSnapshotStateMismatch() to invalidate. Seeded MN-list, rotation, and credit-pool records likewise remain available to normal readers, contradicting the recovery path's promise to restart without snapshot data. Track or isolate snapshot-owned records and remove their unvalidated state during rejection while preserving independently validated records; do not erase the retained section before performing the cleanup that needs it.

In `<commit:23d04e1ef3d>`:
- [SUGGESTION] <commit:23d04e1ef3d>:1: Reword the commit body's end-to-end test claim
  The commit body states that `feature_assumeutxo_dash.py exercises the dump/load/complete cycle end to end`, but the script only calls dumptxoutset and checks returned metadata and the emitted marker. Its docstring explicitly says loading is added in M5. Reword the commit body to describe the emission checks actually present; the PR description repeats the same inaccurate coverage claim and should be corrected as well. This is a reporting correction, not a request to expand the test's scope.

Comment on lines +469 to +470
if (!cpoolman.SeedSnapshot(snapshot_start_block, snapshot.credit_pool) ||
!mnhfman.SeedSignals(snapshot_start_block, snapshot.mnhf_signals)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Seed the credit-pool expiry history needed after the base

Seeding only the base CCreditPool does not support cold snapshot continuation. On a mature v20-active chain, connecting base+2 calls GetCreditPool(base+1), whose ConstructCreditPool() reads the block at base+1-CreditPoolPeriodBlocks() to determine the amount leaving the withdrawal window. If background validation has not supplied that pre-base block, GetCreditDataFromBlock() throws failed-getcbforblock-read, and special-transaction processing converts the failure into a consensus rejection of the valid post-base block. The historical read occurs even when there were no withdrawals. Carry and seed the per-height expiry data, or obtain the required historical blocks before allowing continuation; the aggregate latelyUnlocked value cannot determine future expirations.

source: gpt-6-astra (phase2-reviewer: general)

Comment thread src/validation.cpp
Comment on lines +3207 to +3211
} catch (const evo::SnapshotStateMismatchError& e) {
if (m_chainman.HandleSnapshotStateMismatch(e.what())) {
return state.Error("invalid assumeutxo evo snapshot state");
}
throw;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Allow snapshot mismatches to reach the chainstate exception boundary

This catch cannot handle mismatches raised through special-transaction processing because intervening handlers consume them. GetAllQuorumMembers() rethrows SnapshotStateMismatchError while the EvoDB transaction is active, but CheckSpecialTxInner() and ProcessSpecialTxsInBlock() both catch std::exception and translate it into consensus-invalid state (failed-check-special-tx or failed-procspectxsinblock). ConnectTip() then calls InvalidBlockFound() rather than rejecting the snapshot through HandleSnapshotStateMismatch(). Add explicit passthrough handling for SnapshotStateMismatchError in the intervening catch layers so the transaction unwinds before this boundary handles the local-state failure.

source: gpt-6-astra (phase2-reviewer: general)

Comment thread src/llmq/utils.cpp
Comment on lines +340 to 341
const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex, nullptr);
auto sortedAllMns = CalculateQuorum(allMns, modifier);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Use the seeded modifier when constructing rotation snapshots

Passing nullptr bypasses the seeded modifier when writing a reconstructed rotation snapshot. BuildNewQuorumQuarterMembers() receives the snapshot-aware modifier, but BuildQuorumSnapshot() recalculates its ordering here without the snapshot manager. When the historical work block is unavailable and its coinbase contained a non-null ChainLock, GetNonNullCoinbaseChainlock() returns no value, so this calculation uses the block-hash fallback instead of the seeded ChainLock-derived modifier. The activeQuorumMembers bitmap is consequently indexed in a different order from the order used by GetQuorumQuarterMembersBySnapshot(). This can conflict with an existing seeded snapshot or persist incorrect membership state. Pass the already-resolved modifier into BuildQuorumSnapshot() rather than recalculating it.

source: gpt-6-astra (phase2-reviewer: general)

Comment thread src/evo/evodb.cpp
Comment on lines +275 to 277
EraseHistoricalMNListMarkers(*db, batch);
batch.Erase(EVODB_SNAPSHOT_EVO_SECTION);
batch.Erase(EVODB_DUAL_CHAINSTATE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Remove rejected snapshot-derived state as well as lifecycle markers

Snapshot activation commits seeded records into the shared EvoDB keyspace, but DiscardSnapshotMarkers() removes only lifecycle bookkeeping and the retained section. A rejected llmq_M3 modifier remains readable through GetSeededQuorumModifier() after the default identity returns to NORMAL. Once ordinary validation encounters that work block, it can raise the same mismatch again, now without an active snapshot for HandleSnapshotStateMismatch() to invalidate. Seeded MN-list, rotation, and credit-pool records likewise remain available to normal readers, contradicting the recovery path's promise to restart without snapshot data. Track or isolate snapshot-owned records and remove their unvalidated state during rejection while preserving independently validated records; do not erase the retained section before performing the cleanup that needs it.

source: gpt-6-astra (phase2-reviewer: general)

Comment on lines +137 to +145
const size_t emit_active{std::min(indexes.size(), active_count)};
for (size_t i{0}; i < emit_active; ++i) {
const CBlockIndex* work_index{register_work_block(params, data.rotation_enabled, indexes[i])};
if (work_index == nullptr) {
error = "missing active quorum work block";
return false;
}
auto entry{ReadCommitment(qblockman, params.type, indexes[i], work_index, error)};
if (!error.empty()) return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Include work-block state for quorums not yet mined at the base

The builder registers work blocks only for mined commitments and carried historical rotation snapshots. For a v20-active base at H+5, where H is a DKG cycle boundary and the current mining window has not started, no commitment registers H-WORK_DIFF_DEPTH. The rotation horizons start at H-C, so they do not supply this work block either. A cold consumer validating the first post-base commitment for H requests that pre-base MN list through GetAllQuorumMembers(), but neither a seeded list nor its ordinary diff chain exists; GetListForBlockInternal() throws instead of allowing the valid chain to advance. Include the pre-base work lists and modifiers required by upcoming quorum processing, even before commitments are mined, and update ValidateEvoSnapshotAgainstChain() so its required-work set accepts and requires that state.

source: gpt-6-astra (phase2-reviewer: general)

@thepastaclaw thepastaclaw added the pastaclaw:changes-requested thepastaclaw's latest review requested changes label Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pastaclaw:changes-requested thepastaclaw's latest review requested changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants