cmd/geth: add prune history command

This commit is contained in:
Sina Mahmoodi 2024-12-19 09:10:35 +01:00 committed by Felix Lange
parent 1886922264
commit a48789a7bf
3 changed files with 101 additions and 8 deletions

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/log"
@ -189,6 +190,18 @@ It's deprecated, please use "geth db import" instead.
This command dumps out the state for a given block (or latest, if none provided).
`,
}
pruneCommand = &cli.Command{
Action: pruneHistory,
Name: "prune-history",
Usage: "Prune blockchain history (block bodies and receipts) up to the merge block",
ArgsUsage: "",
Flags: utils.DatabaseFlags,
Description: `
The prune-history command removes historical block bodies and receipts from the
blockchain database up to the merge block, while preserving block headers. This
helps reduce storage requirements for nodes that don't need full historical data.`,
}
)
// initGenesis will initialise the given JSON format genesis file and writes it as
@ -598,3 +611,73 @@ func hashish(x string) bool {
_, err := strconv.Atoi(x)
return err != nil
}
func pruneHistory(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
// Open the chain database
chain, chaindb := utils.MakeChain(ctx, stack, false)
defer chaindb.Close()
defer chain.Stop()
// Pruning only supported for mainnet and sepolia.
if chain.Config().ChainID.Cmp(params.MainnetChainConfig.ChainID) != 0 && chain.Config().ChainID.Cmp(params.SepoliaChainConfig.ChainID) != 0 {
log.Info("Chain pruning not supported for this network")
return nil
}
// Determine the prune point. This will be the first PoS block.
prunePoint, ok := ethconfig.HistoryPrunePoints[chain.Genesis().Hash()]
if !ok || prunePoint == nil {
return errors.New("prune point not found")
}
var (
mergeBlock = prunePoint.BlockNumber
mergeBlockHash = prunePoint.BlockHash.Hex()
)
// Check we're far enough past merge to ensure all data is in freezer
currentHeader := chain.CurrentHeader()
if currentHeader == nil {
return errors.New("current header not found")
}
if currentHeader.Number.Uint64() < mergeBlock+params.FullImmutabilityThreshold {
return fmt.Errorf("chain not far enough past merge block, need %d more blocks",
mergeBlock+params.FullImmutabilityThreshold-currentHeader.Number.Uint64())
}
// Double-check the prune block in db has the expected hash.
hash := rawdb.ReadCanonicalHash(chaindb, mergeBlock)
if hash != common.HexToHash(mergeBlockHash) {
return fmt.Errorf("merge block hash mismatch: got %s, want %s", hash.Hex(), mergeBlockHash)
}
txlookupTail := rawdb.ReadTxIndexTail(chaindb)
log.Info("Starting chain pruning",
"currentHeight", currentHeader.Number,
"mergeBlock", mergeBlock,
"mergeBlockHash", mergeBlockHash)
start := time.Now()
// First prune the transaction lookup index as
// it requires the block bodies.
if txlookupTail != nil && *txlookupTail < mergeBlock {
rawdb.UnindexTransactions(chaindb, *txlookupTail, mergeBlock, nil, false)
}
// TODO(s1na): what if there is a crash between the two prune operations?
// Truncate everything up to merge block
if _, err := chaindb.TruncateTail(mergeBlock); err != nil {
return fmt.Errorf("failed to truncate ancient data: %v", err)
}
log.Info("Chain pruning completed",
"prunedUpTo", mergeBlock,
"elapsed", common.PrettyDuration(time.Since(start)))
return nil
}

View file

@ -226,6 +226,7 @@ func init() {
removedbCommand,
dumpCommand,
dumpGenesisCommand,
pruneCommand,
// See accountcmd.go:
accountCommand,
walletCommand,

View file

@ -222,6 +222,14 @@ 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 {
tail, err := frdb.Tail()
if err != nil {
printChainMetadata(db)
return nil, fmt.Errorf("failed to retrieve tail from freezer %v", err)
}
// If tail > 0, the history has been pruned and genesis block is not anymore in the freezer.
// TODO: we need another way to verify the network for kvdb and freezer match.
if tail == 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.
@ -233,6 +241,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
printChainMetadata(db)
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.
if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 {