Skip to content

Commit 7fffa44

Browse files
zsfelfoldirjl493456442
authored andcommitted
eth/filters, core/filtermaps: safe chain view update (ethereum#31590)
This PR changes the chain view update mechanism of the log filter. Previously the head updates were all wired through the indexer, even in unindexed mode. This was both a bit weird and also unsafe as the indexer's chain view was updates asynchronously with some delay, making some log related tests flaky. Also, the reorg safety of the indexed search was integrated with unindexed search in a weird way, relying on `syncRange.ValidBlocks` in the unindexed case too, with a special condition added to only consider the head of the valid range but not the tail in the unindexed case. In this PR the current chain view is directly accessible through the filter backend and unindexed search is also chain view based, making it inherently safe. The matcher sync mechanism is now only used for indexed search as originally intended, removing a few ugly special conditions. The PR is currently based on top of ethereum#31642 Together they fix ethereum#31518 and replace ethereum#31542 --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
1 parent 65555b6 commit 7fffa44

File tree

15 files changed

+328
-202
lines changed

15 files changed

+328
-202
lines changed

accounts/abi/abigen/bind_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,7 @@ var bindTests = []struct {
939939
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
940940
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
941941
}
942+
time.Sleep(time.Millisecond * 200)
942943
}
943944
sim.Commit()
944945
}
@@ -1495,7 +1496,7 @@ var bindTests = []struct {
14951496
if n != 3 {
14961497
t.Fatalf("Invalid bar0 event")
14971498
}
1498-
case <-time.NewTimer(3 * time.Second).C:
1499+
case <-time.NewTimer(10 * time.Second).C:
14991500
t.Fatalf("Wait bar0 event timeout")
15001501
}
15011502
@@ -1506,7 +1507,7 @@ var bindTests = []struct {
15061507
if n != 1 {
15071508
t.Fatalf("Invalid bar event")
15081509
}
1509-
case <-time.NewTimer(3 * time.Second).C:
1510+
case <-time.NewTimer(10 * time.Second).C:
15101511
t.Fatalf("Wait bar event timeout")
15111512
}
15121513
close(stopCh)

core/filtermaps/chain_view.go

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,55 +58,83 @@ func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView
5858
return cv
5959
}
6060

61-
// getBlockHash returns the block hash belonging to the given block number.
61+
// HeadNumber returns the head block number of the chain view.
62+
func (cv *ChainView) HeadNumber() uint64 {
63+
return cv.headNumber
64+
}
65+
66+
// BlockHash returns the block hash belonging to the given block number.
6267
// Note that the hash of the head block is not returned because ChainView might
6368
// represent a view where the head block is currently being created.
64-
func (cv *ChainView) getBlockHash(number uint64) common.Hash {
65-
if number >= cv.headNumber {
69+
func (cv *ChainView) BlockHash(number uint64) common.Hash {
70+
cv.lock.Lock()
71+
defer cv.lock.Unlock()
72+
73+
if number > cv.headNumber {
6674
panic("invalid block number")
6775
}
6876
return cv.blockHash(number)
6977
}
7078

71-
// getBlockId returns the unique block id belonging to the given block number.
79+
// BlockId returns the unique block id belonging to the given block number.
7280
// Note that it is currently equal to the block hash. In the future it might
7381
// be a different id for future blocks if the log index root becomes part of
7482
// consensus and therefore rendering the index with the new head will happen
7583
// before the hash of that new head is available.
76-
func (cv *ChainView) getBlockId(number uint64) common.Hash {
84+
func (cv *ChainView) BlockId(number uint64) common.Hash {
85+
cv.lock.Lock()
86+
defer cv.lock.Unlock()
87+
7788
if number > cv.headNumber {
7889
panic("invalid block number")
7990
}
8091
return cv.blockHash(number)
8192
}
8293

83-
// getReceipts returns the set of receipts belonging to the block at the given
94+
// Header returns the block header at the given block number.
95+
func (cv *ChainView) Header(number uint64) *types.Header {
96+
return cv.chain.GetHeader(cv.BlockHash(number), number)
97+
}
98+
99+
// Receipts returns the set of receipts belonging to the block at the given
84100
// block number.
85-
func (cv *ChainView) getReceipts(number uint64) types.Receipts {
86-
if number > cv.headNumber {
87-
panic("invalid block number")
88-
}
89-
blockHash := cv.blockHash(number)
101+
func (cv *ChainView) Receipts(number uint64) types.Receipts {
102+
blockHash := cv.BlockHash(number)
90103
if blockHash == (common.Hash{}) {
91104
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
92105
}
93106
return cv.chain.GetReceiptsByHash(blockHash)
94107
}
95108

109+
// SharedRange returns the block range shared by two chain views.
110+
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
111+
cv.lock.Lock()
112+
defer cv.lock.Unlock()
113+
114+
if cv == nil || cv2 == nil || !cv.extendNonCanonical() || !cv2.extendNonCanonical() {
115+
return common.Range[uint64]{}
116+
}
117+
var sharedLen uint64
118+
for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber && cv.blockHash(n) == cv2.blockHash(n); n++ {
119+
sharedLen = n + 1
120+
}
121+
return common.NewRange(0, sharedLen)
122+
}
123+
96124
// limitedView returns a new chain view that is a truncated version of the parent view.
97125
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
98126
if newHead >= cv.headNumber {
99127
return cv
100128
}
101-
return NewChainView(cv.chain, newHead, cv.blockHash(newHead))
129+
return NewChainView(cv.chain, newHead, cv.BlockHash(newHead))
102130
}
103131

104132
// equalViews returns true if the two chain views are equivalent.
105133
func equalViews(cv1, cv2 *ChainView) bool {
106134
if cv1 == nil || cv2 == nil {
107135
return false
108136
}
109-
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber)
137+
return cv1.headNumber == cv2.headNumber && cv1.BlockId(cv1.headNumber) == cv2.BlockId(cv2.headNumber)
110138
}
111139

112140
// matchViews returns true if the two chain views are equivalent up until the
@@ -120,9 +148,9 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
120148
return false
121149
}
122150
if number == cv1.headNumber || number == cv2.headNumber {
123-
return cv1.getBlockId(number) == cv2.getBlockId(number)
151+
return cv1.BlockId(number) == cv2.BlockId(number)
124152
}
125-
return cv1.getBlockHash(number) == cv2.getBlockHash(number)
153+
return cv1.BlockHash(number) == cv2.BlockHash(number)
126154
}
127155

128156
// extendNonCanonical checks whether the previously known reverse list of head
@@ -150,9 +178,6 @@ func (cv *ChainView) extendNonCanonical() bool {
150178

151179
// blockHash returns the given block hash without doing the head number check.
152180
func (cv *ChainView) blockHash(number uint64) common.Hash {
153-
cv.lock.Lock()
154-
defer cv.lock.Unlock()
155-
156181
if number+uint64(len(cv.hashes)) <= cv.headNumber {
157182
hash := cv.chain.GetCanonicalHash(number)
158183
if !cv.extendNonCanonical() {

core/filtermaps/filtermaps.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
262262
f.targetView = initView
263263
if f.indexedRange.initialized {
264264
f.indexedView = f.initChainView(f.targetView)
265-
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.headNumber+1
265+
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.HeadNumber()+1
266266
if !f.indexedRange.headIndexed {
267267
f.indexedRange.headDelimiter = 0
268268
}
@@ -313,7 +313,7 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
313313
log.Error("Could not initialize indexed chain view", "error", err)
314314
break
315315
}
316-
if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId {
316+
if lastBlockNumber <= chainView.HeadNumber() && chainView.BlockId(lastBlockNumber) == lastBlockId {
317317
return chainView.limitedView(lastBlockNumber)
318318
}
319319
}
@@ -370,7 +370,7 @@ func (f *FilterMaps) init() error {
370370
for min < max {
371371
mid := (min + max + 1) / 2
372372
cp := checkpointList[mid-1]
373-
if cp.BlockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.BlockNumber) == cp.BlockId {
373+
if cp.BlockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(cp.BlockNumber) == cp.BlockId {
374374
min = mid
375375
} else {
376376
max = mid - 1
@@ -512,7 +512,7 @@ func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
512512
}
513513
}
514514
// get block receipts
515-
receipts := f.indexedView.getReceipts(firstBlockNumber)
515+
receipts := f.indexedView.Receipts(firstBlockNumber)
516516
if receipts == nil {
517517
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
518518
}

core/filtermaps/indexer.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (f *FilterMaps) indexerLoop() {
4444

4545
for !f.stop {
4646
if !f.indexedRange.initialized {
47-
if f.targetView.headNumber == 0 {
47+
if f.targetView.HeadNumber() == 0 {
4848
// initialize when chain head is available
4949
f.processSingleEvent(true)
5050
continue
@@ -249,7 +249,7 @@ func (f *FilterMaps) tryIndexHead() error {
249249
log.Info("Log index head rendering in progress",
250250
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
251251
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
252-
"remaining", f.indexedView.headNumber-f.indexedRange.blocks.Last(),
252+
"remaining", f.indexedView.HeadNumber()-f.indexedRange.blocks.Last(),
253253
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
254254
f.loggedHeadIndex = true
255255
f.lastLogHeadIndex = time.Now()
@@ -418,10 +418,10 @@ func (f *FilterMaps) needTailEpoch(epoch uint32) bool {
418418
// tailTargetBlock returns the target value for the tail block number according
419419
// to the log history parameter and the current index head.
420420
func (f *FilterMaps) tailTargetBlock() uint64 {
421-
if f.history == 0 || f.indexedView.headNumber < f.history {
421+
if f.history == 0 || f.indexedView.HeadNumber() < f.history {
422422
return 0
423423
}
424-
return f.indexedView.headNumber + 1 - f.history
424+
return f.indexedView.HeadNumber() + 1 - f.history
425425
}
426426

427427
// tailPartialBlocks returns the number of rendered blocks in the partially

core/filtermaps/map_renderer.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ func (f *FilterMaps) lastCanonicalSnapshotOfMap(mapIndex uint32) *renderedMap {
143143
var best *renderedMap
144144
for _, blockNumber := range f.renderSnapshots.Keys() {
145145
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
146-
blockNumber <= f.indexedView.headNumber && f.indexedView.getBlockId(blockNumber) == cp.lastBlockId &&
147-
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
146+
blockNumber <= f.indexedView.HeadNumber() && f.indexedView.BlockId(blockNumber) == cp.lastBlockId &&
147+
blockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(blockNumber) == cp.lastBlockId &&
148148
cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) {
149149
best = cp
150150
}
@@ -173,7 +173,7 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(renderBefore uint32) (nextMa
173173
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
174174
}
175175
if (f.indexedRange.headIndexed && mapIndex >= f.indexedRange.maps.Last()) ||
176-
lastBlock >= f.targetView.headNumber || lastBlockId != f.targetView.getBlockId(lastBlock) {
176+
lastBlock >= f.targetView.HeadNumber() || lastBlockId != f.targetView.BlockId(lastBlock) {
177177
continue // map is not full or inconsistent with targetView; roll back
178178
}
179179
lvPtr, err := f.getBlockLvPointer(lastBlock)
@@ -247,7 +247,7 @@ func (f *FilterMaps) loadHeadSnapshot() error {
247247
filterMap: fm,
248248
mapIndex: f.indexedRange.maps.Last(),
249249
lastBlock: f.indexedRange.blocks.Last(),
250-
lastBlockId: f.indexedView.getBlockId(f.indexedRange.blocks.Last()),
250+
lastBlockId: f.indexedView.BlockId(f.indexedRange.blocks.Last()),
251251
blockLvPtrs: lvPtrs,
252252
finished: true,
253253
headDelimiter: f.indexedRange.headDelimiter,
@@ -264,7 +264,7 @@ func (r *mapRenderer) makeSnapshot() {
264264
filterMap: r.currentMap.filterMap.fastCopy(),
265265
mapIndex: r.currentMap.mapIndex,
266266
lastBlock: r.currentMap.lastBlock,
267-
lastBlockId: r.iterator.chainView.getBlockId(r.currentMap.lastBlock),
267+
lastBlockId: r.iterator.chainView.BlockId(r.currentMap.lastBlock),
268268
blockLvPtrs: r.currentMap.blockLvPtrs,
269269
finished: true,
270270
headDelimiter: r.iterator.lvIndex,
@@ -370,7 +370,7 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
370370
r.currentMap.finished = true
371371
r.currentMap.headDelimiter = r.iterator.lvIndex
372372
}
373-
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
373+
r.currentMap.lastBlockId = r.f.targetView.BlockId(r.currentMap.lastBlock)
374374
totalTime += time.Since(start)
375375
mapRenderTimer.Update(totalTime)
376376
mapLogValueMeter.Mark(logValuesProcessed)
@@ -566,8 +566,8 @@ func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) {
566566
lm := r.finishedMaps[r.finished.Last()]
567567
newRange.headIndexed = lm.finished
568568
if lm.finished {
569-
newRange.blocks.SetLast(r.f.targetView.headNumber)
570-
if lm.lastBlock != r.f.targetView.headNumber {
569+
newRange.blocks.SetLast(r.f.targetView.HeadNumber())
570+
if lm.lastBlock != r.f.targetView.HeadNumber() {
571571
panic("map rendering finished but last block != head block")
572572
}
573573
newRange.headDelimiter = lm.headDelimiter
@@ -665,13 +665,13 @@ var errUnindexedRange = errors.New("unindexed range")
665665
// given block's first log value entry (the block delimiter), according to the
666666
// current targetView.
667667
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint64) (*logIterator, error) {
668-
if blockNumber > f.targetView.headNumber {
669-
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber)
668+
if blockNumber > f.targetView.HeadNumber() {
669+
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.HeadNumber())
670670
}
671671
if !f.indexedRange.blocks.Includes(blockNumber) {
672672
return nil, errUnindexedRange
673673
}
674-
finished := blockNumber == f.targetView.headNumber
674+
finished := blockNumber == f.targetView.HeadNumber()
675675
l := &logIterator{
676676
chainView: f.targetView,
677677
params: &f.Params,
@@ -687,11 +687,11 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint6
687687
// newLogIteratorFromMapBoundary creates a logIterator starting at the given
688688
// map boundary, according to the current targetView.
689689
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
690-
if startBlock > f.targetView.headNumber {
691-
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber)
690+
if startBlock > f.targetView.HeadNumber() {
691+
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.HeadNumber())
692692
}
693693
// get block receipts
694-
receipts := f.targetView.getReceipts(startBlock)
694+
receipts := f.targetView.Receipts(startBlock)
695695
if receipts == nil {
696696
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
697697
}
@@ -758,7 +758,7 @@ func (l *logIterator) next() error {
758758
if l.delimiter {
759759
l.delimiter = false
760760
l.blockNumber++
761-
l.receipts = l.chainView.getReceipts(l.blockNumber)
761+
l.receipts = l.chainView.Receipts(l.blockNumber)
762762
if l.receipts == nil {
763763
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
764764
}
@@ -795,7 +795,7 @@ func (l *logIterator) enforceValidState() {
795795
}
796796
l.logIndex = 0
797797
}
798-
if l.blockNumber == l.chainView.headNumber {
798+
if l.blockNumber == l.chainView.HeadNumber() {
799799
l.finished = true
800800
} else {
801801
l.delimiter = true

core/filtermaps/matcher.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type MatcherBackend interface {
5757
// all states of the chain since the previous SyncLogIndex or the creation of
5858
// the matcher backend.
5959
type SyncRange struct {
60-
HeadNumber uint64
60+
IndexedView *ChainView
6161
// block range where the index has not changed since the last matcher sync
6262
// and therefore the set of matches found in this region is guaranteed to
6363
// be valid and complete.

core/filtermaps/matcher_backend.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func (fm *FilterMapsMatcherBackend) synced() {
128128
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
129129
}
130130
fm.syncCh <- SyncRange{
131-
HeadNumber: fm.f.targetView.headNumber,
131+
IndexedView: fm.f.indexedView,
132132
ValidBlocks: fm.validBlocks,
133133
IndexedBlocks: indexedBlocks,
134134
}
@@ -154,15 +154,15 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
154154
case <-ctx.Done():
155155
return SyncRange{}, ctx.Err()
156156
case <-fm.f.disabledCh:
157-
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
157+
return SyncRange{IndexedView: fm.f.indexedView}, nil
158158
}
159159
select {
160160
case vr := <-syncCh:
161161
return vr, nil
162162
case <-ctx.Done():
163163
return SyncRange{}, ctx.Err()
164164
case <-fm.f.disabledCh:
165-
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
165+
return SyncRange{IndexedView: fm.f.indexedView}, nil
166166
}
167167
}
168168

eth/api_backend.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,14 @@ func (b *EthAPIBackend) RPCTxFeeCap() float64 {
443443
return b.eth.config.RPCTxFeeCap
444444
}
445445

446+
func (b *EthAPIBackend) CurrentView() *filtermaps.ChainView {
447+
head := b.eth.blockchain.CurrentBlock()
448+
if head == nil {
449+
return nil
450+
}
451+
return filtermaps.NewChainView(b.eth.blockchain, head.Number.Uint64(), head.Hash())
452+
}
453+
446454
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
447455
return b.eth.filterMaps.NewMatcherBackend()
448456
}

0 commit comments

Comments
 (0)