core/filtermaps: removed chainView interface and limitedChainView

This commit is contained in:
Zsolt Felfoldi 2025-03-12 14:35:38 +01:00
parent 131883e903
commit 4ece8f50f8
6 changed files with 103 additions and 159 deletions

View file

@ -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]
}

View file

@ -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()

View file

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

View file

@ -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())
}
}
}

View file

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

View file

@ -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()