diff --git a/core/blockchain.go b/core/blockchain.go index 369e9521ac..60bf532d3b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -34,6 +34,7 @@ import ( "github.com/CortexFoundation/CortexTheseus/common/mclock" "github.com/CortexFoundation/CortexTheseus/common/prque" "github.com/CortexFoundation/CortexTheseus/consensus" + "github.com/CortexFoundation/CortexTheseus/core/history" "github.com/CortexFoundation/CortexTheseus/core/rawdb" "github.com/CortexFoundation/CortexTheseus/core/state" "github.com/CortexFoundation/CortexTheseus/core/state/snapshot" @@ -153,7 +154,7 @@ type CacheConfig struct { // This defines the cutoff block for history expiry. // Blocks before this number may be unavailable in the chain database. - HistoryPruningCutoff uint64 + ChainHistoryMode history.HistoryMode } // defaultCacheConfig are the default caching values if none are specified by the @@ -222,6 +223,7 @@ type BlockChain struct { currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) currentFinalizedBlock atomic.Value // Current finalized head currentSafeBlock atomic.Value // Current safe head + historyPrunePoint atomic.Pointer[history.PrunePoint] stateCache *state.CachingDB // State database to reuse between imports (contains state cache) txIndexer *txIndexer @@ -503,6 +505,12 @@ func (bc *BlockChain) loadLastState() error { } bc.hc.SetCurrentHeader(currentHeader) + // Initialize history pruning. + latest := max(currentBlock.NumberU64(), currentHeader.Number.Uint64()) + if err := bc.initializeHistoryPruning(latest); err != nil { + return err + } + // Restore the last known head fast block bc.currentFastBlock.Store(currentBlock) headFastBlockGauge.Update(int64(currentBlock.NumberU64())) @@ -541,9 +549,57 @@ func (bc *BlockChain) loadLastState() error { if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { log.Info("Loaded last fast-sync pivot marker", "number", *pivot) } + if pruning := bc.historyPrunePoint.Load(); pruning != nil { + log.Info("Chain history is pruned", "earliest", pruning.BlockNumber, "hash", pruning.BlockHash) + } return nil } +// initializeHistoryPruning sets bc.historyPrunePoint. +func (bc *BlockChain) initializeHistoryPruning(latest uint64) error { + freezerTail, _ := bc.db.Tail() + + switch bc.cacheConfig.ChainHistoryMode { + case history.KeepAll: + if freezerTail == 0 { + return nil + } + // The database was pruned somehow, so we need to figure out if it's a known + // configuration or an error. + predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()] + if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber { + log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail) + return fmt.Errorf("unexpected database tail") + } + bc.historyPrunePoint.Store(predefinedPoint) + return nil + + case history.KeepPostMerge: + if freezerTail == 0 && latest != 0 { + // This is the case where a user is trying to run with --history.chain + // postmerge directly on an existing DB. We could just trigger the pruning + // here, but it'd be a bit dangerous since they may not have intended this + // action to happen. So just tell them how to do it. + log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cacheConfig.ChainHistoryMode.String())) + log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history.")) + return fmt.Errorf("history pruning requested via configuration") + } + predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()] + if predefinedPoint == nil { + log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash()) + return fmt.Errorf("history pruning requested for unknown network") + } else if freezerTail != predefinedPoint.BlockNumber { + log.Error("Chain history database is pruned to unknown block", "tail", freezerTail) + return fmt.Errorf("unexpected database tail") + } + bc.historyPrunePoint.Store(predefinedPoint) + return nil + + default: + return fmt.Errorf("invalid history mode: %d", bc.cacheConfig.ChainHistoryMode) + } +} + // SetHead rewinds the local chain to a new head. Depending on whether the node // was fast synced or full synced and in which state, the method will try to // delete minimal data from disk whilst retaining chain consistency. @@ -846,7 +902,9 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { bc.hc.SetCurrentHeader(bc.genesisBlock.Header()) bc.currentFastBlock.Store(bc.genesisBlock) headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) - return nil + + // Reset history pruning status. + return bc.initializeHistoryPruning(0) } // Export writes the active chain to the given writer. @@ -2325,9 +2383,3 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i _, err := bc.hc.InsertHeaderChain(chain, start) return 0, err } - -// HistoryPruningCutoff returns the configured history pruning point. -// Blocks before this might not be available in the database. -func (bc *BlockChain) HistoryPruningCutoff() uint64 { - return bc.cacheConfig.HistoryPruningCutoff -} diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index cc00230dd5..9aad5aa90c 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -458,3 +458,13 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription { return bc.scope.Track(bc.blockProcFeed.Subscribe(ch)) } + +// HistoryPruningCutoff returns the configured history pruning point. +// Blocks before this might not be available in the database. +func (bc *BlockChain) HistoryPruningCutoff() (uint64, common.Hash) { + pt := bc.historyPrunePoint.Load() + if pt == nil { + return 0, bc.genesisBlock.Hash() + } + return pt.BlockNumber, pt.BlockHash +} diff --git a/core/history/historymode.go b/core/history/historymode.go new file mode 100644 index 0000000000..a033566537 --- /dev/null +++ b/core/history/historymode.go @@ -0,0 +1,93 @@ +// Copyright 2025 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 history + +import ( + "fmt" + + "github.com/CortexFoundation/CortexTheseus/common" + "github.com/CortexFoundation/CortexTheseus/params" +) + +// HistoryMode configures history pruning. +type HistoryMode uint32 + +const ( + // KeepAll (default) means that all chain history down to genesis block will be kept. + KeepAll HistoryMode = iota + + // KeepPostMerge sets the history pruning point to the merge activation block. + KeepPostMerge +) + +func (m HistoryMode) IsValid() bool { + return m <= KeepPostMerge +} + +func (m HistoryMode) String() string { + switch m { + case KeepAll: + return "all" + case KeepPostMerge: + return "postmerge" + default: + return fmt.Sprintf("invalid HistoryMode(%d)", m) + } +} + +// MarshalText implements encoding.TextMarshaler. +func (m HistoryMode) MarshalText() ([]byte, error) { + if m.IsValid() { + return []byte(m.String()), nil + } + return nil, fmt.Errorf("unknown history mode %d", m) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (m *HistoryMode) UnmarshalText(text []byte) error { + switch string(text) { + case "all": + *m = KeepAll + case "postmerge": + *m = KeepPostMerge + default: + return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text) + } + return nil +} + +type PrunePoint struct { + BlockNumber uint64 + BlockHash common.Hash +} + +// PrunePoints the pre-defined history pruning cutoff blocks for known networks. +// They point to the first post-merge block. Any pruning should truncate *up to* but excluding +// given block. +var PrunePoints = map[common.Hash]*PrunePoint{ + // mainnet + params.MainnetGenesisHash: { + BlockNumber: 15537393, + BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"), + }, +} + +// PrunedHistoryError is returned by APIs when the requested history is pruned. +type PrunedHistoryError struct{} + +func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" } +func (e *PrunedHistoryError) ErrorCode() int { return 4444 } diff --git a/core/txindexer.go b/core/txindexer.go index 37aaa4d05a..7411ff9617 100644 --- a/core/txindexer.go +++ b/core/txindexer.go @@ -58,9 +58,10 @@ type txIndexer struct { // newTxIndexer initializes the transaction indexer. func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer { + cutoff, _ := chain.HistoryPruningCutoff() indexer := &txIndexer{ limit: limit, - cutoff: chain.HistoryPruningCutoff(), + cutoff: cutoff, db: chain.db, progress: make(chan chan TxIndexProgress), term: make(chan chan struct{}), diff --git a/ctxc/backend.go b/ctxc/backend.go index 65d74190da..67aeec01f5 100644 --- a/ctxc/backend.go +++ b/ctxc/backend.go @@ -217,6 +217,7 @@ func New(stack *node.Node, config *Config) (*Cortex, error) { //StateHistory: config.StateHistory, //StateScheme: scheme, //HistoryPruningCutoff: historyPruningCutoff, + ChainHistoryMode: config.HistoryMode, } ) ctxc.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, ctxc.chainConfig, ctxc.engine, vmConfig, ctxc.shouldPreserve, &config.TxLookupLimit) @@ -233,7 +234,7 @@ func New(stack *node.Node, config *Config) (*Cortex, error) { } chainView := ctxc.newChainView(ctxc.blockchain.CurrentBlock().Header()) - historyCutoff := ctxc.blockchain.HistoryPruningCutoff() + historyCutoff, _ := ctxc.blockchain.HistoryPruningCutoff() var finalBlock uint64 if fb := ctxc.blockchain.CurrentFinalizedBlock(); fb != nil { finalBlock = fb.Header().Number.Uint64() @@ -667,7 +668,7 @@ func (s *Cortex) updateFilterMapsHeads() { if head == nil || newHead.Hash() != head.Hash() { head = newHead chainView := s.newChainView(head) - historyCutoff := s.blockchain.HistoryPruningCutoff() + historyCutoff, _ := s.blockchain.HistoryPruningCutoff() var finalBlock uint64 if fb := s.blockchain.CurrentFinalizedBlock(); fb != nil { finalBlock = fb.Header().Number.Uint64() diff --git a/ctxc/config.go b/ctxc/config.go index bef57bb15b..c3eb312d5b 100644 --- a/ctxc/config.go +++ b/ctxc/config.go @@ -25,6 +25,7 @@ import ( "github.com/CortexFoundation/CortexTheseus/common" "github.com/CortexFoundation/CortexTheseus/consensus/cuckoo" "github.com/CortexFoundation/CortexTheseus/core" + "github.com/CortexFoundation/CortexTheseus/core/history" "github.com/CortexFoundation/CortexTheseus/core/txpool" "github.com/CortexFoundation/CortexTheseus/ctxc/downloader" "github.com/CortexFoundation/CortexTheseus/ctxc/gasprice" @@ -48,6 +49,7 @@ var DefaultLightGPOConfig = gasprice.Config{ // DefaultConfig contains default settings for use on the Cortex main net. var DefaultConfig = Config{ + HistoryMode: history.KeepAll, SyncMode: downloader.FullSync, Cuckoo: cuckoo.Config{}, NetworkId: 0, @@ -87,8 +89,10 @@ type Config struct { Genesis *core.Genesis `toml:",omitempty"` // Protocol options - NetworkId uint64 // Network ID to use for selecting peers to connect to - SyncMode downloader.SyncMode + NetworkId uint64 // Network ID to use for selecting peers to connect to + SyncMode downloader.SyncMode + // HistoryMode configures chain history retention. + HistoryMode history.HistoryMode DiscoveryURLs []string NoPruning bool NoPrefetch bool // Whether to disable prefetching and only load state on demand diff --git a/ctxc/gen_config.go b/ctxc/gen_config.go index 3298b4f8e8..822d7af6c9 100644 --- a/ctxc/gen_config.go +++ b/ctxc/gen_config.go @@ -8,6 +8,7 @@ import ( "github.com/CortexFoundation/CortexTheseus/common" "github.com/CortexFoundation/CortexTheseus/consensus/cuckoo" "github.com/CortexFoundation/CortexTheseus/core" + "github.com/CortexFoundation/CortexTheseus/core/history" "github.com/CortexFoundation/CortexTheseus/core/txpool" "github.com/CortexFoundation/CortexTheseus/ctxc/downloader" "github.com/CortexFoundation/CortexTheseus/ctxc/gasprice" @@ -21,10 +22,16 @@ func (c Config) MarshalTOML() (interface{}, error) { Genesis *core.Genesis `toml:",omitempty"` NetworkId uint64 SyncMode downloader.SyncMode + HistoryMode history.HistoryMode DiscoveryURLs []string NoPruning bool NoPrefetch bool - TxLookupLimit uint64 `toml:",omitempty"` + TxLookupLimit uint64 `toml:",omitempty"` + TransactionHistory uint64 `toml:",omitempty"` + LogHistory uint64 `toml:",omitempty"` + LogNoHistory bool `toml:",omitempty"` + LogExportCheckpoints string + StateHistory uint64 `toml:",omitempty"` Whitelist map[uint64]common.Hash `toml:"-"` SkipBcVersionCheck bool `toml:"-"` DatabaseHandles int `toml:"-"` @@ -61,10 +68,16 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.Genesis = c.Genesis enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode + enc.HistoryMode = c.HistoryMode enc.DiscoveryURLs = c.DiscoveryURLs enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.TxLookupLimit = c.TxLookupLimit + enc.TransactionHistory = c.TransactionHistory + enc.LogHistory = c.LogHistory + enc.LogNoHistory = c.LogNoHistory + enc.LogExportCheckpoints = c.LogExportCheckpoints + enc.StateHistory = c.StateHistory enc.Whitelist = c.Whitelist enc.SkipBcVersionCheck = c.SkipBcVersionCheck enc.DatabaseHandles = c.DatabaseHandles @@ -105,10 +118,16 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { Genesis *core.Genesis `toml:",omitempty"` NetworkId *uint64 SyncMode *downloader.SyncMode + HistoryMode *history.HistoryMode DiscoveryURLs []string NoPruning *bool NoPrefetch *bool - TxLookupLimit *uint64 `toml:",omitempty"` + TxLookupLimit *uint64 `toml:",omitempty"` + TransactionHistory *uint64 `toml:",omitempty"` + LogHistory *uint64 `toml:",omitempty"` + LogNoHistory *bool `toml:",omitempty"` + LogExportCheckpoints *string + StateHistory *uint64 `toml:",omitempty"` Whitelist map[uint64]common.Hash `toml:"-"` SkipBcVersionCheck *bool `toml:"-"` DatabaseHandles *int `toml:"-"` @@ -154,6 +173,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SyncMode != nil { c.SyncMode = *dec.SyncMode } + if dec.HistoryMode != nil { + c.HistoryMode = *dec.HistoryMode + } if dec.DiscoveryURLs != nil { c.DiscoveryURLs = dec.DiscoveryURLs } @@ -166,6 +188,21 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TxLookupLimit != nil { c.TxLookupLimit = *dec.TxLookupLimit } + if dec.TransactionHistory != nil { + c.TransactionHistory = *dec.TransactionHistory + } + if dec.LogHistory != nil { + c.LogHistory = *dec.LogHistory + } + if dec.LogNoHistory != nil { + c.LogNoHistory = *dec.LogNoHistory + } + if dec.LogExportCheckpoints != nil { + c.LogExportCheckpoints = *dec.LogExportCheckpoints + } + if dec.StateHistory != nil { + c.StateHistory = *dec.StateHistory + } if dec.Whitelist != nil { c.Whitelist = dec.Whitelist } diff --git a/go.mod b/go.mod index 0d395cad77..4e7cb84731 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 github.com/CortexFoundation/inference v1.0.2-0.20230307032835-9197d586a4e8 github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66 - github.com/CortexFoundation/torrentfs v1.0.69-0.20250415112856-b1b452a24746 + github.com/CortexFoundation/torrentfs v1.0.69-0.20250415134051-0b782fe8b66d github.com/VictoriaMetrics/fastcache v1.12.2 github.com/arsham/figurine v1.3.0 github.com/aws/aws-sdk-go-v2 v1.36.3 diff --git a/go.sum b/go.sum index e0d9de77d2..51726ce023 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66/go.mod h1: github.com/CortexFoundation/torrentfs v1.0.13-0.20200623060705-ce027f43f2f8/go.mod h1:Ma+tGhPPvz4CEZHaqEJQMOEGOfHeQBiAoNd1zyc/w3Q= github.com/CortexFoundation/torrentfs v1.0.14-0.20200703071639-3fcabcabf274/go.mod h1:qnb3YlIJmuetVBtC6Lsejr0Xru+1DNmDCdTqnwy7lhk= github.com/CortexFoundation/torrentfs v1.0.20-0.20200810031954-d36d26f82fcc/go.mod h1:N5BsicP5ynjXIi/Npl/SRzlJ630n1PJV2sRj0Z0t2HA= -github.com/CortexFoundation/torrentfs v1.0.69-0.20250415112856-b1b452a24746 h1:VP6BvIxVY1jh4UmvpJQqIzpmnYva25xVRevXHoPP/2M= -github.com/CortexFoundation/torrentfs v1.0.69-0.20250415112856-b1b452a24746/go.mod h1:Lf7iaFiGPvExQiyPufsbQ8hs0XkKUunTLcp0r77/IaY= +github.com/CortexFoundation/torrentfs v1.0.69-0.20250415134051-0b782fe8b66d h1:oZk98KwRFZJHPhztM2yGwHobSa0wls6gdJXHRbSTdPs= +github.com/CortexFoundation/torrentfs v1.0.69-0.20250415134051-0b782fe8b66d/go.mod h1:Lf7iaFiGPvExQiyPufsbQ8hs0XkKUunTLcp0r77/IaY= github.com/CortexFoundation/wormhole v0.0.2-0.20241128010855-a23c88842cfa h1:46VAGWxOwpoLlPNcR9etAhK0NtT215skO9Wl4i14r4o= github.com/CortexFoundation/wormhole v0.0.2-0.20241128010855-a23c88842cfa/go.mod h1:ipzmPabDgzYKUbXkGVe2gTkBEp+MsDx6pXGiuYzmP6s= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= diff --git a/vendor/modules.txt b/vendor/modules.txt index 946b131cc5..56780b71d5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -61,7 +61,7 @@ github.com/CortexFoundation/robot/backend # github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66 ## explicit; go 1.16 github.com/CortexFoundation/statik -# github.com/CortexFoundation/torrentfs v1.0.69-0.20250415112856-b1b452a24746 +# github.com/CortexFoundation/torrentfs v1.0.69-0.20250415134051-0b782fe8b66d ## explicit; go 1.23.4 github.com/CortexFoundation/torrentfs github.com/CortexFoundation/torrentfs/backend