Skip to content

Commit 9bd1f9a

Browse files
committed
eth/filters: safe chain view update
1 parent 4c9e7d1 commit 9bd1f9a

14 files changed

+315
-208
lines changed

core/filtermaps/chain_view.go

+61-27
Original file line numberDiff line numberDiff line change
@@ -41,61 +41,95 @@ type blockchain interface {
4141
// of the underlying blockchain, it should only possess the block headers
4242
// and receipts up until the expected chain view head.
4343
type ChainView struct {
44-
lock sync.Mutex
45-
chain blockchain
46-
headNumber uint64
47-
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
44+
lock sync.Mutex
45+
chain blockchain
46+
finishedHead uint64
47+
hashes []common.Hash // block hashes starting backwards from finishedHead until first canonical hash
4848
}
4949

5050
// NewChainView creates a new ChainView.
5151
func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView {
5252
cv := &ChainView{
53-
chain: chain,
54-
headNumber: number,
55-
hashes: []common.Hash{hash},
53+
chain: chain,
54+
finishedHead: number,
55+
hashes: []common.Hash{hash},
5656
}
5757
cv.extendNonCanonical()
5858
return cv
5959
}
6060

61-
// getBlockHash returns the block hash belonging to the given block number.
61+
// ProcessedHead returns the highest block number where BlockId and Receipts
62+
// are available.
63+
// Note that though in the current implementation ProcessedHead is always equal
64+
// to FinishedHead, the called should expect that ProcessedHead can be one block
65+
// higher in case the view represents a chain during block processing, when the
66+
// receipts and block id are already available but the header is not finished
67+
// yet and therefore the block hash cannot be calculated.
68+
func (cv *ChainView) ProcessedHead() uint64 {
69+
return cv.finishedHead
70+
}
71+
72+
// FinishedHead returns the highest block number where BlockHash and Header
73+
// are available.
74+
func (cv *ChainView) FinishedHead() uint64 {
75+
return cv.finishedHead
76+
}
77+
78+
// BlockHash returns the block hash belonging to the given block number.
6279
// Note that the hash of the head block is not returned because ChainView might
6380
// 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 {
81+
func (cv *ChainView) BlockHash(number uint64) common.Hash {
82+
if number > cv.finishedHead {
6683
panic("invalid block number")
6784
}
6885
return cv.blockHash(number)
6986
}
7087

71-
// getBlockId returns the unique block id belonging to the given block number.
88+
// BlockId returns the unique block id belonging to the given block number.
7289
// Note that it is currently equal to the block hash. In the future it might
7390
// be a different id for future blocks if the log index root becomes part of
7491
// consensus and therefore rendering the index with the new head will happen
7592
// before the hash of that new head is available.
76-
func (cv *ChainView) getBlockId(number uint64) common.Hash {
77-
if number > cv.headNumber {
93+
func (cv *ChainView) BlockId(number uint64) common.Hash {
94+
if number > cv.finishedHead {
7895
panic("invalid block number")
7996
}
8097
return cv.blockHash(number)
8198
}
8299

83-
// getReceipts returns the set of receipts belonging to the block at the given
100+
// Header returns the block header at the given block number.
101+
func (cv *ChainView) Header(number uint64) *types.Header {
102+
return cv.chain.GetHeader(cv.BlockHash(number), number)
103+
}
104+
105+
// Receipts returns the set of receipts belonging to the block at the given
84106
// block number.
85-
func (cv *ChainView) getReceipts(number uint64) types.Receipts {
86-
if number > cv.headNumber {
107+
func (cv *ChainView) Receipts(number uint64) types.Receipts {
108+
if number > cv.finishedHead {
87109
panic("invalid block number")
88110
}
89111
blockHash := cv.blockHash(number)
90112
if blockHash == (common.Hash{}) {
91-
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
113+
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.finishedHead)
92114
}
93115
return cv.chain.GetReceiptsByHash(blockHash)
94116
}
95117

118+
// SharedRange returns the block range shared by two chain views.
119+
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
120+
if cv == nil || cv2 == nil {
121+
return common.Range[uint64]{}
122+
}
123+
var sharedLen uint64
124+
for n := min(cv.finishedHead+1-uint64(len(cv.hashes)), cv2.finishedHead+1-uint64(len(cv2.hashes))); n <= cv.finishedHead && n <= cv2.finishedHead && cv.blockHash(n) == cv2.blockHash(n); n++ {
125+
sharedLen = n + 1
126+
}
127+
return common.NewRange(0, sharedLen)
128+
}
129+
96130
// limitedView returns a new chain view that is a truncated version of the parent view.
97131
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
98-
if newHead >= cv.headNumber {
132+
if newHead >= cv.finishedHead {
99133
return cv
100134
}
101135
return NewChainView(cv.chain, newHead, cv.blockHash(newHead))
@@ -106,7 +140,7 @@ func equalViews(cv1, cv2 *ChainView) bool {
106140
if cv1 == nil || cv2 == nil {
107141
return false
108142
}
109-
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber)
143+
return cv1.finishedHead == cv2.finishedHead && cv1.BlockId(cv1.finishedHead) == cv2.BlockId(cv2.finishedHead)
110144
}
111145

112146
// matchViews returns true if the two chain views are equivalent up until the
@@ -116,13 +150,13 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
116150
if cv1 == nil || cv2 == nil {
117151
return false
118152
}
119-
if cv1.headNumber < number || cv2.headNumber < number {
153+
if cv1.finishedHead < number || cv2.finishedHead < number {
120154
return false
121155
}
122-
if number == cv1.headNumber || number == cv2.headNumber {
123-
return cv1.getBlockId(number) == cv2.getBlockId(number)
156+
if number == cv1.finishedHead || number == cv2.finishedHead {
157+
return cv1.BlockId(number) == cv2.BlockId(number)
124158
}
125-
return cv1.getBlockHash(number) == cv2.getBlockHash(number)
159+
return cv1.BlockHash(number) == cv2.BlockHash(number)
126160
}
127161

128162
// extendNonCanonical checks whether the previously known reverse list of head
@@ -131,7 +165,7 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
131165
// more hashes to the list.
132166
func (cv *ChainView) extendNonCanonical() bool {
133167
for {
134-
hash, number := cv.hashes[len(cv.hashes)-1], cv.headNumber-uint64(len(cv.hashes)-1)
168+
hash, number := cv.hashes[len(cv.hashes)-1], cv.finishedHead-uint64(len(cv.hashes)-1)
135169
if cv.chain.GetCanonicalHash(number) == hash {
136170
return true
137171
}
@@ -153,14 +187,14 @@ func (cv *ChainView) blockHash(number uint64) common.Hash {
153187
cv.lock.Lock()
154188
defer cv.lock.Unlock()
155189

156-
if number+uint64(len(cv.hashes)) <= cv.headNumber {
190+
if number+uint64(len(cv.hashes)) <= cv.finishedHead {
157191
hash := cv.chain.GetCanonicalHash(number)
158192
if !cv.extendNonCanonical() {
159193
return common.Hash{}
160194
}
161-
if number+uint64(len(cv.hashes)) <= cv.headNumber {
195+
if number+uint64(len(cv.hashes)) <= cv.finishedHead {
162196
return hash
163197
}
164198
}
165-
return cv.hashes[cv.headNumber-number]
199+
return cv.hashes[cv.finishedHead-number]
166200
}

core/filtermaps/filtermaps.go

+4-4
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.ProcessedHead()+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.ProcessedHead() && 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.ProcessedHead() && 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

+4-4
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.ProcessedHead() == 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.ProcessedHead()-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.ProcessedHead() < f.history {
422422
return 0
423423
}
424-
return f.indexedView.headNumber + 1 - f.history
424+
return f.indexedView.ProcessedHead() + 1 - f.history
425425
}
426426

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

core/filtermaps/map_renderer.go

+16-16
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.ProcessedHead() && f.indexedView.BlockId(blockNumber) == cp.lastBlockId &&
147+
blockNumber <= f.targetView.ProcessedHead() && 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.ProcessedHead() || 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.ProcessedHead())
570+
if lm.lastBlock != r.f.targetView.ProcessedHead() {
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.ProcessedHead() {
669+
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.ProcessedHead())
670670
}
671671
if !f.indexedRange.blocks.Includes(blockNumber) {
672672
return nil, errUnindexedRange
673673
}
674-
finished := blockNumber == f.targetView.headNumber
674+
finished := blockNumber == f.targetView.ProcessedHead()
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.ProcessedHead() {
691+
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.ProcessedHead())
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.ProcessedHead() {
799799
l.finished = true
800800
} else {
801801
l.delimiter = true

core/filtermaps/matcher.go

+1-1
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

+3-3
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

+8
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)