Skip to content

Commit eb668c4

Browse files
committed
Merge branch 'develop' into mardizzone/upstream-v1.16.4
2 parents c875c93 + d72e55d commit eb668c4

File tree

22 files changed

+636
-37
lines changed

22 files changed

+636
-37
lines changed

core/rawdb/accessors_chain.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,3 +1123,21 @@ func WriteBlockPruneCursor(db ethdb.KeyValueWriter, cursor uint64) {
11231123
log.Crit("Failed to store block cursor", "err", err)
11241124
}
11251125
}
1126+
1127+
func ReadBlockPruneHead(db ethdb.KeyValueReader) *uint64 {
1128+
log.Debug("ReadBlockHead")
1129+
data, err := db.Get(blockPruneHeadKey())
1130+
if err != nil || len(data) == 0 {
1131+
return nil
1132+
}
1133+
1134+
number := binary.BigEndian.Uint64(data)
1135+
return &number
1136+
}
1137+
1138+
func WriteBlockPruneHead(db ethdb.KeyValueWriter, head uint64) {
1139+
log.Debug("WriteBlockPruneHead", "head", head)
1140+
if err := db.Put(blockPruneHeadKey(), encodeBlockNumber(head)); err != nil {
1141+
log.Crit("Failed to store block head", "err", err)
1142+
}
1143+
}

core/rawdb/accessors_chain_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,3 +1038,93 @@ func TestHeadersRLPStorage(t *testing.T) {
10381038
checkSequence(1, 1) // Only block 1
10391039
checkSequence(1, 2) // Genesis + block 1
10401040
}
1041+
1042+
// mockErrorDB is a mock database that always returns errors on Get operations
1043+
type mockErrorDB struct {
1044+
getError error
1045+
}
1046+
1047+
func (m *mockErrorDB) Has(key []byte) (bool, error) {
1048+
return false, m.getError
1049+
}
1050+
1051+
func (m *mockErrorDB) Get(key []byte) ([]byte, error) {
1052+
return nil, m.getError
1053+
}
1054+
1055+
func TestReadBlockPruneHead(t *testing.T) {
1056+
db := NewMemoryDatabase()
1057+
1058+
// Test reading non-existent entry (should return nil)
1059+
if entry := ReadBlockPruneHead(db); entry != nil {
1060+
t.Fatalf("Non-existent block prune head returned: %v", entry)
1061+
}
1062+
1063+
// Test reading when Get returns an error
1064+
errorDB := &mockErrorDB{getError: fmt.Errorf("database error")}
1065+
if entry := ReadBlockPruneHead(errorDB); entry != nil {
1066+
t.Fatalf("Expected nil when Get returns error, got: %v", entry)
1067+
}
1068+
1069+
// Write a valid value and verify read
1070+
testHead := uint64(12345)
1071+
WriteBlockPruneHead(db, testHead)
1072+
1073+
if entry := ReadBlockPruneHead(db); entry == nil {
1074+
t.Fatalf("Stored block prune head not found")
1075+
} else if *entry != testHead {
1076+
t.Fatalf("Retrieved block prune head mismatch: have %v, want %v", *entry, testHead)
1077+
}
1078+
1079+
// Test reading after writing zero value
1080+
WriteBlockPruneHead(db, 0)
1081+
if entry := ReadBlockPruneHead(db); entry == nil {
1082+
t.Fatalf("Stored block prune head (zero) not found")
1083+
} else if *entry != 0 {
1084+
t.Fatalf("Retrieved block prune head mismatch: have %v, want 0", *entry)
1085+
}
1086+
1087+
// Test reading after writing max uint64 value
1088+
maxHead := uint64(18446744073709551615)
1089+
WriteBlockPruneHead(db, maxHead)
1090+
if entry := ReadBlockPruneHead(db); entry == nil {
1091+
t.Fatalf("Stored block prune head (max uint64) not found")
1092+
} else if *entry != maxHead {
1093+
t.Fatalf("Retrieved block prune head mismatch: have %v, want %v", *entry, maxHead)
1094+
}
1095+
}
1096+
1097+
func TestWriteBlockPruneHead(t *testing.T) {
1098+
db := NewMemoryDatabase()
1099+
1100+
// Test writing and reading back a value
1101+
testHead := uint64(54321)
1102+
WriteBlockPruneHead(db, testHead)
1103+
1104+
if entry := ReadBlockPruneHead(db); entry == nil {
1105+
t.Fatalf("Stored block prune head not found")
1106+
} else if *entry != testHead {
1107+
t.Fatalf("Retrieved block prune head mismatch: have %v, want %v", *entry, testHead)
1108+
}
1109+
1110+
// Test overwriting with a different value
1111+
newHead := uint64(99999)
1112+
WriteBlockPruneHead(db, newHead)
1113+
1114+
if entry := ReadBlockPruneHead(db); entry == nil {
1115+
t.Fatalf("Updated block prune head not found")
1116+
} else if *entry != newHead {
1117+
t.Fatalf("Updated block prune head mismatch: have %v, want %v", *entry, newHead)
1118+
}
1119+
1120+
// Test writing multiple different values in sequence
1121+
testValues := []uint64{0, 1, 100, 1000, 10000, 18446744073709551615}
1122+
for _, val := range testValues {
1123+
WriteBlockPruneHead(db, val)
1124+
if entry := ReadBlockPruneHead(db); entry == nil {
1125+
t.Fatalf("Block prune head not found for value %d", val)
1126+
} else if *entry != val {
1127+
t.Fatalf("Block prune head mismatch for value %d: have %v, want %v", val, *entry, val)
1128+
}
1129+
}
1130+
}

core/rawdb/accessors_state.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,21 @@ func WriteTrienodeHistory(db ethdb.AncientWriter, id uint64, header []byte, keyS
429429
})
430430
return err
431431
}
432+
433+
func ReadWitnessPruneHead(db ethdb.KeyValueReader) *uint64 {
434+
log.Debug("ReadWitnessHead")
435+
data, err := db.Get(witnessPruneHeadKey())
436+
if err != nil || len(data) == 0 {
437+
return nil
438+
}
439+
440+
number := binary.BigEndian.Uint64(data)
441+
return &number
442+
}
443+
444+
func WriteWitnessPruneHead(db ethdb.KeyValueWriter, head uint64) {
445+
log.Debug("WriteWitnessPruneHead", "head", head)
446+
if err := db.Put(witnessPruneHeadKey(), encodeBlockNumber(head)); err != nil {
447+
log.Crit("Failed to store witness Head", "err", err)
448+
}
449+
}

core/rawdb/pruner.go

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/ethereum/go-ethereum/log"
99
)
1010

11+
const MaxDeleteRangeSize = uint64(50_000)
12+
1113
type Strategy interface {
1214
Name() string
1315
RetentionBlocks() uint64
@@ -17,6 +19,10 @@ type Strategy interface {
1719
ReadCursor(db ethdb.KeyValueReader) *uint64
1820
WriteCursor(db ethdb.KeyValueWriter, cur uint64)
1921

22+
// Head Persistency
23+
ReadPrunerHead(db ethdb.KeyValueReader) *uint64
24+
WritePrunerHead(db ethdb.KeyValueWriter, head uint64)
25+
2026
// Find earliest height <= cutoff that has data for this domain.
2127
FindEarliest(db ethdb.Database, cutoff uint64) (earliest uint64, ok bool)
2228

@@ -37,10 +43,13 @@ type pruner struct {
3743
strategy Strategy
3844
}
3945

40-
// This pruner implements an online solution of pruning data. It demands the data to be pruned
41-
// to be on the KV database. That's why, for example, if you prune blocks you need to disable ancient db
4246
func NewPruner(db ethdb.Database, s Strategy) *pruner {
43-
return &pruner{db: db, strategy: s, quit: make(chan struct{}), stopped: make(chan struct{})}
47+
return &pruner{
48+
db: db,
49+
strategy: s,
50+
quit: make(chan struct{}),
51+
stopped: make(chan struct{}),
52+
}
4453
}
4554

4655
func (p *pruner) Start() {
@@ -60,6 +69,7 @@ func (p *pruner) Start() {
6069
}()
6170
log.Info(p.strategy.Name()+": started", "retentionBlocks", p.strategy.RetentionBlocks(), "interval", p.strategy.Interval().String())
6271
}
72+
6373
func (p *pruner) Close() error {
6474
close(p.quit)
6575
<-p.stopped
@@ -74,12 +84,35 @@ func (p *pruner) prune() {
7484
}
7585
latest := head.Number.Uint64()
7686

87+
prevHeadPtr := p.strategy.ReadPrunerHead(p.db)
88+
prevHead := latest
89+
if prevHeadPtr != nil {
90+
prevHead = *prevHeadPtr
91+
}
92+
93+
if latest < prevHead {
94+
// Reorg between prevHead and latest (could have happened while offline).
95+
if err := p.handleReorg(latest, prevHead); err != nil {
96+
log.Error(p.strategy.Name()+": reorg cleanup failed", "newHead", latest, "oldHead", prevHead, "err", err)
97+
// Do not update stored head; we want to try again next run.
98+
return
99+
}
100+
}
101+
77102
var cutoff uint64
78103
if rb := p.strategy.RetentionBlocks(); latest > rb {
79104
cutoff = latest - rb
80105
}
81106

82107
cur := p.strategy.ReadCursor(p.db)
108+
109+
if cur != nil && *cur > latest {
110+
log.Warn(p.strategy.Name()+": cursor beyond head; clamping", "cursor", *cur, "head", latest)
111+
p.strategy.WriteCursor(p.db, latest)
112+
tmp := latest
113+
cur = &tmp
114+
}
115+
83116
if cur == nil {
84117
if e, ok := p.strategy.FindEarliest(p.db, cutoff); ok {
85118
log.Info(p.strategy.Name()+": no cursor stored", "earliestFound", e)
@@ -90,45 +123,108 @@ func (p *pruner) prune() {
90123
log.Info(p.strategy.Name()+": no data ≤ cutoff; starting at cutoff", "cutoff", cutoff)
91124
}
92125
}
126+
93127
if *cur >= cutoff {
94-
batch := p.db.NewBatch()
95-
p.strategy.WriteCursor(batch, *cur)
96-
_ = batch.Write()
97-
log.Info(p.strategy.Name()+": no data to prune", "cursor", *cur, "cutoff", cutoff)
128+
p.strategy.WritePrunerHead(p.db, latest)
98129
return
99130
}
100131

101-
// Chunk to keep batches reasonable.
102-
const step = uint64(50_000)
132+
// Normal pruning: delete [cur .. cutoff-1] inclusive, then move cursor to cutoff.
103133
from := *cur
104-
for from < cutoff {
105-
to := from + step
106-
if to > cutoff {
107-
to = cutoff
134+
to := cutoff - 1
135+
136+
if err := p.deleteRange(from, to); err != nil {
137+
log.Error(p.strategy.Name()+": batch write error during prune", "from", from, "to", to, "err", err)
138+
return
139+
}
140+
141+
p.strategy.WriteCursor(p.db, cutoff)
142+
log.Info(p.strategy.Name()+": successfully pruned", "count", cutoff-from, "from", from, "to", to)
143+
p.strategy.WritePrunerHead(p.db, latest)
144+
}
145+
146+
func (p *pruner) handleReorg(newHead, oldHead uint64) error {
147+
log.Warn(p.strategy.Name()+": reorg detected", "newHead", newHead, "oldHead", oldHead)
148+
149+
cur := p.strategy.ReadCursor(p.db)
150+
if cur == nil {
151+
cur = new(uint64)
152+
*cur = 0 // meaning no pruned data yet
153+
}
154+
155+
// Range of non-canonical heights
156+
deleteFrom := max(*cur, newHead+1)
157+
deleteTo := oldHead
158+
159+
if deleteFrom <= deleteTo {
160+
log.Warn(p.strategy.Name()+": deleting non-canonical data",
161+
"from", deleteFrom, "to", deleteTo)
162+
163+
if err := p.deleteRange(deleteFrom, deleteTo); err != nil {
164+
log.Error(p.strategy.Name()+": reorg cleanup failed; keeping cursor unchanged",
165+
"from", deleteFrom, "to", deleteTo, "err", err)
166+
return err
108167
}
109-
nhs := p.strategy.ReadNumberHashes(p.db, from, to-1)
168+
}
169+
170+
if *cur > newHead {
171+
p.strategy.WriteCursor(p.db, newHead)
172+
log.Warn(p.strategy.Name()+": cursor rolled back", "oldCursor", *cur, "newCursor", newHead)
173+
}
174+
175+
return nil
176+
}
177+
178+
func max(a, b uint64) uint64 {
179+
if a > b {
180+
return a
181+
}
182+
return b
183+
}
184+
185+
func (p *pruner) deleteRange(from, to uint64) error {
186+
if from > to {
187+
return nil
188+
}
189+
190+
const step = MaxDeleteRangeSize
191+
start := from
192+
193+
for start <= to {
194+
end := start + step - 1
195+
if end > to {
196+
end = to
197+
}
198+
199+
nhs := p.strategy.ReadNumberHashes(p.db, start, end)
110200

111201
batch := p.db.NewBatch()
112-
var lastNum = ^uint64(0) // sentinel
202+
var lastNum = ^uint64(0)
203+
113204
for _, nh := range nhs {
114205
p.strategy.DeletePerHash(batch, nh.Number, nh.Hash)
206+
115207
if nh.Number != lastNum {
116-
if lastNum != ^uint64(0) { // avoid multiple deletes per same height in case of multiple hashs in same height
208+
if lastNum != ^uint64(0) {
117209
p.strategy.DeletePerHeight(batch, lastNum)
118210
}
119211
lastNum = nh.Number
120212
}
121213
}
122-
// flush last height if any rows existed
214+
123215
if lastNum != ^uint64(0) {
124216
p.strategy.DeletePerHeight(batch, lastNum)
125217
}
126-
p.strategy.WriteCursor(batch, to)
218+
127219
if err := batch.Write(); err != nil {
128-
log.Error(p.strategy.Name()+": batch write error", "from", from, "to", to-1, "err", err)
129-
return
220+
log.Error(p.strategy.Name()+": batch write error during reorg cleanup",
221+
"from", start, "to", end, "err", err)
222+
return err
130223
}
131-
log.Info(p.strategy.Name()+": successfully pruned", "count", (to-1)-from, "from", from, "to", to-1)
132-
from = to
224+
225+
log.Info(p.strategy.Name()+": removed reverted data", "from", start, "to", end)
226+
227+
start = end + 1
133228
}
229+
return nil
134230
}

core/rawdb/pruner_strategy_block.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ func (b *BlockStrategy) WriteCursor(db ethdb.KeyValueWriter, cur uint64) {
3333
WriteBlockPruneCursor(db, cur)
3434
}
3535

36+
func (b *BlockStrategy) ReadPrunerHead(db ethdb.KeyValueReader) *uint64 {
37+
return ReadBlockPruneHead(db)
38+
}
39+
func (b *BlockStrategy) WritePrunerHead(db ethdb.KeyValueWriter, cur uint64) {
40+
WriteBlockPruneHead(db, cur)
41+
}
42+
3643
func (b *BlockStrategy) FindEarliest(db ethdb.Database, cutoff uint64) (uint64, bool) {
3744
// earliest canonical header with data
3845
return findEarliestBlockWithData(db, cutoff)

core/rawdb/pruner_strategy_wit.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ func (w *WitnessStrategy) WriteCursor(db ethdb.KeyValueWriter, cur uint64) {
3535
WriteWitnessPruneCursor(db, cur)
3636
}
3737

38+
func (w *WitnessStrategy) ReadPrunerHead(db ethdb.KeyValueReader) *uint64 {
39+
return ReadWitnessPruneHead(db)
40+
}
41+
func (w *WitnessStrategy) WritePrunerHead(db ethdb.KeyValueWriter, cur uint64) {
42+
WriteWitnessPruneHead(db, cur)
43+
}
44+
3845
func (w *WitnessStrategy) FindEarliest(db ethdb.Database, cutoff uint64) (uint64, bool) {
3946
// same as your findEarliestWitness
4047
return findEarliestWitness(db, cutoff)

0 commit comments

Comments
 (0)