mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 07:06:42 +00:00
cmd/geth: add prune history command
This commit is contained in:
parent
1886922264
commit
a48789a7bf
3 changed files with 101 additions and 8 deletions
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"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).
|
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
|
// 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)
|
_, err := strconv.Atoi(x)
|
||||||
return err != nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ func init() {
|
||||||
removedbCommand,
|
removedbCommand,
|
||||||
dumpCommand,
|
dumpCommand,
|
||||||
dumpGenesisCommand,
|
dumpGenesisCommand,
|
||||||
|
pruneCommand,
|
||||||
// See accountcmd.go:
|
// See accountcmd.go:
|
||||||
accountCommand,
|
accountCommand,
|
||||||
walletCommand,
|
walletCommand,
|
||||||
|
|
|
||||||
|
|
@ -222,16 +222,25 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
|
||||||
// it to the freezer content.
|
// it to the freezer content.
|
||||||
if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 {
|
if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 {
|
||||||
if frozen, _ := frdb.Ancients(); frozen > 0 {
|
if frozen, _ := frdb.Ancients(); frozen > 0 {
|
||||||
// If the freezer already contains something, ensure that the genesis blocks
|
tail, err := frdb.Tail()
|
||||||
// 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 {
|
if err != nil {
|
||||||
printChainMetadata(db)
|
printChainMetadata(db)
|
||||||
return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err)
|
return nil, fmt.Errorf("failed to retrieve tail from freezer %v", err)
|
||||||
} else if !bytes.Equal(kvgenesis, frgenesis) {
|
}
|
||||||
printChainMetadata(db)
|
// If tail > 0, the history has been pruned and genesis block is not anymore in the freezer.
|
||||||
return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis)
|
// 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.
|
||||||
|
frgenesis, err := frdb.Ancient(ChainFreezerHashTable, 0)
|
||||||
|
if err != nil {
|
||||||
|
printChainMetadata(db)
|
||||||
|
return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err)
|
||||||
|
} else if !bytes.Equal(kvgenesis, frgenesis) {
|
||||||
|
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
|
// 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.
|
// are contiguous, otherwise we might end up with a non-functional freezer.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue