Skip to main content

Purpose and Scope

This document describes the ancient store subsystem (commonly referred to as the “freezer”) in go-stablenet and how it manages the storage lifecycle of historical blockchain data. The ancient store is an append-only archive storage layer that separates and preserves old block data in order to optimize performance and disk usage of the active database. This page covers:
  • Ancient store architecture and its separation of roles from the active database
  • Data migration flow from the active store to the ancient store
  • Ancient store operations (read, write, truncate)
  • Chain rewind handling when frozen data exists
  • Importing and exporting historical data using the Era1 format
For the active database layer and trie storage, see Database Layer and Merkle Patricia Trie.
For caching and snapshots, see Caching and Snapshots.

Ancient Store Architecture

The ancient store is a dedicated storage for historical blockchain data that has become immutable.
This layer is designed to improve performance and disk efficiency with the following characteristics:
  • Append-only write model
  • Sequential access by block number
  • Physical and logical separation from the active key-value database
  • Minimized random-access cost for old data

Storage Layers

Blockchain data is divided into two main layers:
  • Active Database
    Recent blocks, state (tries), headers, transaction indices, and other data
    that require frequent random access and writes
  • Ancient Store
    Blocks, receipts, and total difficulty data that are sufficiently old and no longer mutable

Database Interface

The ethdb.Database interface provides a unified access point for both the active and ancient stores:
MethodDescriptionStorage Layer
Ancients()Returns the number of frozen blocksAncient store
TruncateHead(n)Removes ancient data after block nAncient store
Get(key) / Put(key, val)Key-value read/writeActive database
NewBatch()Create a batch writeActive database
Although accessed via the same DB handle, the ancient store uses a separate internal implementation optimized for sequential, immutable data.

Data Lifecycle and Migration

Block Data States

Blockchain data transitions through the following stages depending on block age and state availability:
  1. Active State
    Recent blocks that must support state (trie) access and rewinds
  2. Transitional State
    State is no longer needed, but data has not yet moved to the ancient store
  3. Frozen State
    State access is no longer required; data is considered immutable and stored in the ancient store

Freezer Threshold

The freezer threshold defines the point at which blocks are migrated to the ancient store.
Blocks below this threshold are considered ineligible for state recovery during rewinds.
Key considerations:
  • State availability
    Blocks requiring state (tries) must remain in the active database
  • TriesInMemory constant
    Typically, the most recent ~128 blocks are kept in the active DB
  • Snap synchronization
    During snap sync, the pivot block may affect the freezer boundary
  • Full synchronization
    Once state is no longer required, old blocks are migrated sequentially

Ancient Store Operations

Reading Ancient Data

When accessing old blocks, the BlockChain logic transparently reads data from the ancient store. From the perspective of higher layers, there is no need to explicitly distinguish whether data resides in the active DB or the ancient store.

Writing and Truncation

Modifications to the ancient store occur only in limited scenarios:
  1. Chain rewind (SetHead)
    When rewinding below the frozen threshold
  2. Historical data import
    When injecting past data via Era1 archives
TruncateHead removes data after a given block number from the ancient store.

Chain Rewind with Ancient Data

Rewind Process

When rewinding the chain (SetHead, SetHeadWithTimestamp),
special handling is required for frozen data.
ScenarioActive DB ActionAncient Store Action
Target above freezer thresholdDelete active blocks/headersNone
Target below freezer thresholdDelete all active blocksTruncate ancient store
Rewind to genesisReset active DBRemove all ancient data
This logic is implemented in the setHeadBeyondRoot flow.

Handling Missing State

During rewind, the system must locate a block with available state:
  1. Check HasState(root) or stateRecoverable(root)
  2. If state is missing, move to the parent block
  3. Rewinds below the snap pivot are prohibited
  4. If recoverable, attempt triedb.Recover(root)

Safety of Ancient Truncation

Ancient truncation follows strict safety rules:
  • Executed via a single TruncateHead call
  • Active DB cleanup performed first
  • Hash/number mappings of removed blocks deleted
  • Chain head markers updated atomically

Importing and Exporting Historical Data

Era1 Archive Format

go-stablenet uses the Era1 format to bundle historical blockchain data.
ComponentContentPurpose
Era Archive~8192 blocksSequential storage unit
Block DataHeader, Body, TxBlock reconstruction
ReceiptsTransaction receiptsExecution result verification
Total DifficultyCumulative difficultyChain weight calculation
Root HashEra Merkle rootIntegrity verification

Import Process

ImportHistory writes Era1 archives directly into the ancient store.
Since the active DB is bypassed, this enables fast bootstrapping.

Export Process

ExportHistory packages existing chain data into the Era1 format for external storage or distribution.

Direct Ancient Store Initialization

When attaching an external ancient store to an empty node, automatic initialization is performed at startup. This is useful for:
  • Fast initialization from a trusted archive
  • Physical separation of ancient and active data
  • Disaster recovery setups

Integration with BlockChain

BlockChain Structure Fields

type BlockChain struct {
	db ethdb.Database
	triedb *triedb.Database
	stateCache state.Database
	currentBlock atomic.Pointer[types.Header]
	currentSnapBlock atomic.Pointer[types.Header]
	bodyCache *lru.Cache[common.Hash, *types.Body]
	receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
	blockCache *lru.Cache[common.Hash, *types.Block]
}

Ancient Store Query Patterns

OperationImplementationAncient Check
Get block by numberBranch on Ancients()Yes
Header lookupActive → ancient fallbackImplicit
Chain continuity checkVerify ancient boundaryYes
State availability checkAncient blocks have no stateYes

Interaction with Snap Synchronization

  • Snap pivot block may lie at the ancient boundary
  • Receipt chains are filled via InsertReceiptChain
  • currentSnapBlock and currentBlock may diverge
  • Snap insertion below the boundary is prohibited

Maintenance Operations

Disk Space Management

Because the ancient store can occupy a large portion of disk space, block count and disk usage should be continuously monitored.

Database Corruption Recovery

Possible causes:
  • Abnormal shutdown during truncation
  • Filesystem errors
  • Manual file modification
Recovery procedure:
  1. Verify ancient count and head at startup
  2. Automatically truncate on mismatch
  3. Manually recover using SetHead if necessary

Best Practices

Operational Guidelines

  1. The ancient store can be backed up while running
  2. Consider placing it on a separate disk for I/O isolation
  3. Monitor ancient block count and disk usage separately
  4. Ancient data is immutable and removed only via truncation

Performance Considerations

AspectRecommendationRationale
Ancient locationDisk with strong sequential read performanceAppend-only access pattern
Active DBDisk with strong random I/O performanceFrequent reads/writes
MemoryMinimal cachingLow access frequency
SynchronizationFull sync fills naturallySnap requires separate receipts