Skip to content

fullhistory: reorganize packages (#824)#844

Merged
chowbao merged 6 commits into
feature/full-historyfrom
chore/fullhistory-pkg-reorg-824
Jul 9, 2026
Merged

fullhistory: reorganize packages (#824)#844
chowbao merged 6 commits into
feature/full-historyfrom
chore/fullhistory-pkg-reorg-824

Conversation

@chowbao

@chowbao chowbao commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Closes #824. Closes #835. The streaming/ catch-all was already split into geometry/catalog/ingest/backfill/lifecycle + per-domain stores in the #817#820 stack; this PR does the leftover moves plus the safe cleanups agreed on the issue.

This is a pure reorganization — moves + mechanical renames, zero behavior change. No production logic is altered.

What changed

1. pkg/storage/

pkg/ is meaningless inside an already-internal/ tree. Rather than a flat drop (the issue's literal ask), this renames it to storage/ — a meaningful name for the storage tier that sits below the daemon logic, keeping chunk, rocksdb, and the per-domain stores/ grouped.

Deviation from the issue text, which says "drop the pkg/ folder." Flagging explicitly — happy to flatten fully instead if reviewers prefer.

2. Extract config/

config.go + config_lock.go (schema, load/parse, PathsLayout, root locks) move into a config/ package. Cycle-safe: only main.go imports the root package and it uses RunDaemon(ctx, path) — no config symbols.

config_validate.go stays in root: validateConfig depends on catalog.Catalog and the root-defined NetworkTipBackend, so it's startup orchestration, not static config — moving it would create a config → root cycle.

3. Extract durable/ (fsync helpers)

Per tamirms' review: the 7 os-level durability ops (BarrierNewFile, FsyncDir, FsyncParentDirs, FsyncNewDirs, DeepestExistingDir, DeleteFileIfExists, RmdirIfEmpty) had zero geometry/domain references — they only sat in geometry/paths.go by accident of sharing the file with the path bijection. They move to a durable/ leaf imported by config, catalog, backfill, and the daemon. geometry is now naming + math only (no os import).

4. Safe ride-along cleanups

  • Removed the errLockHeld relay sentinel — acquireLock wraps ErrRootLocked directly (one producer, one consumer). issue comment
  • Swept the streaming codename from operator-facing error/log messages: the self-locating prefixes are deleted outright (per the full-history: dead pre-wired surface + stale contract docs (~300 lines deletable) #841 plan — an error's location comes from its wrap chain, so a prefix is redundant whether it says streaming: or fullhistory:); the four Info/Debug log lines follow the same decision. Comments naming a now-nonexistent "streaming package/daemon" reworded. Left domain uses of "streaming", the fullhistory_streaming Prometheus subsystem, and metastore test-key literals (data/interface formats, not codenames).

Open-question resolutions (from #824)

  • chunk + geometry: kept separate — the dependency graph forbids merging (chunk is imported by the stores; geometry imports the stores → a merge would cycle).
  • hotloop: stays in root — it's the daemon's runtime loop, coupled to daemon.go orchestration.
  • progress → root; retention stays in lifecycle — since the soroban-rpc: Replace all hex-encoded contract ids with strkeys #25 clamp deletion, progress.go's only production caller is startup.go, so it moves next to it (unexported lastCommittedLedger). Retention remains lifecycle's freeze/prune policy gate.

5. Behavioral cleanups (second commit)

The logic refactors from the review, kept in a separate commit (fullhistory: hot-create choreography, OpenReady, test-fixture cleanup) so the reorg commit above stays a reviewable pure-reorg:

  • cat.BeginHotCreate / cat.FinishHotCreate — the hot-DB create choreography (wipe + transient key → hotchunk.Open → fsync chain → flip ready) moves behind the catalog, mirroring MarkChunkFreezing/FlipChunkFrozen. hotloop.openHotDBForChunk now sandwiches hotchunk.Open between the pair and no longer does raw os/fsync itself. discussion_r3517588234
  • hotchunk.OpenReadyWrite / OpenReadyView — a thin named pair over one unexported openReady enforcement site for "ready key ⇒ must-exist, never-creating open," so call sites name the handle instead of passing a naked boolean. openHotDBForChunk (write), refineWithHotDB, and tryHotSource (view) all route through it. hotchunk gains a geometry import for HotState (verified acyclic). discussion_r3517588278
  • DeleteHotKeydeleteHotKey unexported (only prod caller is same-package DiscardHotChunk); its raw-key round-trip test moves into the catalog package. PutHotTransient/FlipHotReady stay exported — they're genuine cross-package test-setup primitives.
  • Shared fhtest package holds the ZeroTxLCMBytes fixture, replacing three byte-identical copies.

Not done: the review also named testSink/execRecorder for consolidation, but each exists in exactly one package — they aren't duplicated, and merging them would only force package-internal interfaces to be exported. Left as-is.

6. Fold metastore into catalog (finishes #824) + lastCommittedLedger(cat) (finishes #835)

Per review comment: metastore was a 115-line rocksdb wrapper with exactly one consumer. The string-KV surface is now catalog-internal (catalog/kv.go, unexported), and NewCatalog(store, …) becomes catalog.Open(path, layout, txLayout, logger) — the catalog owns its backing store, Close releases it. Gone with the fold: the two-step daemon wiring, the metastore.BatchWriter type threaded through the protocol/sweep closures, the ErrNotFound manufacture-then-translate round-trip in Catalog.get, and metastore.New's guards that duplicated rocksdb.New's. The KV test suite moved to catalog/kv_test.go.

The fold hands over the rest of #835: the catalog now carries a logger, so refineWithHotDB uses cat.Logger(), the logger parameter drops, and lastCommittedLedger(cat) lands as the design wrote it. The deriveCompleteThrough/deriveLastCommitted test-shim aliases collapsed into direct calls (the nil-logger seam #835 worried about was already gone — those tests run against real hot DBs).

One test seam changed shape: backfill's skip-when-coverage-frozen test used to seed raw "pruning" chunk keys on the bare store; with no bare store to reach, it now runs the real SweepChunkArtifacts — which is exactly the finalized-window scenario it was simulating.

Verification

go build, go vet, go test -short ./cmd/stellar-rpc/internal/fullhistory/..., and the repo golangci-lint all green (rocksdb cgo) at every commit. Renames are git-mv (history preserved); import rewrites are mechanical (gofmt -r, AST-based).

chowbao added 2 commits July 7, 2026 16:54
Pure reorganization — moves + mechanical renames, zero behavior change.

- pkg/ -> storage/ (rename the meaningless pkg/ to the storage tier's name;
  chunk, rocksdb, and per-domain stores stay grouped under it)
- extract config/ (config.go + config_lock.go); config_validate.go stays in
  root as startup orchestration (depends on catalog + NetworkTipBackend)
- extract durable/ leaf for the os-level fsync/barrier helpers, so geometry is
  naming + math only (per tamirms PR #818 review)
- drop the errLockHeld relay sentinel; acquireLock wraps ErrRootLocked directly
- sweep the streaming codename from operator-facing messages and stale
  "streaming package/daemon" comments

Deferred to a follow-up: BeginHotCreate/FinishHotCreate choreography, the
hotchunk open-ready helper, and test-fixture consolidation (behavioral
refactors kept out to keep this a reviewable pure-reorg).
…#824)

The behavioral cleanups agreed on #824, kept out of the pure-reorg commit.

- catalog: BeginHotCreate (wipe + transient) / FinishHotCreate (fsync + flip
  ready) move the hot-DB create choreography behind the catalog, mirroring
  MarkChunkFreezing/FlipChunkFrozen; hotloop.openHotDBForChunk sandwiches
  hotchunk.Open between the pair and no longer does raw os/fsync itself.
- hotchunk.OpenReady is the single enforcement site for "ready key ⇒ must-exist,
  never-creating open": openHotDBForChunk, refineWithHotDB, and tryHotSource all
  route through it. hotchunk gains a geometry import (HotState); acyclic.
- catalog.DeleteHotKey unexported to deleteHotKey (only prod caller is
  same-package DiscardHotChunk); its raw-key round-trip test moves into the
  catalog package. PutHotTransient/FlipHotReady stay exported — genuine
  cross-package test-setup primitives.
- New fhtest package holds the shared ZeroTxLCMBytes fixture, replacing three
  byte-identical copies.

Not done: the review also named testSink/execRecorder for consolidation, but
each exists in exactly one package (not duplicated), so merging them would only
force package-internal interfaces to be exported. Left as-is.
// live is the highest "ready" hot chunk (from ReadyHotChunkKeys), so route the
// read-only open through the single OpenReady enforcement site: must-exist,
// never auto-healed, uniform won't-open error.
hot, openErr := hotchunk.OpenReady(geometry.HotReady, cat.Layout().HotChunkPath(id), id, logger, true)

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.

The PR description resolves "progress + retention: stay in lifecycle — freeze/prune policy," but that rationale only fits retention (the tick's gate). Nothing in the lifecycle package has called anything in progress.go since the clamp deletion — its only production caller is startup.go — and #835's recorded scope (from your deferral on #820) includes moving it next to that caller in the fullhistory root. Since this PR is exactly the place for pure moves, suggest doing it here: git mv progress.go to the root, unexport LastCommittedLedger (same package as its caller now), and #835 shrinks to just the logger change. If you'd rather not, no problem — but then the resolution text should say "deferred to #835," not "stays."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 8fa6c1a: git mv to the root next to startup.go, LastCommittedLedger → unexported lastCommittedLedger, and its three test files moved along (the root test helpers already covered the scaffolding, including openLiveHotDB). The lifecycle tests that used deriveCompleteThrough as a fixture oracle now pass their fixture-known values directly — runTickForCatalog collapsed into direct runLifecycle(…, lastChunk) calls, so tick inputs are explicit rather than re-derived by the code under test's own family. PR description updated; noted on #835 that its scope shrinks to the logger-parameter change.

return nil, fmt.Errorf("%w: %q (another daemon is using it)", ErrRootLocked, root)
}
return nil, fmt.Errorf("streaming: lock %q: %w", path, err)
return nil, fmt.Errorf("fullhistory: lock %q: %w", path, err)

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.

The sweep renamed streaming:fullhistory: on the error strings, but the plan recorded on #841 is to delete these prefixes outright: an error's location comes from its wrap chain (callers already wrap with "read config", "lock root", etc.), so a self-locating prefix is redundant — renaming it just moves the problem from a codename to a package name. Since you're already sweeping every one of these strings in this PR, deleting the prefix instead is the same mechanical pass and closes #841's bullet rather than half-addressing it. If you prefer keeping the prefix, that's shippable too — just needs a note on #841 so its bullet shrinks to the four Info/Debug log lines.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 8fa6c1a — deleted outright rather than renamed: all 19 remaining error strings (config ×2, config_lock ×6, catalog ×6, geometry ×5; the 7th config_lock one was errLockHeld, already dropped in this PR), and the four Info/Debug log lines followed the same decision since it's the same mechanical pass. Noted on #841 that its prefix bullet is closed here.

// txhash, metastore) and the cross-cutting error sentinels they
// emit. Per-domain stores translate their backing primitive's
// errors (pkg/rocksdb, pkg/packfile, os) into these sentinels at
// errors (storage/rocksdb, storage/rocksdb, os) into these sentinels at

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.

The sweep garbled this line: "(storage/rocksdb, storage/rocksdb, os)" — the second one was pkg/packfile in the base (itself stale; packfile lives at internal/packfile). Should read "(storage/rocksdb, internal/packfile, os)".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8fa6c1a — now reads "(storage/rocksdb, internal/packfile, os)".

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.

One codename straggler the sweep missed, at rocksdb.go:579: "The streaming ingestion contract requires…" — that's the retired codename, not the domain word. "The ingestion contract" says it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8fa6c1a — "The ingestion contract requires…".

// a missing or gutted "ready" DB fails the open — never auto-healed into a fresh
// empty one — wrapped in the uniform won't-open error so every ready-open site
// reports it identically.
func OpenReady(

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.

Optional: the trailing readOnly bool makes the call sites read OpenReady(state, dir, id, logger, false) — a naked boolean the reader has to look up. Two thin names over an unexported core — OpenReadyWrite / OpenReadyView (or similar) — would keep the single enforcement site while letting each call site say what it means.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 8fa6c1a: OpenReadyWrite / OpenReadyView over an unexported openReady core — the enforcement site stays single, and the three call sites (hotloop resume handle, freeze source, last-committed refiner) now say what they mean without the naked boolean.

@tamirms

tamirms commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Two more code changes belong in this PR, and they compound:

  1. Fold the metastore package into catalog. It's a 115-line wrapper over rocksdb with exactly one consumer — the catalog — and folding it was the one fullhistory/streaming: split the streaming package into purpose-named packages #824 item this PR doesn't address (Streaming daemon: Phase 2 — hot store, lifecycle + live ingestion (closes #816, #808) #820 (comment)). The fold deletes the package, the two-step daemon wiring, and the metastore.BatchWriter type threaded through the protocol closures; NewCatalog(store) becomes catalog.Open(path, logger).
  2. That fold hands you the rest of fullhistory: simplify LastCommittedLedger to the design signature LastCommittedLedger(cat) #835 for free. Once the catalog is constructed with a logger, refineWithHotDB can use it, the logger parameter drops, and LastCommittedLedger(cat) lands as the design wrote it. Combined with moving progress.go next to its only caller (other comment), fullhistory: simplify LastCommittedLedger to the design signature LastCommittedLedger(cat) #835 is done — in this PR, in a handful of lines.

With those two, this PR finishes #824 and #835 outright. I'd stop there: the #841 deletions (~300 lines of dead API plus test migrations) are a different kind of change and should be their own PR.

…enReadyWrite/View (#824)

- Move progress.go (+ its three test files) from lifecycle/ to the
  fullhistory root next to its only production caller, startup.go;
  unexport LastCommittedLedger -> lastCommittedLedger. Lifecycle tests
  that re-derived 'through' as an oracle now pass their fixture-known
  values directly (runTickForCatalog collapsed into runLifecycle calls).
- Delete the 'fullhistory: ' self-locating prefixes outright (19 error
  strings + 4 Info/Debug log lines) instead of keeping the rename;
  error context comes from the wrap chain (#841).
- Split hotchunk.OpenReady into OpenReadyWrite/OpenReadyView over an
  unexported openReady core, so call sites name the handle they take.
- Fix the garbled sentinel-translation comment in stores/errors.go and
  the retired 'streaming' codename straggler in rocksdb.go.
…t) (#824, #835)

- metastore was a 115-line rocksdb wrapper with exactly one consumer.
  Its string-KV surface is now catalog-internal (catalog/kv.go,
  unexported); NewCatalog(store, ...) becomes catalog.Open(path, layout,
  txLayout, logger) — the catalog owns its backing store, Close releases
  it. Gone with the fold: the two-step daemon wiring, the
  metastore.BatchWriter type threaded through the protocol/sweep
  closures, the ErrNotFound manufacture-then-translate round-trip in
  Catalog.get, and metastore.New's guards duplicating rocksdb.New's.
  The KV test suite moves to catalog/kv_test.go.
- The catalog now carries a logger, so refineWithHotDB uses
  cat.Logger(), the logger parameter drops, and lastCommittedLedger(cat)
  lands as the design wrote it (#835). The deriveCompleteThrough/
  deriveLastCommitted test-shim aliases collapse into direct calls.
- backfill's skip-when-coverage-frozen test seeded raw "pruning" keys on
  the bare store; with no bare store to reach it now runs the real
  SweepChunkArtifacts — the finalized-window scenario it simulates.
@chowbao

chowbao commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Both done in a051a06 (and the progress.go move from the inline thread landed in 8fa6c1a):

  1. metastore folded into catalog. The string-KV surface is catalog-internal now (catalog/kv.go, unexported), and NewCatalog(store, …) became catalog.Open(path, layout, txLayout, logger) — the catalog owns its backing store, Close releases it. The two-step daemon wiring and the metastore.BatchWriter threading through the protocol/sweep closures are gone, and the fold also deleted the ErrNotFound manufacture-then-translate round-trip in Catalog.get plus metastore.New's guards that duplicated rocksdb.New's. The KV test suite moved to catalog/kv_test.go. Net −510 lines.
  2. fullhistory: simplify LastCommittedLedger to the design signature LastCommittedLedger(cat) #835 landed. refineWithHotDB uses cat.Logger(), the logger parameter dropped, and lastCommittedLedger(cat) is the design signature. The deriveCompleteThrough/deriveLastCommitted shim aliases collapsed into direct calls — the nil-logger seam fullhistory: simplify LastCommittedLedger to the design signature LastCommittedLedger(cat) #835 worried about was already gone (those tests run against real hot DBs).

One test seam changed shape: backfill's skip-when-coverage-frozen test used to seed raw "pruning" chunk keys on the bare store; with no bare store to reach, it now runs the real SweepChunkArtifacts — exactly the finalized-window scenario it was simulating.

Agreed on stopping there — the only #841 piece taken here is the error-prefix bullet you flagged inline; the ~300-line dead-API deletions stay a separate PR. PR description updated (Closes #824 / Closes #835).

chowbao added 2 commits July 9, 2026 12:09
CI floats on the stable channel (rust-toolchain.toml + rustup update in
every workflow) and picked up rustc 1.97.0, under which ethnum 1.5.2
fails to compile: its error.rs does mem::transmute(()) into
core::num::TryFromIntError, whose size changed (E0512). ethnum 1.5.3
constructs the error values from real from_str_radix failures instead.
Every job dies in the shared make build-libs step, so this cleared the
whole matrix; main needs the same bump separately.
@chowbao chowbao merged commit 3188071 into feature/full-history Jul 9, 2026
8 of 15 checks passed
@chowbao chowbao deleted the chore/fullhistory-pkg-reorg-824 branch July 9, 2026 17:27
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