diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index bbadb1cc1928..072530301957 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -78,7 +78,9 @@ if one is set. Otherwise it prints the genesis from the datadir.`, Flags: slices.Concat([]cli.Flag{ utils.CacheFlag, utils.SyncModeFlag, + utils.StateSchemeFlag, utils.GCModeFlag, + utils.HistoryModeFlag, utils.SnapshotFlag, utils.CacheDatabaseFlag, utils.CacheGCFlag, diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 9d9256862b63..503f308158a2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -82,7 +82,9 @@ var ( utils.SyncModeFlag, utils.SyncTargetFlag, utils.ExitWhenSyncedFlag, + utils.StateSchemeFlag, utils.GCModeFlag, + utils.HistoryModeFlag, utils.SnapshotFlag, utils.TxLookupLimitFlag, // deprecated utils.TransactionHistoryFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 728ec2d667c1..cf814c8851e9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -255,6 +255,12 @@ var ( Value: "full", Category: flags.StateCategory, } + HistoryModeFlag = &cli.StringFlag{ + Name: "historymode", + Usage: `Blockchain history pruning mode ("full" keeps all history, "pruned" keeps only recent history according to StateHistory and TransactionHistory settings)`, + Value: "full", + Category: flags.StateCategory, + } StateSchemeFlag = &cli.StringFlag{ Name: "state.scheme", Usage: "Scheme to use for storing ethereum state ('hash' or 'path')", @@ -1587,6 +1593,34 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(GCModeFlag.Name) { cfg.NoPruning = ctx.String(GCModeFlag.Name) == "archive" } + if historymode := ctx.String(HistoryModeFlag.Name); historymode != "full" && historymode != "pruned" { + Fatalf("--%s must be either 'full' or 'pruned'", HistoryModeFlag.Name) + } + if ctx.IsSet(HistoryModeFlag.Name) { + if ctx.String(HistoryModeFlag.Name) == "full" { + cfg.HistoryMode = ethconfig.AllHistory + } else { + cfg.HistoryMode = ethconfig.PrunedHistory + } + } else { + // Set the default value if not specified + // Future versions of Geth will default to pruned, but for now we use full + cfg.HistoryMode = ethconfig.AllHistory + } + + // If gcmode is set to archive, ensure historymode is set to full + if ctx.IsSet(GCModeFlag.Name) && ctx.String(GCModeFlag.Name) == "archive" { + cfg.HistoryMode = ethconfig.AllHistory + log.Info("Setting historymode to 'full' because gcmode is 'archive'") + } + + // Conversely, if historymode is set to pruned, gcmode can't be archive + if ctx.IsSet(HistoryModeFlag.Name) && ctx.String(HistoryModeFlag.Name) == "pruned" && ctx.String(GCModeFlag.Name) == "archive" { + log.Warn("Conflicting flags: historymode=pruned and gcmode=archive cannot be used together") + log.Warn("Setting gcmode to 'full' to be compatible with historymode=pruned") + cfg.NoPruning = false + } + if ctx.IsSet(CacheNoPrefetchFlag.Name) { cfg.NoPrefetch = ctx.Bool(CacheNoPrefetchFlag.Name) } diff --git a/eth/backend.go b/eth/backend.go index 4c0fd9e68b2c..d81afbdcdb53 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -197,10 +197,25 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { TrieTimeLimit: config.TrieTimeout, SnapshotLimit: config.SnapshotCache, Preimages: config.Preimages, - StateHistory: config.StateHistory, StateScheme: scheme, } ) + + // Configure state history based on history mode + if config.HistoryMode == ethconfig.AllHistory { + // In all history mode, retain all state history if specified as 0, + // otherwise use the configured value + cacheConfig.StateHistory = config.StateHistory + } else if config.HistoryMode == ethconfig.PrunedHistory { + // In pruned history mode, ensure state history is limited + if config.StateHistory == 0 { + // If it's set to 0 (unlimited), use a default value for pruned mode + cacheConfig.StateHistory = 2350000 // Same default as TransactionHistory + } else { + cacheConfig.StateHistory = config.StateHistory + } + } + if config.VMTrace != "" { traceConfig := json.RawMessage("{}") if config.VMTraceJsonConfig != "" { @@ -220,7 +235,24 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideVerkle != nil { overrides.OverrideVerkle = config.OverrideVerkle } - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, &config.TransactionHistory) + + // Set transaction history based on history mode + var txHistoryLimit *uint64 + if config.HistoryMode == ethconfig.AllHistory { + // In all history mode, use the configured transaction history + txHistoryLimit = &config.TransactionHistory + } else if config.HistoryMode == ethconfig.PrunedHistory { + // In pruned history mode, ensure transaction history is limited + // Make a copy to avoid modifying the original config + limit := config.TransactionHistory + if limit == 0 { + // If it's set to 0 (unlimited), use a reasonable default for pruned mode + limit = 2350000 // Default value in ethconfig.Defaults + } + txHistoryLimit = &limit + } + + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, txHistoryLimit) if err != nil { return nil, err } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 6b75ab816fa6..cd9181e5cd60 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -49,6 +49,7 @@ var FullNodeGPO = gasprice.Config{ // Defaults contains default settings for use on the Ethereum main net. var Defaults = Config{ SyncMode: SnapSync, + HistoryMode: AllHistory, NetworkId: 0, // enable auto configuration of networkID == chainID TxLookupLimit: 2350000, TransactionHistory: 2350000, @@ -80,6 +81,8 @@ type Config struct { // zero, the chain ID is used as network ID. NetworkId uint64 SyncMode SyncMode + // HistoryMode defines the pruning mode for historical blockchain data + HistoryMode HistoryMode // This can be set to list of enrtree:// URLs which will be queried for // nodes to connect to. diff --git a/eth/ethconfig/historymode.go b/eth/ethconfig/historymode.go new file mode 100644 index 000000000000..4bc40e04fd69 --- /dev/null +++ b/eth/ethconfig/historymode.go @@ -0,0 +1,66 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethconfig + +import "fmt" + +// HistoryMode represents the blockchain history mode for pruning. +type HistoryMode uint32 + +const ( + AllHistory HistoryMode = iota // Keep all history + PrunedHistory // Prune history beyond StateHistory and TransactionHistory +) + +func (mode HistoryMode) IsValid() bool { + return mode == AllHistory || mode == PrunedHistory +} + +// String implements the stringer interface. +func (mode HistoryMode) String() string { + switch mode { + case AllHistory: + return "full" + case PrunedHistory: + return "pruned" + default: + return "unknown" + } +} + +func (mode HistoryMode) MarshalText() ([]byte, error) { + switch mode { + case AllHistory: + return []byte("full"), nil + case PrunedHistory: + return []byte("pruned"), nil + default: + return nil, fmt.Errorf("unknown history mode %d", mode) + } +} + +func (mode *HistoryMode) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *mode = AllHistory + case "pruned": + *mode = PrunedHistory + default: + return fmt.Errorf(`unknown history mode %q, want "full" or "pruned"`, text) + } + return nil +}