Skip to content

Commit fd0eae9

Browse files
committed
eth/filters: safe chain view update
1 parent 77dc1ac commit fd0eae9

File tree

13 files changed

+206
-146
lines changed

13 files changed

+206
-146
lines changed

core/filtermaps/chain_view.go

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,60 +39,93 @@ type blockchain interface {
3939
// of the underlying blockchain, it should only possess the block headers
4040
// and receipts up until the expected chain view head.
4141
type ChainView struct {
42-
chain blockchain
43-
headNumber uint64
44-
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
42+
chain blockchain
43+
finishedHead uint64
44+
hashes []common.Hash // block hashes starting backwards from finishedHead until first canonical hash
4545
}
4646

4747
// NewChainView creates a new ChainView.
4848
func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView {
4949
cv := &ChainView{
50-
chain: chain,
51-
headNumber: number,
52-
hashes: []common.Hash{hash},
50+
chain: chain,
51+
finishedHead: number,
52+
hashes: []common.Hash{hash},
5353
}
5454
cv.extendNonCanonical()
5555
return cv
5656
}
5757

58-
// getBlockHash returns the block hash belonging to the given block number.
58+
// ProcessedHead returns the highest block number where BlockId and Receipts
59+
// are available.
60+
// Note that though in the current implementation ProcessedHead is always equal
61+
// to FinishedHead, the called should expect that ProcessedHead can be one block
62+
// higher in case the view represents a chain during block processing, when the
63+
// receipts and block id are already available but the header is not finished
64+
// yet and therefore the block hash cannot be calculated.
65+
func (cv *ChainView) ProcessedHead() uint64 {
66+
return cv.finishedHead
67+
}
68+
69+
// FinishedHead returns the highest block number where BlockHash and Header
70+
// are available.
71+
func (cv *ChainView) FinishedHead() uint64 {
72+
return cv.finishedHead
73+
}
74+
75+
// BlockHash returns the block hash belonging to the given block number.
5976
// Note that the hash of the head block is not returned because ChainView might
6077
// represent a view where the head block is currently being created.
61-
func (cv *ChainView) getBlockHash(number uint64) common.Hash {
62-
if number >= cv.headNumber {
78+
func (cv *ChainView) BlockHash(number uint64) common.Hash {
79+
if number > cv.finishedHead {
6380
panic("invalid block number")
6481
}
6582
return cv.blockHash(number)
6683
}
6784

68-
// getBlockId returns the unique block id belonging to the given block number.
85+
// BlockId returns the unique block id belonging to the given block number.
6986
// Note that it is currently equal to the block hash. In the future it might
7087
// be a different id for future blocks if the log index root becomes part of
7188
// consensus and therefore rendering the index with the new head will happen
7289
// before the hash of that new head is available.
73-
func (cv *ChainView) getBlockId(number uint64) common.Hash {
74-
if number > cv.headNumber {
90+
func (cv *ChainView) BlockId(number uint64) common.Hash {
91+
if number > cv.finishedHead {
7592
panic("invalid block number")
7693
}
7794
return cv.blockHash(number)
7895
}
7996

80-
// getReceipts returns the set of receipts belonging to the block at the given
97+
// Header returns the block header at the given block number.
98+
func (cv *ChainView) Header(number uint64) *types.Header {
99+
return cv.chain.GetHeader(cv.BlockHash(number), number)
100+
}
101+
102+
// Receipts returns the set of receipts belonging to the block at the given
81103
// block number.
82-
func (cv *ChainView) getReceipts(number uint64) types.Receipts {
83-
if number > cv.headNumber {
104+
func (cv *ChainView) Receipts(number uint64) types.Receipts {
105+
if number > cv.finishedHead {
84106
panic("invalid block number")
85107
}
86108
blockHash := cv.blockHash(number)
87109
if blockHash == (common.Hash{}) {
88-
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
110+
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.finishedHead)
89111
}
90112
return cv.chain.GetReceiptsByHash(blockHash)
91113
}
92114

115+
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
116+
if cv == nil || cv2 == nil {
117+
return common.Range[uint64]{}
118+
}
119+
var sharedLen uint64
120+
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++ {
121+
sharedLen = n + 1
122+
}
123+
return common.NewRange(0, sharedLen)
124+
}
125+
93126
// limitedView returns a new chain view that is a truncated version of the parent view.
94127
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
95-
if newHead >= cv.headNumber {
128+
if newHead >= cv.finishedHead {
96129
return cv
97130
}
98131
return NewChainView(cv.chain, newHead, cv.blockHash(newHead))
@@ -103,7 +136,7 @@ func equalViews(cv1, cv2 *ChainView) bool {
103136
if cv1 == nil || cv2 == nil {
104137
return false
105138
}
106-
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber)
139+
return cv1.finishedHead == cv2.finishedHead && cv1.BlockId(cv1.finishedHead) == cv2.BlockId(cv2.finishedHead)
107140
}
108141

109142
// matchViews returns true if the two chain views are equivalent up until the
@@ -113,13 +146,13 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
113146
if cv1 == nil || cv2 == nil {
114147
return false
115148
}
116-
if cv1.headNumber < number || cv2.headNumber < number {
149+
if cv1.finishedHead < number || cv2.finishedHead < number {
117150
return false
118151
}
119-
if number == cv1.headNumber || number == cv2.headNumber {
120-
return cv1.getBlockId(number) == cv2.getBlockId(number)
152+
if number == cv1.finishedHead || number == cv2.finishedHead {
153+
return cv1.BlockId(number) == cv2.BlockId(number)
121154
}
122-
return cv1.getBlockHash(number) == cv2.getBlockHash(number)
155+
return cv1.BlockHash(number) == cv2.BlockHash(number)
123156
}
124157

125158
// extendNonCanonical checks whether the previously known reverse list of head
@@ -128,7 +161,7 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
128161
// more hashes to the list.
129162
func (cv *ChainView) extendNonCanonical() bool {
130163
for {
131-
hash, number := cv.hashes[len(cv.hashes)-1], cv.headNumber-uint64(len(cv.hashes)-1)
164+
hash, number := cv.hashes[len(cv.hashes)-1], cv.finishedHead-uint64(len(cv.hashes)-1)
132165
if cv.chain.GetCanonicalHash(number) == hash {
133166
return true
134167
}
@@ -147,14 +180,14 @@ func (cv *ChainView) extendNonCanonical() bool {
147180

148181
// blockHash returns the given block hash without doing the head number check.
149182
func (cv *ChainView) blockHash(number uint64) common.Hash {
150-
if number+uint64(len(cv.hashes)) <= cv.headNumber {
183+
if number+uint64(len(cv.hashes)) <= cv.finishedHead {
151184
hash := cv.chain.GetCanonicalHash(number)
152185
if !cv.extendNonCanonical() {
153186
return common.Hash{}
154187
}
155-
if number+uint64(len(cv.hashes)) <= cv.headNumber {
188+
if number+uint64(len(cv.hashes)) <= cv.finishedHead {
156189
return hash
157190
}
158191
}
159-
return cv.hashes[cv.headNumber-number]
192+
return cv.hashes[cv.finishedHead-number]
160193
}

core/filtermaps/filtermaps.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
248248
f.targetView = initView
249249
if f.indexedRange.initialized {
250250
f.indexedView = f.initChainView(f.targetView)
251-
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.headNumber+1
251+
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.ProcessedHead()+1
252252
if !f.indexedRange.headIndexed {
253253
f.indexedRange.headDelimiter = 0
254254
}
@@ -299,7 +299,7 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
299299
log.Error("Could not initialize indexed chain view", "error", err)
300300
break
301301
}
302-
if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId {
302+
if lastBlockNumber <= chainView.ProcessedHead() && chainView.BlockId(lastBlockNumber) == lastBlockId {
303303
return chainView.limitedView(lastBlockNumber)
304304
}
305305
}
@@ -356,7 +356,7 @@ func (f *FilterMaps) init() error {
356356
for min < max {
357357
mid := (min + max + 1) / 2
358358
cp := checkpointList[mid-1]
359-
if cp.BlockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.BlockNumber) == cp.BlockId {
359+
if cp.BlockNumber <= f.targetView.ProcessedHead() && f.targetView.BlockId(cp.BlockNumber) == cp.BlockId {
360360
min = mid
361361
} else {
362362
max = mid - 1
@@ -497,7 +497,7 @@ func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
497497
}
498498
}
499499
// get block receipts
500-
receipts := f.indexedView.getReceipts(firstBlockNumber)
500+
receipts := f.indexedView.Receipts(firstBlockNumber)
501501
if receipts == nil {
502502
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
503503
}

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.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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func (f *FilterMaps) lastCanonicalSnapshotBefore(renderBefore uint32) *renderedM
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.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
146+
blockNumber <= f.targetView.ProcessedHead() && f.targetView.BlockId(blockNumber) == cp.lastBlockId &&
147147
cp.mapIndex < renderBefore && (best == nil || blockNumber > best.lastBlock) {
148148
best = cp
149149
}
@@ -171,8 +171,8 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(renderBefore uint32) (nextMa
171171
if err != nil {
172172
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
173173
}
174-
if lastBlock >= f.indexedView.headNumber || lastBlock >= f.targetView.headNumber ||
175-
lastBlockId != f.targetView.getBlockId(lastBlock) {
174+
if lastBlock >= f.indexedView.ProcessedHead() || lastBlock >= f.targetView.ProcessedHead() ||
175+
lastBlockId != f.targetView.BlockId(lastBlock) {
176176
// map is not full or inconsistent with targetView; roll back
177177
continue
178178
}
@@ -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,
@@ -261,7 +261,7 @@ func (r *mapRenderer) makeSnapshot() {
261261
filterMap: r.currentMap.filterMap.copy(),
262262
mapIndex: r.currentMap.mapIndex,
263263
lastBlock: r.iterator.blockNumber,
264-
lastBlockId: r.f.targetView.getBlockId(r.currentMap.lastBlock),
264+
lastBlockId: r.f.targetView.BlockId(r.currentMap.lastBlock),
265265
blockLvPtrs: r.currentMap.blockLvPtrs,
266266
finished: true,
267267
headDelimiter: r.iterator.lvIndex,
@@ -367,7 +367,7 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
367367
r.currentMap.finished = true
368368
r.currentMap.headDelimiter = r.iterator.lvIndex
369369
}
370-
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
370+
r.currentMap.lastBlockId = r.f.targetView.BlockId(r.currentMap.lastBlock)
371371
totalTime += time.Since(start)
372372
mapRenderTimer.Update(totalTime)
373373
mapLogValueMeter.Mark(logValuesProcessed)
@@ -563,8 +563,8 @@ func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) {
563563
lm := r.finishedMaps[r.finished.Last()]
564564
newRange.headIndexed = lm.finished
565565
if lm.finished {
566-
newRange.blocks.SetLast(r.f.targetView.headNumber)
567-
if lm.lastBlock != r.f.targetView.headNumber {
566+
newRange.blocks.SetLast(r.f.targetView.ProcessedHead())
567+
if lm.lastBlock != r.f.targetView.ProcessedHead() {
568568
panic("map rendering finished but last block != head block")
569569
}
570570
newRange.headDelimiter = lm.headDelimiter
@@ -662,8 +662,8 @@ var errUnindexedRange = errors.New("unindexed range")
662662
// given block's first log value entry (the block delimiter), according to the
663663
// current targetView.
664664
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logIterator, error) {
665-
if blockNumber > f.targetView.headNumber {
666-
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber)
665+
if blockNumber > f.targetView.ProcessedHead() {
666+
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.ProcessedHead())
667667
}
668668
if !f.indexedRange.blocks.Includes(blockNumber) {
669669
return nil, errUnindexedRange
@@ -679,7 +679,7 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logI
679679
}
680680
lvIndex--
681681
}
682-
finished := blockNumber == f.targetView.headNumber
682+
finished := blockNumber == f.targetView.ProcessedHead()
683683
l := &logIterator{
684684
chainView: f.targetView,
685685
params: &f.Params,
@@ -695,11 +695,11 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logI
695695
// newLogIteratorFromMapBoundary creates a logIterator starting at the given
696696
// map boundary, according to the current targetView.
697697
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
698-
if startBlock > f.targetView.headNumber {
699-
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber)
698+
if startBlock > f.targetView.ProcessedHead() {
699+
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.ProcessedHead())
700700
}
701701
// get block receipts
702-
receipts := f.targetView.getReceipts(startBlock)
702+
receipts := f.targetView.Receipts(startBlock)
703703
if receipts == nil {
704704
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
705705
}
@@ -766,7 +766,7 @@ func (l *logIterator) next() error {
766766
if l.delimiter {
767767
l.delimiter = false
768768
l.blockNumber++
769-
l.receipts = l.chainView.getReceipts(l.blockNumber)
769+
l.receipts = l.chainView.Receipts(l.blockNumber)
770770
if l.receipts == nil {
771771
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
772772
}
@@ -803,7 +803,7 @@ func (l *logIterator) enforceValidState() {
803803
}
804804
l.logIndex = 0
805805
}
806-
if l.blockNumber == l.chainView.headNumber {
806+
if l.blockNumber == l.chainView.ProcessedHead() {
807807
l.finished = true
808808
} else {
809809
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
@@ -125,7 +125,7 @@ func (fm *FilterMapsMatcherBackend) synced() {
125125
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
126126
}
127127
fm.syncCh <- SyncRange{
128-
HeadNumber: fm.f.targetView.headNumber,
128+
IndexedView: fm.f.indexedView,
129129
ValidBlocks: fm.validBlocks,
130130
IndexedBlocks: indexedBlocks,
131131
}
@@ -151,15 +151,15 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
151151
case <-ctx.Done():
152152
return SyncRange{}, ctx.Err()
153153
case <-fm.f.disabledCh:
154-
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
154+
return SyncRange{IndexedView: fm.f.indexedView}, nil
155155
}
156156
select {
157157
case vr := <-syncCh:
158158
return vr, nil
159159
case <-ctx.Done():
160160
return SyncRange{}, ctx.Err()
161161
case <-fm.f.disabledCh:
162-
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
162+
return SyncRange{IndexedView: fm.f.indexedView}, nil
163163
}
164164
}
165165

eth/api_backend.go

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

440+
func (b *EthAPIBackend) CurrentView() *filtermaps.ChainView {
441+
head := b.eth.blockchain.CurrentBlock()
442+
if head == nil {
443+
return nil
444+
}
445+
return filtermaps.NewChainView(b.eth.blockchain, head.Number.Uint64(), head.Hash())
446+
}
447+
440448
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
441449
return b.eth.filterMaps.NewMatcherBackend()
442450
}

0 commit comments

Comments
 (0)