Skip to content

Commit 4ff1266

Browse files
authored
Merge pull request #2328 from CortexFoundation/dev
initialize history pruning in BlockChain
2 parents 40bcc8e + 19d2aad commit 4ff1266

10 files changed

Lines changed: 217 additions & 19 deletions

File tree

core/blockchain.go

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/CortexFoundation/CortexTheseus/common/mclock"
3535
"github.com/CortexFoundation/CortexTheseus/common/prque"
3636
"github.com/CortexFoundation/CortexTheseus/consensus"
37+
"github.com/CortexFoundation/CortexTheseus/core/history"
3738
"github.com/CortexFoundation/CortexTheseus/core/rawdb"
3839
"github.com/CortexFoundation/CortexTheseus/core/state"
3940
"github.com/CortexFoundation/CortexTheseus/core/state/snapshot"
@@ -153,7 +154,7 @@ type CacheConfig struct {
153154

154155
// This defines the cutoff block for history expiry.
155156
// Blocks before this number may be unavailable in the chain database.
156-
HistoryPruningCutoff uint64
157+
ChainHistoryMode history.HistoryMode
157158
}
158159

159160
// defaultCacheConfig are the default caching values if none are specified by the
@@ -222,6 +223,7 @@ type BlockChain struct {
222223
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
223224
currentFinalizedBlock atomic.Value // Current finalized head
224225
currentSafeBlock atomic.Value // Current safe head
226+
historyPrunePoint atomic.Pointer[history.PrunePoint]
225227

226228
stateCache *state.CachingDB // State database to reuse between imports (contains state cache)
227229
txIndexer *txIndexer
@@ -503,6 +505,12 @@ func (bc *BlockChain) loadLastState() error {
503505
}
504506
bc.hc.SetCurrentHeader(currentHeader)
505507

508+
// Initialize history pruning.
509+
latest := max(currentBlock.NumberU64(), currentHeader.Number.Uint64())
510+
if err := bc.initializeHistoryPruning(latest); err != nil {
511+
return err
512+
}
513+
506514
// Restore the last known head fast block
507515
bc.currentFastBlock.Store(currentBlock)
508516
headFastBlockGauge.Update(int64(currentBlock.NumberU64()))
@@ -541,9 +549,57 @@ func (bc *BlockChain) loadLastState() error {
541549
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
542550
log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
543551
}
552+
if pruning := bc.historyPrunePoint.Load(); pruning != nil {
553+
log.Info("Chain history is pruned", "earliest", pruning.BlockNumber, "hash", pruning.BlockHash)
554+
}
544555
return nil
545556
}
546557

558+
// initializeHistoryPruning sets bc.historyPrunePoint.
559+
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
560+
freezerTail, _ := bc.db.Tail()
561+
562+
switch bc.cacheConfig.ChainHistoryMode {
563+
case history.KeepAll:
564+
if freezerTail == 0 {
565+
return nil
566+
}
567+
// The database was pruned somehow, so we need to figure out if it's a known
568+
// configuration or an error.
569+
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
570+
if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber {
571+
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
572+
return fmt.Errorf("unexpected database tail")
573+
}
574+
bc.historyPrunePoint.Store(predefinedPoint)
575+
return nil
576+
577+
case history.KeepPostMerge:
578+
if freezerTail == 0 && latest != 0 {
579+
// This is the case where a user is trying to run with --history.chain
580+
// postmerge directly on an existing DB. We could just trigger the pruning
581+
// here, but it'd be a bit dangerous since they may not have intended this
582+
// action to happen. So just tell them how to do it.
583+
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cacheConfig.ChainHistoryMode.String()))
584+
log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history."))
585+
return fmt.Errorf("history pruning requested via configuration")
586+
}
587+
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
588+
if predefinedPoint == nil {
589+
log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash())
590+
return fmt.Errorf("history pruning requested for unknown network")
591+
} else if freezerTail != predefinedPoint.BlockNumber {
592+
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
593+
return fmt.Errorf("unexpected database tail")
594+
}
595+
bc.historyPrunePoint.Store(predefinedPoint)
596+
return nil
597+
598+
default:
599+
return fmt.Errorf("invalid history mode: %d", bc.cacheConfig.ChainHistoryMode)
600+
}
601+
}
602+
547603
// SetHead rewinds the local chain to a new head. Depending on whether the node
548604
// was fast synced or full synced and in which state, the method will try to
549605
// delete minimal data from disk whilst retaining chain consistency.
@@ -846,7 +902,9 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
846902
bc.hc.SetCurrentHeader(bc.genesisBlock.Header())
847903
bc.currentFastBlock.Store(bc.genesisBlock)
848904
headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64()))
849-
return nil
905+
906+
// Reset history pruning status.
907+
return bc.initializeHistoryPruning(0)
850908
}
851909

852910
// Export writes the active chain to the given writer.
@@ -2325,9 +2383,3 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
23252383
_, err := bc.hc.InsertHeaderChain(chain, start)
23262384
return 0, err
23272385
}
2328-
2329-
// HistoryPruningCutoff returns the configured history pruning point.
2330-
// Blocks before this might not be available in the database.
2331-
func (bc *BlockChain) HistoryPruningCutoff() uint64 {
2332-
return bc.cacheConfig.HistoryPruningCutoff
2333-
}

core/blockchain_reader.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,13 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
458458
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
459459
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
460460
}
461+
462+
// HistoryPruningCutoff returns the configured history pruning point.
463+
// Blocks before this might not be available in the database.
464+
func (bc *BlockChain) HistoryPruningCutoff() (uint64, common.Hash) {
465+
pt := bc.historyPrunePoint.Load()
466+
if pt == nil {
467+
return 0, bc.genesisBlock.Hash()
468+
}
469+
return pt.BlockNumber, pt.BlockHash
470+
}

core/history/historymode.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package history
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/CortexFoundation/CortexTheseus/common"
23+
"github.com/CortexFoundation/CortexTheseus/params"
24+
)
25+
26+
// HistoryMode configures history pruning.
27+
type HistoryMode uint32
28+
29+
const (
30+
// KeepAll (default) means that all chain history down to genesis block will be kept.
31+
KeepAll HistoryMode = iota
32+
33+
// KeepPostMerge sets the history pruning point to the merge activation block.
34+
KeepPostMerge
35+
)
36+
37+
func (m HistoryMode) IsValid() bool {
38+
return m <= KeepPostMerge
39+
}
40+
41+
func (m HistoryMode) String() string {
42+
switch m {
43+
case KeepAll:
44+
return "all"
45+
case KeepPostMerge:
46+
return "postmerge"
47+
default:
48+
return fmt.Sprintf("invalid HistoryMode(%d)", m)
49+
}
50+
}
51+
52+
// MarshalText implements encoding.TextMarshaler.
53+
func (m HistoryMode) MarshalText() ([]byte, error) {
54+
if m.IsValid() {
55+
return []byte(m.String()), nil
56+
}
57+
return nil, fmt.Errorf("unknown history mode %d", m)
58+
}
59+
60+
// UnmarshalText implements encoding.TextUnmarshaler.
61+
func (m *HistoryMode) UnmarshalText(text []byte) error {
62+
switch string(text) {
63+
case "all":
64+
*m = KeepAll
65+
case "postmerge":
66+
*m = KeepPostMerge
67+
default:
68+
return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text)
69+
}
70+
return nil
71+
}
72+
73+
type PrunePoint struct {
74+
BlockNumber uint64
75+
BlockHash common.Hash
76+
}
77+
78+
// PrunePoints the pre-defined history pruning cutoff blocks for known networks.
79+
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding
80+
// given block.
81+
var PrunePoints = map[common.Hash]*PrunePoint{
82+
// mainnet
83+
params.MainnetGenesisHash: {
84+
BlockNumber: 15537393,
85+
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
86+
},
87+
}
88+
89+
// PrunedHistoryError is returned by APIs when the requested history is pruned.
90+
type PrunedHistoryError struct{}
91+
92+
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }
93+
func (e *PrunedHistoryError) ErrorCode() int { return 4444 }

core/txindexer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ type txIndexer struct {
5858

5959
// newTxIndexer initializes the transaction indexer.
6060
func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer {
61+
cutoff, _ := chain.HistoryPruningCutoff()
6162
indexer := &txIndexer{
6263
limit: limit,
63-
cutoff: chain.HistoryPruningCutoff(),
64+
cutoff: cutoff,
6465
db: chain.db,
6566
progress: make(chan chan TxIndexProgress),
6667
term: make(chan chan struct{}),

ctxc/backend.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ func New(stack *node.Node, config *Config) (*Cortex, error) {
217217
//StateHistory: config.StateHistory,
218218
//StateScheme: scheme,
219219
//HistoryPruningCutoff: historyPruningCutoff,
220+
ChainHistoryMode: config.HistoryMode,
220221
}
221222
)
222223
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) {
233234
}
234235

235236
chainView := ctxc.newChainView(ctxc.blockchain.CurrentBlock().Header())
236-
historyCutoff := ctxc.blockchain.HistoryPruningCutoff()
237+
historyCutoff, _ := ctxc.blockchain.HistoryPruningCutoff()
237238
var finalBlock uint64
238239
if fb := ctxc.blockchain.CurrentFinalizedBlock(); fb != nil {
239240
finalBlock = fb.Header().Number.Uint64()
@@ -667,7 +668,7 @@ func (s *Cortex) updateFilterMapsHeads() {
667668
if head == nil || newHead.Hash() != head.Hash() {
668669
head = newHead
669670
chainView := s.newChainView(head)
670-
historyCutoff := s.blockchain.HistoryPruningCutoff()
671+
historyCutoff, _ := s.blockchain.HistoryPruningCutoff()
671672
var finalBlock uint64
672673
if fb := s.blockchain.CurrentFinalizedBlock(); fb != nil {
673674
finalBlock = fb.Header().Number.Uint64()

ctxc/config.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/CortexFoundation/CortexTheseus/common"
2626
"github.com/CortexFoundation/CortexTheseus/consensus/cuckoo"
2727
"github.com/CortexFoundation/CortexTheseus/core"
28+
"github.com/CortexFoundation/CortexTheseus/core/history"
2829
"github.com/CortexFoundation/CortexTheseus/core/txpool"
2930
"github.com/CortexFoundation/CortexTheseus/ctxc/downloader"
3031
"github.com/CortexFoundation/CortexTheseus/ctxc/gasprice"
@@ -48,6 +49,7 @@ var DefaultLightGPOConfig = gasprice.Config{
4849

4950
// DefaultConfig contains default settings for use on the Cortex main net.
5051
var DefaultConfig = Config{
52+
HistoryMode: history.KeepAll,
5153
SyncMode: downloader.FullSync,
5254
Cuckoo: cuckoo.Config{},
5355
NetworkId: 0,
@@ -87,8 +89,10 @@ type Config struct {
8789
Genesis *core.Genesis `toml:",omitempty"`
8890

8991
// Protocol options
90-
NetworkId uint64 // Network ID to use for selecting peers to connect to
91-
SyncMode downloader.SyncMode
92+
NetworkId uint64 // Network ID to use for selecting peers to connect to
93+
SyncMode downloader.SyncMode
94+
// HistoryMode configures chain history retention.
95+
HistoryMode history.HistoryMode
9296
DiscoveryURLs []string
9397
NoPruning bool
9498
NoPrefetch bool // Whether to disable prefetching and only load state on demand

ctxc/gen_config.go

Lines changed: 39 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
77
github.com/CortexFoundation/inference v1.0.2-0.20230307032835-9197d586a4e8
88
github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66
9-
github.com/CortexFoundation/torrentfs v1.0.69-0.20250415112856-b1b452a24746
9+
github.com/CortexFoundation/torrentfs v1.0.69-0.20250415134051-0b782fe8b66d
1010
github.com/VictoriaMetrics/fastcache v1.12.2
1111
github.com/arsham/figurine v1.3.0
1212
github.com/aws/aws-sdk-go-v2 v1.36.3

0 commit comments

Comments
 (0)