Files
geth/core/rawdb/zap_ancient_migration.go
zeekay facd99fee2 feat(rawdb): ZAP-native ancient store for cold block history
Implement ethdb.ResettableAncientStore over a single KV backend (the
canonical luxfi/zapdb). Replaces the upstream geth freezer
(.cdat/.ridx files per table) with one ZAP database per chain. Snappy
compression per-block (~10x cold data); single-byte kind markers keep
prefix scans efficient.

Key layout (single ZAP database per chain):
  ['a'][kind byte][big-endian uint64 number] -> snappy value
  ['m']['h'][kind byte]                      -> head pointer
  ['m']['t'][kind byte]                      -> tail pointer

ZAP is canonical Lux storage (luxfi/zap + luxfi/zapdb). PebbleDB stays
only as legacy in-place upgrade. New deployments use ZAP everywhere.

Surface:
- core/rawdb/zap_ancient.go            (ZapAncientStore implementation)
- core/rawdb/zap_ancient_migration.go  (exported wrappers for migrate-ancient)
- core/rawdb/zap_ancient_rlp.go        (rlp.EncodeToBytes split out)
- core/rawdb/zap_ancient_test.go       (7 tests: roundtrip, out-of-order,
                                        range maxBytes, truncate head/tail,
                                        snappy, persist-across-open)
- core/rawdb/zap_ancient_bench_test.go (write + read throughput)
- cmd/migrate-ancient/                 (one-shot upstream-freezer → ZAP migrator)
- cmd/fleet-bench/                     (CSV harness for write/read latency)

Benchmarks (in-memory backend, M1 Pro):
  WriteThroughput: 6860 ns/op, 4 puts per block (header+hash+body+receipt)
  ReadHot:          920 ns/op

Migration command for an existing chain:
  migrate-ancient --src /data/db/<net>/chainData/<chainID>/ancient \
                  --dst /data/ancient/<chainID>

The luxfi/operator NodeFleet CRD ships archive pods with
--ancient-store-backend=zap as the default.

Tests: 7 zap-ancient tests + full core/rawdb suite race-clean.
2026-06-02 03:37:48 -07:00

51 lines
2.0 KiB
Go

// Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// Migration helpers — exported wrappers around the internal
// freezerTableConfig + NewFreezer / NewZapAncientStore plumbing so the
// `cmd/migrate-ancient` tool can drive both stores without re-importing
// the unexported config struct.
//
// We deliberately keep these wrappers minimal: only the table
// configuration and the open-for-migration entry points are exposed.
// The full ResettableAncientStore surface stays on the existing
// `ethdb.AncientStore` interface.
package rawdb
import (
"github.com/luxfi/geth/ethdb"
)
// FreezerTableConfig is the exported mirror of the unexported
// freezerTableConfig. Only the migration tool consumes this — the
// production path keeps the unexported struct.
type FreezerTableConfig struct {
NoSnappy bool
Prunable bool
}
// internalTables converts the exported config into the unexported
// shape the freezer + ZAP store consume.
func internalTables(in map[string]FreezerTableConfig) map[string]freezerTableConfig {
out := make(map[string]freezerTableConfig, len(in))
for k, v := range in {
out[k] = freezerTableConfig{noSnappy: v.NoSnappy, prunable: v.Prunable}
}
return out
}
// OpenFreezerForMigration opens the upstream-format ancient store in
// read-only mode. Used by `cmd/migrate-ancient` as the source of a
// migration.
func OpenFreezerForMigration(datadir string, tables map[string]FreezerTableConfig) (ethdb.AncientStore, error) {
return NewFreezer(datadir, "migrate", true, freezerTableSize, internalTables(tables))
}
// NewZapAncientStoreForMigration opens a fresh ZAP-native ancient
// store at the given datadir. The caller supplies the underlying KV
// store — typically `ethdb/zapdb.New(datadir, ...)` in production;
// `memorydb.New()` in tests.
func NewZapAncientStoreForMigration(datadir string, tables map[string]FreezerTableConfig, store zapAncientBackend) (*ZapAncientStore, error) {
return NewZapAncientStore(datadir, internalTables(tables), store)
}