Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/geth/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,13 @@ func dbHasLegacyReceipts(db ethdb.Database, firstIdx uint64) (bool, uint64, erro
if numAncients < 1 {
return false, 0, nil
}
// If offline block-pruning has advanced the freezer tail past firstIdx,
// scanning from position 0 will fail with "out of bounds" on the first
// Ancient() call. Clamp firstIdx to the current tail so we scan only
// the accessible range.
if tail, terr := db.Tail(); terr == nil && tail > firstIdx {
firstIdx = tail
}
if firstIdx >= numAncients {
return false, firstIdx, nil
}
Expand Down
24 changes: 16 additions & 8 deletions core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,22 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
// it to the freezer content.
if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 {
if frozen, _ := frdb.Ancients(); frozen > 0 {
// If the freezer already contains something, ensure that the genesis blocks
// match, otherwise we might mix up freezers across chains and destroy both
// the freezer and the key-value store.
frgenesis, err := frdb.Ancient(chainFreezerHashTable, 0)
if err != nil {
return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err)
} else if !bytes.Equal(kvgenesis, frgenesis) {
return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis)
// If offline block-pruning has advanced the freezer tail past
// block 0, the ancient store can no longer produce genesis;
// the kv store is the sole source of truth in that case and
// snapshot prune-block copied genesis there before truncating.
// Skip the ancient cross-check when we detect a non-zero tail.
frtail, _ := frdb.Tail()
if frtail == 0 {
// Genesis still lives in ancient; cross-validate the genesis
// blocks match, otherwise we might mix up freezers across
// chains and destroy both the freezer and the key-value store.
frgenesis, err := frdb.Ancient(chainFreezerHashTable, 0)
if err != nil {
return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err)
} else if !bytes.Equal(kvgenesis, frgenesis) {
return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis)
}
}
// Key-value store and freezer belong to the same network. Ensure that they
// are contiguous, otherwise we might end up with a non-functional freezer.
Expand Down
44 changes: 44 additions & 0 deletions core/state/pruner/block_pruner.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,40 @@ func (p *BlockPruner) Prune() error {
"blocksToDelete", newTail-oldTail,
"amountReserved", p.amountReserved)

// Preserve the genesis block in the kv store before truncating the
// ancient tail. The freezer's TruncateTail(newTail) hides every item
// numbered below newTail, including block 0. After such truncation,
// geth refuses to start with:
//
// Fatal: Failed to register the Ethereum service:
// failed to retrieve genesis from ancient out of bounds
//
// because the startup code reads genesis via rawdb.ReadBlock(hash, 0)
// and the ancient lookup returns nothing once tail > 0. The kv-store
// path is the natural fallback for rawdb readers, so we copy genesis
// (canonical hash, header, body, total difficulty) back into the kv
// tables. Receipts are omitted because the genesis block has no
// transactions.
//
// This read must happen before TruncateTail: afterwards, the ancient
// lookup for block 0 will fail and we would be writing zeroes.
genesisHash := rawdb.ReadCanonicalHash(p.db, 0)
if genesisHash == (common.Hash{}) {
return errors.New("failed to read genesis canonical hash; refusing to truncate")
}
genesisHeader := rawdb.ReadHeader(p.db, genesisHash, 0)
if genesisHeader == nil {
return fmt.Errorf("failed to read genesis header for %s; refusing to truncate", genesisHash.Hex())
}
genesisBody := rawdb.ReadBody(p.db, genesisHash, 0)
if genesisBody == nil {
return fmt.Errorf("failed to read genesis body for %s; refusing to truncate", genesisHash.Hex())
}
genesisTd := rawdb.ReadTd(p.db, genesisHash, 0)
if genesisTd == nil {
return fmt.Errorf("failed to read genesis total difficulty for %s; refusing to truncate", genesisHash.Hex())
}

// Perform the in-place tail truncation on the ancient store. This is a
// local operation that drops data files on disk once the truncated range
// spans an entire file (2 GiB per file by default), and hides partial
Expand All @@ -156,6 +190,16 @@ func (p *BlockPruner) Prune() error {
}
log.Info("Ancient tail truncated", "newTail", newTail, "blocksDeleted", newTail-oldTail)

// Write genesis back to the kv store. This is idempotent; repeated
// invocations with the same genesis data simply overwrite the same
// keys. Placement matters: it must happen after TruncateTail so that
// genesis survives as the only block below newTail.
rawdb.WriteCanonicalHash(p.db, genesisHash, 0)
rawdb.WriteHeader(p.db, genesisHeader)
rawdb.WriteBody(p.db, genesisHash, 0, genesisBody)
rawdb.WriteTd(p.db, genesisHash, 0, genesisTd)
log.Info("Genesis block preserved in kv store", "hash", genesisHash.Hex())

// Make sure the transaction index tail is not pointing below the new
// ancient tail. Otherwise, when the node starts with --txlookuplimit,
// the background indexer would try to walk pruned block bodies and
Expand Down
4 changes: 2 additions & 2 deletions params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 4 // Minor version component of the current release
VersionPatch = 8 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string
VersionPatch = 9 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string
)

// Version holds the textual version string.
Expand Down
Loading