Skip to content

Commit 79c1b15

Browse files
committed
use triegc for trie retention
1 parent 47ee58b commit 79c1b15

File tree

2 files changed

+33
-83
lines changed

2 files changed

+33
-83
lines changed

core/blockchain.go

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ type BlockChain struct {
223223
vmConfig vm.Config
224224
}
225225

226+
type trieGcEntry struct {
227+
Root common.Hash
228+
Timestamp uint64
229+
}
230+
226231
// NewBlockChain returns a fully initialised block chain using information
227232
// available in the database. It initialises the default Ethereum Validator
228233
// and Processor.
@@ -870,9 +875,6 @@ func (bc *BlockChain) Stop() {
870875
}
871876
}
872877

873-
// Arbitrum: only discard tries sufficiently old in both time and height
874-
retain := bc.FindRetentionBound()
875-
876878
// Ensure the state of a recent block is also stored to disk before exiting.
877879
// We're writing three different states to catch different restart scenarios:
878880
// - HEAD: So we don't need to reprocess any blocks in the general case
@@ -881,7 +883,7 @@ func (bc *BlockChain) Stop() {
881883
if !bc.cacheConfig.TrieDirtyDisabled {
882884
triedb := bc.stateCache.TrieDB()
883885

884-
for _, offset := range []uint64{0, 1, retain - 1} {
886+
for _, offset := range []uint64{0, 1, bc.cacheConfig.TriesInMemory} {
885887
if number := bc.CurrentBlock().NumberU64(); number > offset {
886888
recent := bc.GetBlockByNumber(number - offset)
887889
if recent.Root() == (common.Hash{}) {
@@ -901,7 +903,7 @@ func (bc *BlockChain) Stop() {
901903
}
902904
}
903905
for !bc.triegc.Empty() {
904-
triedb.Dereference(bc.triegc.PopItem().(common.Hash))
906+
triedb.Dereference(bc.triegc.PopItem().(trieGcEntry).Root)
905907
}
906908
if size, _ := triedb.Size(); size != 0 {
907909
log.Error("Dangling trie nodes after full cleanup")
@@ -1224,8 +1226,6 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
12241226
return 0, nil
12251227
}
12261228

1227-
var lastWrite uint64
1228-
12291229
// writeBlockWithoutState writes only the block and its metadata to the database,
12301230
// but does not write any state. This is used to construct competing side forks
12311231
// up to the point where they exceed the canonical total difficulty.
@@ -1286,18 +1286,18 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
12861286
}
12871287
triedb := bc.stateCache.TrieDB()
12881288

1289-
// Arbitrum: only discard tries sufficiently old in both time and height
1290-
retain := bc.FindRetentionBound()
1291-
12921289
// If we're running an archive node, always flush
12931290
if bc.cacheConfig.TrieDirtyDisabled {
12941291
return triedb.Commit(root, false, nil)
12951292
} else {
12961293
// Full but not archive node, do proper garbage collection
12971294
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
1298-
bc.triegc.Push(root, -int64(block.NumberU64()))
1295+
bc.triegc.Push(trieGcEntry{root, block.Header().Time}, -int64(block.NumberU64()))
12991296

1300-
if current := block.NumberU64(); current > retain {
1297+
blockLimit := int64(block.NumberU64()) - int64(bc.cacheConfig.TriesInMemory) // only cleared if below that
1298+
timeLimit := time.Now().Unix() - int64(bc.cacheConfig.TrieRetention.Seconds()) // only cleared if less than that
1299+
1300+
if blockLimit > 0 && timeLimit > 0 {
13011301
// If we exceeded our memory allowance, flush matured singleton nodes to disk
13021302
var (
13031303
nodes, imgs = triedb.Size()
@@ -1306,36 +1306,39 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
13061306
if nodes > limit || imgs > 4*1024*1024 {
13071307
triedb.Cap(limit - ethdb.IdealBatchSize)
13081308
}
1309-
// Find the next state trie we need to commit
1310-
chosen := current - retain
1311-
1309+
var prevEntry *trieGcEntry
1310+
var prevNum uint64
1311+
// Garbage collect anything below our required write retention
1312+
for !bc.triegc.Empty() {
1313+
tmp, number := bc.triegc.Pop()
1314+
triegcEntry := tmp.(trieGcEntry)
1315+
if uint64(-number) > uint64(blockLimit) || triegcEntry.Timestamp > uint64(timeLimit) {
1316+
bc.triegc.Push(triegcEntry, number)
1317+
break
1318+
}
1319+
if prevEntry != nil {
1320+
triedb.Dereference(prevEntry.Root)
1321+
}
1322+
prevEntry = &triegcEntry
1323+
prevNum = uint64(-number)
1324+
}
13121325
// If we exceeded out time allowance, flush an entire trie to disk
1313-
if bc.gcproc > bc.cacheConfig.TrieTimeLimit {
1326+
if bc.gcproc > bc.cacheConfig.TrieTimeLimit && prevEntry != nil {
13141327
// If the header is missing (canonical chain behind), we're reorging a low
13151328
// diff sidechain. Suspend committing until this operation is completed.
1316-
header := bc.GetHeaderByNumber(chosen)
1329+
header := bc.GetHeaderByNumber(prevNum)
13171330
if header == nil {
1318-
log.Warn("Reorg in progress, trie commit postponed", "number", chosen)
1331+
log.Warn("Reorg in progress, trie commit postponed")
13191332
} else {
13201333
// If we're exceeding limits but haven't reached a large enough memory gap,
13211334
// warn the user that the system is becoming unstable.
1322-
if chosen < lastWrite+retain && bc.gcproc >= 2*bc.cacheConfig.TrieTimeLimit {
1323-
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/float64(retain))
1324-
}
13251335
// Flush an entire trie and restart the counters
13261336
triedb.Commit(header.Root, true, nil)
1327-
lastWrite = chosen
13281337
bc.gcproc = 0
13291338
}
13301339
}
1331-
// Garbage collect anything below our required write retention
1332-
for !bc.triegc.Empty() {
1333-
root, number := bc.triegc.Pop()
1334-
if uint64(-number) > chosen {
1335-
bc.triegc.Push(root, number)
1336-
break
1337-
}
1338-
triedb.Dereference(root.(common.Hash))
1340+
if prevEntry != nil {
1341+
triedb.Dereference(prevEntry.Root)
13391342
}
13401343
}
13411344
}

core/blockchain_arbitrum.go

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -67,56 +67,3 @@ func (bc *BlockChain) ClipToPostNitroGenesis(blockNum rpc.BlockNumber) (rpc.Bloc
6767
}
6868
return blockNum, currentBlock
6969
}
70-
71-
// finds the number of blocks that aren't prunable
72-
func (bc *BlockChain) FindRetentionBound() uint64 {
73-
minimumSpan := bc.cacheConfig.TriesInMemory
74-
minimumAge := uint64(bc.cacheConfig.TrieRetention.Seconds())
75-
76-
if minimumAge == 0 {
77-
return minimumSpan
78-
}
79-
80-
saturatingCast := func(value int64) uint64 {
81-
if value < 0 {
82-
return 0
83-
}
84-
return uint64(value)
85-
}
86-
87-
// enforce that the block be sufficiently deep
88-
current := bc.CurrentBlock()
89-
heightBound := saturatingCast(int64(current.NumberU64()) - int64(minimumSpan) + 1)
90-
91-
// find the left bound to our subsequent binary search
92-
timeBound := heightBound
93-
leap := int64(1)
94-
for timeBound > 0 {
95-
age := current.Time() - bc.GetBlockByNumber(uint64(timeBound)).Time()
96-
if age > minimumAge {
97-
break
98-
}
99-
timeBound = saturatingCast(int64(timeBound) - leap)
100-
leap *= 2
101-
}
102-
if timeBound == heightBound {
103-
return current.NumberU64() - timeBound + 1
104-
}
105-
106-
// Algo: binary search on the interval [a, b] for the first prunable block
107-
// Timebound is a prunable block, if one exists.
108-
// We want to find the first block that's not prunable.
109-
//
110-
a := timeBound // a prunable block, if possible
111-
b := heightBound // not prunable
112-
for a+2 < b {
113-
mid := a/2 + b/2 // a < mid < b
114-
age := current.Time() - bc.GetBlockByNumber(mid).Time()
115-
if age <= minimumAge {
116-
b = mid // mid is not prunable and less than b
117-
} else {
118-
a = mid // mid is prunable, but might equal a
119-
}
120-
}
121-
return current.NumberU64() - a
122-
}

0 commit comments

Comments
 (0)