Skip to content

Commit c8a9a9c

Browse files
zsfelfoldifjl
andauthoredMar 28, 2025
core/filtermaps: revert to unindexed mode in case of indexing error (#31500)
This PR changes log indexer error handling so that if an indexing error happens then it disables the indexer and reverts to unindexed more without resetting the database (except in case of a failed database init). Resetting the database on the first error would probably be overkill as a client update might fix this without having to reindex the entire history. It would also make debugging very hard. On the other hand, these errors do not resolve themselves automatically so constantly retrying makes no sense either. With these changes a new attempt to resume indexing is made every time the client is restarted. The PR also fixes #31491 which originated from the tail indexer trying to resume processing a failed map renderer. --------- Co-authored-by: Felix Lange <fjl@twurst.com>
1 parent 32f36a6 commit c8a9a9c

File tree

2 files changed

+65
-38
lines changed

2 files changed

+65
-38
lines changed
 

‎core/filtermaps/chain_view.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ func (cv *ChainView) getReceipts(number uint64) types.Receipts {
8383
if number > cv.headNumber {
8484
panic("invalid block number")
8585
}
86-
return cv.chain.GetReceiptsByHash(cv.blockHash(number))
86+
blockHash := cv.blockHash(number)
87+
if blockHash == (common.Hash{}) {
88+
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
89+
}
90+
return cv.chain.GetReceiptsByHash(blockHash)
8791
}
8892

8993
// limitedView returns a new chain view that is a truncated version of the parent view.

‎core/filtermaps/indexer.go

+60-37
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package filtermaps
1818

1919
import (
20+
"errors"
2021
"math"
2122
"time"
2223

@@ -49,18 +50,15 @@ func (f *FilterMaps) indexerLoop() {
4950
continue
5051
}
5152
if err := f.init(); err != nil {
52-
log.Error("Error initializing log index; reverting to unindexed mode", "error", err)
53-
f.reset()
54-
f.disabled = true
55-
close(f.disabledCh)
53+
f.disableForError("initialization", err)
54+
f.reset() // remove broken index from DB
5655
return
5756
}
5857
}
5958
if !f.targetHeadIndexed() {
60-
if !f.tryIndexHead() {
61-
// either shutdown or unexpected error; in the latter case ensure
62-
// that proper shutdown is still possible.
63-
f.processSingleEvent(true)
59+
if err := f.tryIndexHead(); err != nil && err != errChainUpdate {
60+
f.disableForError("head rendering", err)
61+
return
6462
}
6563
} else {
6664
if f.finalBlock != f.lastFinal {
@@ -69,13 +67,36 @@ func (f *FilterMaps) indexerLoop() {
6967
}
7068
f.lastFinal = f.finalBlock
7169
}
72-
if f.tryIndexTail() && f.tryUnindexTail() {
73-
f.waitForNewHead()
70+
if done, err := f.tryIndexTail(); err != nil {
71+
f.disableForError("tail rendering", err)
72+
return
73+
} else if !done {
74+
continue
75+
}
76+
if done, err := f.tryUnindexTail(); err != nil {
77+
f.disableForError("tail unindexing", err)
78+
return
79+
} else if !done {
80+
continue
7481
}
82+
// tail indexing/unindexing is done; if head is also indexed then
83+
// wait here until there is a new head
84+
f.waitForNewHead()
7585
}
7686
}
7787
}
7888

89+
// disableForError is called when the indexer encounters a database error, for example a
90+
// missing receipt. We can't continue operating when the database is broken, so the
91+
// indexer goes into disabled state.
92+
// Note that the partial index is left in disk; maybe a client update can fix the
93+
// issue without reindexing.
94+
func (f *FilterMaps) disableForError(op string, err error) {
95+
log.Error("Log index "+op+" failed, reverting to unindexed mode", "error", err)
96+
f.disabled = true
97+
close(f.disabledCh)
98+
}
99+
79100
type targetUpdate struct {
80101
targetView *ChainView
81102
historyCutoff, finalBlock uint64
@@ -116,14 +137,15 @@ func (f *FilterMaps) SetBlockProcessing(blockProcessing bool) {
116137
// WaitIdle blocks until the indexer is in an idle state while synced up to the
117138
// latest targetView.
118139
func (f *FilterMaps) WaitIdle() {
119-
if f.disabled {
120-
f.closeWg.Wait()
121-
return
122-
}
123140
for {
124141
ch := make(chan bool)
125-
f.waitIdleCh <- ch
126-
if <-ch {
142+
select {
143+
case f.waitIdleCh <- ch:
144+
if <-ch {
145+
return
146+
}
147+
case <-f.disabledCh:
148+
f.closeWg.Wait()
127149
return
128150
}
129151
}
@@ -196,16 +218,16 @@ func (f *FilterMaps) setTarget(target targetUpdate) {
196218
f.finalBlock = target.finalBlock
197219
}
198220

199-
// tryIndexHead tries to render head maps according to the current targetView
200-
// and returns true if successful.
201-
func (f *FilterMaps) tryIndexHead() bool {
221+
// tryIndexHead tries to render head maps according to the current targetView.
222+
// Should be called when targetHeadIndexed returns false. If this function
223+
// returns no error then either stop is true or head indexing is finished.
224+
func (f *FilterMaps) tryIndexHead() error {
202225
headRenderer, err := f.renderMapsBefore(math.MaxUint32)
203226
if err != nil {
204-
log.Error("Error creating log index head renderer", "error", err)
205-
return false
227+
return err
206228
}
207229
if headRenderer == nil {
208-
return true
230+
return errors.New("head indexer has nothing to do") // tryIndexHead should be called when head is not indexed
209231
}
210232
if !f.startedHeadIndex {
211233
f.lastLogHeadIndex = time.Now()
@@ -230,8 +252,7 @@ func (f *FilterMaps) tryIndexHead() bool {
230252
f.lastLogHeadIndex = time.Now()
231253
}
232254
}); err != nil {
233-
log.Error("Log index head rendering failed", "error", err)
234-
return false
255+
return err
235256
}
236257
if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() {
237258
log.Info("Log index head rendering finished",
@@ -240,23 +261,23 @@ func (f *FilterMaps) tryIndexHead() bool {
240261
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
241262
}
242263
f.loggedHeadIndex, f.startedHeadIndex = false, false
243-
return true
264+
return nil
244265
}
245266

246267
// tryIndexTail tries to render tail epochs until the tail target block is
247268
// indexed and returns true if successful.
248269
// Note that tail indexing is only started if the log index head is fully
249270
// rendered according to targetView and is suspended as soon as the targetView
250271
// is changed.
251-
func (f *FilterMaps) tryIndexTail() bool {
272+
func (f *FilterMaps) tryIndexTail() (bool, error) {
252273
for {
253274
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
254275
if firstEpoch == 0 || !f.needTailEpoch(firstEpoch-1) {
255276
break
256277
}
257278
f.processEvents()
258279
if f.stop || !f.targetHeadIndexed() {
259-
return false
280+
return false, nil
260281
}
261282
// resume process if tail rendering was interrupted because of head rendering
262283
tailRenderer := f.tailRenderer
@@ -268,8 +289,7 @@ func (f *FilterMaps) tryIndexTail() bool {
268289
var err error
269290
tailRenderer, err = f.renderMapsBefore(f.indexedRange.maps.First())
270291
if err != nil {
271-
log.Error("Error creating log index tail renderer", "error", err)
272-
return false
292+
return false, err
273293
}
274294
}
275295
if tailRenderer == nil {
@@ -302,13 +322,16 @@ func (f *FilterMaps) tryIndexTail() bool {
302322
f.lastLogTailIndex = time.Now()
303323
}
304324
})
305-
if err != nil && f.needTailEpoch(firstEpoch-1) {
325+
if err != nil && !f.needTailEpoch(firstEpoch-1) {
306326
// stop silently if cutoff point has move beyond epoch boundary while rendering
307-
log.Error("Log index tail rendering failed", "error", err)
327+
return true, nil
328+
}
329+
if err != nil {
330+
return false, err
308331
}
309332
if !done {
310333
f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb
311-
return false
334+
return false, nil
312335
}
313336
}
314337
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
@@ -318,22 +341,22 @@ func (f *FilterMaps) tryIndexTail() bool {
318341
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
319342
f.loggedTailIndex = false
320343
}
321-
return true
344+
return true, nil
322345
}
323346

324347
// tryUnindexTail removes entire epochs of log index data as long as the first
325348
// fully indexed block is at least as old as the tail target.
326349
// Note that unindexing is very quick as it only removes continuous ranges of
327350
// data from the database and is also called while running head indexing.
328-
func (f *FilterMaps) tryUnindexTail() bool {
351+
func (f *FilterMaps) tryUnindexTail() (bool, error) {
329352
for {
330353
firstEpoch := (f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch) >> f.logMapsPerEpoch
331354
if f.needTailEpoch(firstEpoch) {
332355
break
333356
}
334357
f.processEvents()
335358
if f.stop {
336-
return false
359+
return false, nil
337360
}
338361
if !f.startedTailUnindex {
339362
f.startedTailUnindexAt = time.Now()
@@ -343,7 +366,7 @@ func (f *FilterMaps) tryUnindexTail() bool {
343366
}
344367
if err := f.deleteTailEpoch(firstEpoch); err != nil {
345368
log.Error("Log index tail epoch unindexing failed", "error", err)
346-
return false
369+
return false, err
347370
}
348371
}
349372
if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() {
@@ -354,7 +377,7 @@ func (f *FilterMaps) tryUnindexTail() bool {
354377
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
355378
f.startedTailUnindex = false
356379
}
357-
return true
380+
return true, nil
358381
}
359382

360383
// needTailEpoch returns true if the given tail epoch needs to be kept

0 commit comments

Comments
 (0)
Please sign in to comment.