Skip to content

Feat/blockstore base and db info earliest#31

Open
dntjd1097 wants to merge 10 commits into
ChorusOne:mainfrom
anam-145:feat/blockstore-base-and-db-info-earliest
Open

Feat/blockstore base and db info earliest#31
dntjd1097 wants to merge 10 commits into
ChorusOne:mainfrom
anam-145:feat/blockstore-base-and-db-info-earliest

Conversation

@dntjd1097

Copy link
Copy Markdown

No description provided.

dntjd1097 and others added 10 commits June 22, 2026 15:18
After pruning, set the blockstore base to the actual lowest block meta
("H:") height that still exists, instead of blindly using pruneHeight+1.
This ensures cometbft's LoadBaseMeta(base) finds a real block so /status
reports earliest_block_* instead of empty values (which occur when base
is 0 or points at a deleted block meta).

Scans the "H:" prefix for the minimum height (heights are ascii without
zero-padding, so they don't sort numerically). Falls back to
pruneHeight+1 if the scan finds nothing or errors. Shared by all chains
using pruneBlockAndStateStore; behavior is unchanged where the meta at
pruneHeight+1 exists, just verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SnapshotAndRestoreApp previously removed application.db immediately after
creating the snapshot, then restored in place — a failed or wrong restore
(e.g. an incompatible hashing stack) left no original to fall back to.

With --verify-after-prune (default on), restore now happens into a
temporary DB on the same filesystem, the restored per-store roots are
verified against the pre-snapshot commit info, and only on success is the
original removed and the verified DB atomically renamed into place. Any
failure before the swap leaves the original application.db intact; a
mid-swap rename failure preserves the verified DB at a logged path for
manual recovery.

With --verify-after-prune=false the previous destructive in-place restore
is kept (lower peak disk, no fallback). The safe path costs transient disk
equal to the original plus the restored DB simultaneously.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setting the base from the lowest surviving block meta could land on a
stale orphan height: range deletion uses an exclusive upper bound, so the
all-nines digit boundaries (9999999, 999999, ...) are skipped and survive,
making the scan report base=9999999 even though real blocks are far higher.
That misrepresents retention.

Drop the base override entirely (both the lowest-surviving scan added
earlier and the prior pruneHeight+1). cometbft maintains its own base
during block pruning; leave it to the node. Removes findLowestBlockHeight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete SetBlockStoreStateBase and its blockStoreKey var (now uncalled
after the base override was removed), the cmtstore import they were the
only user of, and the long-unused appKeyInfos var.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document standard vs celestia-dedicated builds (make build / build-celestia,
Docker, raw go build), the full current flag set including
--verify-after-prune and --iavl-disable-fastnode, the two application
pruning strategies (PruneAppState vs SnapshotAndRestoreApp), the
verify/verify-before-destroy guard, per-chain config, and the celestia
rationale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After offline pruning, CometBFT's /status returned empty earliest_block_*
values (base=0/"") because the blockstore base either pointed at a deleted
block meta or was never updated. LoadBaseMeta(base) then returns nil and all
earliest_* fields stay zero.

Set the blockstore base to the retain floor (pruneHeight+1) at the end of
pruneBlockAndStateStore. This differs from the previously reverted approach
(a7aca84) in three ways that avoid its failure modes:

- Use the retain floor instead of the global-minimum surviving "H:" key.
  deleteHeightRange leaves digit-boundary all-nines metas (e.g. H:9999999) as
  orphans because the per-digit range iterator end bound is exclusive; the old
  global-min scan picked those orphans and set base=9999999, misreporting
  retention.
- Verify the meta actually exists (Has "H:<height>"), scanning upward from the
  retain floor, so LoadBaseMeta(base) is guaranteed a real block.
- Write with SetSync (fsync) and log a read-back, so the base survives process
  exit instead of being lost by an async Set.

Failure is non-fatal: a wrong/absent base only affects /status earliest_*, not
node operation, so we log and continue.

Also add basefix/, a standalone diagnose/repair tool for nodes whose base is
already corrupted from an earlier prune, so the base can be fixed without a
full re-prune.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DbState only read state.db (latest state + the genesis initial_height), so
db-info never showed the post-prune base block. initial_height stays 1 because
it is the chain's genesis height, not the retained base.

Read the blockstore base (BlockStoreState.Base) and load its block meta
("H:<base>") to populate earliest_block_height / earliest_block_hash /
earliest_app_hash / earliest_block_time, mirroring CometBFT /status. The lookup
is best-effort and non-fatal: when the base is unset or its meta was pruned it
leaves the fields empty/zero, the same condition under which /status reports
empty earliest_* values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- "Block & state stores" now documents that base is rewritten to the retain
  floor (with meta-existence check, fsync, and the orphan-avoidance rationale)
  instead of being left to the node.
- db-info JSON example and notes now cover the earliest_block_* fields and
  clarify that initial_height is the genesis height, not the post-prune base.
- Add a basefix section documenting the standalone diagnose/repair tool and the
  hard limit that pruned blocks are unrecoverable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The blockstore base is now set automatically at the end of pruning
(setBlockStoreBase in generic_chain.go), so the standalone basefix tool is
redundant: an already-corrupted base is repaired simply by re-running prune.
Drop the tool and its README section to keep one code path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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