diff --git a/core/filtermaps/chain_view.go b/core/filtermaps/chain_view.go index 1d606b763e..bc0de47ded 100644 --- a/core/filtermaps/chain_view.go +++ b/core/filtermaps/chain_view.go @@ -22,119 +22,109 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// chainView represents an immutable view of a chain with a block hash, a -// block id and a set of receipts associated to each block number. Block id -// can be any unique identifier of the blocks. -// Note that id and receipts are expected to be available up to headNumber -// while the canonical block hash is only expected up to headNumber-1 so that -// it can be implemented by the block builder while the processed head hash -// is not known yet. -type chainView interface { - headNumber() uint64 - getBlockHash(number uint64) common.Hash - getBlockId(number uint64) common.Hash - getReceipts(number uint64) types.Receipts -} - -// equalViews returns true if the two chain views are equivalent. -func equalViews(cv1, cv2 chainView) bool { - if cv1 == nil || cv2 == nil { - return false - } - head1, head2 := cv1.headNumber(), cv2.headNumber() - return head1 == head2 && cv1.getBlockId(head1) == cv2.getBlockId(head2) -} - -// matchViews returns true if the two chain views are equivalent up until the -// specified block number. If the specified number is higher than one of the -// heads then false is returned. -func matchViews(cv1, cv2 chainView, number uint64) bool { - if cv1 == nil || cv2 == nil { - return false - } - head1 := cv1.headNumber() - if head1 < number { - return false - } - head2 := cv2.headNumber() - if head2 < number { - return false - } - if number == head1 || number == head2 { - return cv1.getBlockId(number) == cv2.getBlockId(number) - } - return cv1.getBlockHash(number) == cv2.getBlockHash(number) -} - -// blockchain defines functions required by the FilterMaps log indexer. +// blockchain represents the underlying blockchain of ChainView. type blockchain interface { GetHeader(hash common.Hash, number uint64) *types.Header GetCanonicalHash(number uint64) common.Hash GetReceiptsByHash(hash common.Hash) types.Receipts } -// StoredChainView implements chainView based on a given blockchain. +// ChainView represents an immutable view of a chain with a block id and a set +// of receipts associated to each block number and a block hash associated with +// all block numbers except the head block. This is because in the future +// ChainView might represent a view where the head block is currently being +// created. Block id is a unique identifier that can also be calculated for the +// head block. // Note that the view's head does not have to be the current canonical head // of the underlying blockchain, it should only possess the block headers // and receipts up until the expected chain view head. -// Also note that this implementation uses the canonical block hash as block -// id which works as long as the log index structure is not hashed into the -// block headers. Starting from the fork that hashes the log index to the -// block the id needs to be based on a set of fields that exactly defines the -// block but does not include the log index root itself. -type StoredChainView struct { - chain blockchain - head uint64 - hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash +type ChainView struct { + chain blockchain + headNumber uint64 + hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash } -// NewStoredChainView creates a new StoredChainView. -func NewStoredChainView(chain blockchain, number uint64, hash common.Hash) *StoredChainView { - cv := &StoredChainView{ - chain: chain, - head: number, - hashes: []common.Hash{hash}, +// NewChainView creates a new ChainView. +func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView { + cv := &ChainView{ + chain: chain, + headNumber: number, + hashes: []common.Hash{hash}, } cv.extendNonCanonical() return cv } -// headNumber implements chainView. -func (cv *StoredChainView) headNumber() uint64 { - return cv.head -} - -// getBlockHash implements chainView. -func (cv *StoredChainView) getBlockHash(number uint64) common.Hash { - if number >= cv.head { +// getBlockHash returns the block hash belonging to the given block number. +// Note that the hash of the head block is not returned because ChainView might +// represent a view where the head block is currently being created. +func (cv *ChainView) getBlockHash(number uint64) common.Hash { + if number >= cv.headNumber { panic("invalid block number") } return cv.blockHash(number) } -// getBlockId implements chainView. -func (cv *StoredChainView) getBlockId(number uint64) common.Hash { - if number > cv.head { +// getBlockId returns the unique block id belonging to the given block number. +// Note that it is currently equal to the block hash. In the future it might +// be a different id for future blocks if the log index root becomes part of +// consensus and therefore rendering the index with the new head will happen +// before the hash of that new head is available. +func (cv *ChainView) getBlockId(number uint64) common.Hash { + if number > cv.headNumber { panic("invalid block number") } return cv.blockHash(number) } -// getReceipts implements chainView. -func (cv *StoredChainView) getReceipts(number uint64) types.Receipts { - if number > cv.head { +// getReceipts returns the set of receipts belonging to the block at the given +// block number. +func (cv *ChainView) getReceipts(number uint64) types.Receipts { + if number > cv.headNumber { panic("invalid block number") } return cv.chain.GetReceiptsByHash(cv.blockHash(number)) } +// limitedView returns a new chain view that is a truncated version of the parent view. +func (cv *ChainView) limitedView(newHead uint64) *ChainView { + if newHead >= cv.headNumber { + return cv + } + return NewChainView(cv.chain, newHead, cv.blockHash(newHead)) +} + +// equalViews returns true if the two chain views are equivalent. +func equalViews(cv1, cv2 *ChainView) bool { + if cv1 == nil || cv2 == nil { + return false + } + return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber) +} + +// matchViews returns true if the two chain views are equivalent up until the +// specified block number. If the specified number is higher than one of the +// heads then false is returned. +func matchViews(cv1, cv2 *ChainView, number uint64) bool { + if cv1 == nil || cv2 == nil { + return false + } + if cv1.headNumber < number || cv2.headNumber < number { + return false + } + if number == cv1.headNumber || number == cv2.headNumber { + return cv1.getBlockId(number) == cv2.getBlockId(number) + } + return cv1.getBlockHash(number) == cv2.getBlockHash(number) +} + // extendNonCanonical checks whether the previously known reverse list of head // hashes still ends with one that is canonical on the underlying blockchain. // If necessary then it traverses further back on the header chain and adds // more hashes to the list. -func (cv *StoredChainView) extendNonCanonical() bool { +func (cv *ChainView) extendNonCanonical() bool { for { - hash, number := cv.hashes[len(cv.hashes)-1], cv.head-uint64(len(cv.hashes)-1) + hash, number := cv.hashes[len(cv.hashes)-1], cv.headNumber-uint64(len(cv.hashes)-1) if cv.chain.GetCanonicalHash(number) == hash { return true } @@ -152,61 +142,15 @@ func (cv *StoredChainView) extendNonCanonical() bool { } // blockHash returns the given block hash without doing the head number check. -func (cv *StoredChainView) blockHash(number uint64) common.Hash { - if number+uint64(len(cv.hashes)) <= cv.head { +func (cv *ChainView) blockHash(number uint64) common.Hash { + if number+uint64(len(cv.hashes)) <= cv.headNumber { hash := cv.chain.GetCanonicalHash(number) if !cv.extendNonCanonical() { return common.Hash{} } - if number+uint64(len(cv.hashes)) <= cv.head { + if number+uint64(len(cv.hashes)) <= cv.headNumber { return hash } } - return cv.hashes[cv.head-number] -} - -// limitedChainView wraps a chainView and truncates it at a given head number. -type limitedChainView struct { - parent chainView - head uint64 -} - -// newLimitedChainView returns a truncated view of the given parent. -func newLimitedChainView(parent chainView, headNumber uint64) chainView { - if headNumber >= parent.headNumber() { - return parent - } - return &limitedChainView{ - parent: parent, - head: headNumber, - } -} - -// headNumber implements chainView. -func (cv *limitedChainView) headNumber() uint64 { - return cv.head -} - -// getBlockHash implements chainView. -func (cv *limitedChainView) getBlockHash(number uint64) common.Hash { - if number >= cv.head { - panic("invalid block number") - } - return cv.parent.getBlockHash(number) -} - -// getBlockId implements chainView. -func (cv *limitedChainView) getBlockId(number uint64) common.Hash { - if number > cv.head { - panic("invalid block number") - } - return cv.parent.getBlockId(number) -} - -// getReceipts implements chainView. -func (cv *limitedChainView) getReceipts(number uint64) types.Receipts { - if number > cv.head { - panic("invalid block number") - } - return cv.parent.getReceipts(number) + return cv.hashes[cv.headNumber-number] } diff --git a/core/filtermaps/filtermaps.go b/core/filtermaps/filtermaps.go index 73eb9fd67f..090347bd2f 100644 --- a/core/filtermaps/filtermaps.go +++ b/core/filtermaps/filtermaps.go @@ -62,7 +62,7 @@ type FilterMaps struct { // Matcher backend can read them under indexLock read lock. indexLock sync.RWMutex filterMapsRange - indexedView chainView // always consistent with the log index + indexedView *ChainView // always consistent with the log index // also accessed by indexer and matcher backend but no locking needed. filterMapCache *lru.Cache[uint32, filterMap] @@ -86,12 +86,12 @@ type FilterMaps struct { ptrHeadIndex, ptrTailIndex, ptrTailUnindexBlock uint64 ptrTailUnindexMap uint32 - targetView chainView + targetView *ChainView matcherSyncRequest *FilterMapsMatcherBackend finalBlock, lastFinal uint64 lastFinalEpoch uint32 stop bool - TargetViewCh chan chainView + TargetViewCh chan *ChainView FinalBlockCh chan uint64 BlockProcessingCh chan bool blockProcessing bool @@ -174,7 +174,7 @@ type lastBlockOfMap struct { } // NewFilterMaps creates a new FilterMaps and starts the indexer. -func NewFilterMaps(db ethdb.KeyValueStore, initView chainView, params Params, history, unindexLimit uint64, noHistory bool, exportFileName string) *FilterMaps { +func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, params Params, history, unindexLimit uint64, noHistory bool, exportFileName string) *FilterMaps { rs, initialized, err := rawdb.ReadFilterMapsRange(db) if err != nil { log.Error("Error reading log index range", "error", err) @@ -184,7 +184,7 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView chainView, params Params, hi db: db, closeCh: make(chan struct{}), waitIdleCh: make(chan chan bool), - TargetViewCh: make(chan chainView), + TargetViewCh: make(chan *ChainView), FinalBlockCh: make(chan uint64), BlockProcessingCh: make(chan bool), history: history, @@ -213,7 +213,7 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView chainView, params Params, hi f.targetView = initView if f.initialized { f.indexedView = f.initChainView(f.targetView) - f.headBlockIndexed = f.afterLastIndexedBlock == f.indexedView.headNumber()+1 + f.headBlockIndexed = f.afterLastIndexedBlock == f.indexedView.headNumber+1 if !f.headBlockIndexed { f.headBlockDelimiter = 0 } @@ -248,7 +248,7 @@ func (f *FilterMaps) Stop() { // on the last block of stored maps. // Note that the returned view might be shorter than the existing index if // the latest maps are not consistent with targetView. -func (f *FilterMaps) initChainView(chainView chainView) chainView { +func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView { mapIndex := f.afterLastRenderedMap for { var ok bool @@ -261,11 +261,11 @@ func (f *FilterMaps) initChainView(chainView chainView) chainView { log.Error("Could not initialize indexed chain view", "error", err) break } - if lastBlockNumber <= chainView.headNumber() && chainView.getBlockId(lastBlockNumber) == lastBlockId { - return newLimitedChainView(chainView, lastBlockNumber) + if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId { + return chainView.limitedView(lastBlockNumber) } } - return newLimitedChainView(chainView, 0) + return chainView.limitedView(0) } // reset un-initializes the FilterMaps structure and removes all related data from @@ -298,7 +298,7 @@ func (f *FilterMaps) init() error { for min < max { mid := (min + max + 1) / 2 cp := checkpointList[mid-1] - if cp.blockNumber <= f.targetView.headNumber() && f.targetView.getBlockId(cp.blockNumber) == cp.blockId { + if cp.blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.blockNumber) == cp.blockId { min = mid } else { max = mid - 1 @@ -367,7 +367,7 @@ func (f *FilterMaps) removeDbWithPrefix(prefix []byte, action string) bool { // setRange updates the indexed chain view and covered range and also adds the // changes to the given batch. // Note that this function assumes that the index write lock is being held. -func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView chainView, newRange filterMapsRange) { +func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange) { f.indexedView = newView f.filterMapsRange = newRange f.updateMatchersValidRange() diff --git a/core/filtermaps/indexer.go b/core/filtermaps/indexer.go index cc0ed7488a..ac554c7b6c 100644 --- a/core/filtermaps/indexer.go +++ b/core/filtermaps/indexer.go @@ -132,7 +132,7 @@ func (f *FilterMaps) processSingleEvent(blocking bool) bool { } // setTargetView updates the target chain view of the iterator. -func (f *FilterMaps) setTargetView(targetView chainView) { +func (f *FilterMaps) setTargetView(targetView *ChainView) { if equalViews(f.targetView, targetView) { return } @@ -170,7 +170,7 @@ func (f *FilterMaps) tryIndexHead() bool { log.Info("Log index head rendering in progress", "first block", f.firstIndexedBlock, "last block", f.afterLastIndexedBlock-1, "processed", f.afterLastIndexedBlock-f.ptrHeadIndex, - "remaining", f.indexedView.headNumber()+1-f.afterLastIndexedBlock, + "remaining", f.indexedView.headNumber+1-f.afterLastIndexedBlock, "elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt))) f.loggedHeadIndex = true f.lastLogHeadIndex = time.Now() @@ -323,10 +323,10 @@ func (f *FilterMaps) needTailEpoch(epoch uint32) bool { // tailTargetBlock returns the target value for the tail block number according // to the log history parameter and the current index head. func (f *FilterMaps) tailTargetBlock() uint64 { - if f.history == 0 || f.indexedView.headNumber() < f.history { + if f.history == 0 || f.indexedView.headNumber < f.history { return 0 } - return f.indexedView.headNumber() + 1 - f.history + return f.indexedView.headNumber + 1 - f.history } // tailPartialBlocks returns the number of rendered blocks in the partially diff --git a/core/filtermaps/indexer_test.go b/core/filtermaps/indexer_test.go index 980998d1fe..1ec681aa0c 100644 --- a/core/filtermaps/indexer_test.go +++ b/core/filtermaps/indexer_test.go @@ -230,7 +230,7 @@ func (ts *testSetup) setHistory(history uint64, noHistory bool) { ts.fm.Stop() } head := ts.chain.CurrentBlock() - ts.fm = NewFilterMaps(ts.db, NewStoredChainView(ts.chain, head.Number.Uint64(), head.Hash()), ts.params, history, 1, noHistory, "") + ts.fm = NewFilterMaps(ts.db, NewChainView(ts.chain, head.Number.Uint64(), head.Hash()), ts.params, history, 1, noHistory, "") ts.fm.testDisableSnapshots = ts.testDisableSnapshots ts.fm.Start() } @@ -414,7 +414,7 @@ func (tc *testChain) setTargetHead() { head := tc.CurrentBlock() if tc.ts.fm != nil { if !tc.ts.fm.noHistory { - tc.ts.fm.TargetViewCh <- NewStoredChainView(tc, head.Number.Uint64(), head.Hash()) + tc.ts.fm.TargetViewCh <- NewChainView(tc, head.Number.Uint64(), head.Hash()) } } } diff --git a/core/filtermaps/map_renderer.go b/core/filtermaps/map_renderer.go index ec9ce9993f..b1d027066b 100644 --- a/core/filtermaps/map_renderer.go +++ b/core/filtermaps/map_renderer.go @@ -140,7 +140,7 @@ func (f *FilterMaps) lastCanonicalSnapshotBefore(afterLastMap uint32) *renderedM var best *renderedMap for _, blockNumber := range f.renderSnapshots.Keys() { if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.afterLastIndexedBlock && - blockNumber <= f.targetView.headNumber() && f.targetView.getBlockId(blockNumber) == cp.lastBlockId && + blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId && cp.mapIndex < afterLastMap && (best == nil || blockNumber > best.lastBlock) { best = cp } @@ -168,7 +168,7 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(afterLastMap uint32) (nextMa if err != nil { return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err) } - if lastBlock >= f.indexedView.headNumber() || lastBlock >= f.targetView.headNumber() || + if lastBlock >= f.indexedView.headNumber || lastBlock >= f.targetView.headNumber || lastBlockId != f.targetView.getBlockId(lastBlock) { // map is not full or inconsistent with targetView; roll back continue @@ -533,8 +533,8 @@ func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) { lm := r.finishedMaps[r.afterLastFinished-1] newRange.headBlockIndexed = lm.finished if lm.finished { - newRange.afterLastIndexedBlock = r.f.targetView.headNumber() + 1 - if lm.lastBlock != r.f.targetView.headNumber() { + newRange.afterLastIndexedBlock = r.f.targetView.headNumber + 1 + if lm.lastBlock != r.f.targetView.headNumber { panic("map rendering finished but last block != head block") } newRange.headBlockDelimiter = lm.headDelimiter @@ -608,7 +608,7 @@ func (fmr *filterMapsRange) addRenderedRange(firstRendered, afterLastRendered, a // logIterator iterates on the linear log value index range. type logIterator struct { - chainView chainView + chainView *ChainView blockNumber uint64 receipts types.Receipts blockStart, delimiter, finished bool @@ -622,8 +622,8 @@ var errUnindexedRange = errors.New("unindexed range") // given block's first log value entry (the block delimiter), according to the // current targetView. func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logIterator, error) { - if blockNumber > f.targetView.headNumber() { - return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber()) + if blockNumber > f.targetView.headNumber { + return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber) } if blockNumber < f.firstIndexedBlock || blockNumber >= f.afterLastIndexedBlock { return nil, errUnindexedRange @@ -639,7 +639,7 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logI } lvIndex-- } - finished := blockNumber == f.targetView.headNumber() + finished := blockNumber == f.targetView.headNumber return &logIterator{ chainView: f.targetView, blockNumber: blockNumber, @@ -652,8 +652,8 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logI // newLogIteratorFromMapBoundary creates a logIterator starting at the given // map boundary, according to the current targetView. func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) { - if startBlock > f.targetView.headNumber() { - return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber()) + if startBlock > f.targetView.headNumber { + return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber) } // get block receipts receipts := f.targetView.getReceipts(startBlock) @@ -687,7 +687,7 @@ func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, // updateChainView updates the iterator's chain view if it still matches the // previous view at the current position. Returns true if successful. -func (l *logIterator) updateChainView(cv chainView) bool { +func (l *logIterator) updateChainView(cv *ChainView) bool { if !matchViews(cv, l.chainView, l.blockNumber) { return false } @@ -744,7 +744,7 @@ func (l *logIterator) nextValid() { } l.logIndex = 0 } - if l.blockNumber == l.chainView.headNumber() { + if l.blockNumber == l.chainView.headNumber { l.finished = true } else { l.delimiter = true diff --git a/core/filtermaps/matcher_backend.go b/core/filtermaps/matcher_backend.go index 3829c2dd00..c89f4d1480 100644 --- a/core/filtermaps/matcher_backend.go +++ b/core/filtermaps/matcher_backend.go @@ -133,7 +133,7 @@ func (fm *FilterMapsMatcherBackend) synced() { indexed, lastIndexed = true, fm.f.afterLastIndexedBlock-subLastIndexed-1 } fm.syncCh <- SyncRange{ - HeadNumber: fm.f.indexedView.headNumber(), + HeadNumber: fm.f.indexedView.headNumber, Valid: fm.valid, FirstValid: fm.firstValid, LastValid: fm.lastValid, @@ -159,7 +159,7 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange if fm.f.targetView == nil { return SyncRange{}, errors.New("canonical chain head not available") } - return SyncRange{HeadNumber: fm.f.targetView.headNumber()}, nil + return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil } syncCh := make(chan SyncRange, 1) fm.f.matchersLock.Lock()