github.com/ethereum/go-ethereum@v1.16.1/eth/filters/filter.go (about)

     1  // Copyright 2014 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package filters
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"math"
    23  	"math/big"
    24  	"slices"
    25  	"time"
    26  
    27  	"github.com/ethereum/go-ethereum/common"
    28  	"github.com/ethereum/go-ethereum/core/filtermaps"
    29  	"github.com/ethereum/go-ethereum/core/history"
    30  	"github.com/ethereum/go-ethereum/core/types"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/rpc"
    33  )
    34  
    35  // Filter can be used to retrieve and filter logs.
    36  type Filter struct {
    37  	sys *FilterSystem
    38  
    39  	addresses []common.Address
    40  	topics    [][]common.Hash
    41  
    42  	block      *common.Hash // Block hash if filtering a single block
    43  	begin, end int64        // Range interval if filtering multiple blocks
    44  
    45  	rangeLogsTestHook chan rangeLogsTestEvent
    46  }
    47  
    48  // NewRangeFilter creates a new filter which uses a bloom filter on blocks to
    49  // figure out whether a particular block is interesting or not.
    50  func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
    51  	// Create a generic filter and convert it into a range filter
    52  	filter := newFilter(sys, addresses, topics)
    53  	filter.begin = begin
    54  	filter.end = end
    55  
    56  	return filter
    57  }
    58  
    59  // NewBlockFilter creates a new filter which directly inspects the contents of
    60  // a block to figure out whether it is interesting or not.
    61  func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter {
    62  	// Create a generic filter and convert it into a block filter
    63  	filter := newFilter(sys, addresses, topics)
    64  	filter.block = &block
    65  	return filter
    66  }
    67  
    68  // newFilter creates a generic filter that can either filter based on a block hash,
    69  // or based on range queries. The search criteria needs to be explicitly set.
    70  func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter {
    71  	return &Filter{
    72  		sys:       sys,
    73  		addresses: addresses,
    74  		topics:    topics,
    75  	}
    76  }
    77  
    78  // Logs searches the blockchain for matching log entries, returning all from the
    79  // first block that contains matches, updating the start of the filter accordingly.
    80  func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
    81  	// If we're doing singleton block filtering, execute and return
    82  	if f.block != nil {
    83  		header, err := f.sys.backend.HeaderByHash(ctx, *f.block)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		if header == nil {
    88  			return nil, errors.New("unknown block")
    89  		}
    90  		if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
    91  			return nil, &history.PrunedHistoryError{}
    92  		}
    93  		return f.blockLogs(ctx, header)
    94  	}
    95  
    96  	// Disallow pending logs.
    97  	if f.begin == rpc.PendingBlockNumber.Int64() || f.end == rpc.PendingBlockNumber.Int64() {
    98  		return nil, errPendingLogsUnsupported
    99  	}
   100  
   101  	resolveSpecial := func(number int64) (uint64, error) {
   102  		switch number {
   103  		case rpc.LatestBlockNumber.Int64():
   104  			// when searching from and/or until the current head, we resolve it
   105  			// to MaxUint64 which is translated by rangeLogs to the actual head
   106  			// in each iteration, ensuring that the head block will be searched
   107  			// even if the chain is updated during search.
   108  			return math.MaxUint64, nil
   109  		case rpc.FinalizedBlockNumber.Int64():
   110  			hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber)
   111  			if hdr == nil {
   112  				return 0, errors.New("finalized header not found")
   113  			}
   114  			return hdr.Number.Uint64(), nil
   115  		case rpc.SafeBlockNumber.Int64():
   116  			hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber)
   117  			if hdr == nil {
   118  				return 0, errors.New("safe header not found")
   119  			}
   120  			return hdr.Number.Uint64(), nil
   121  		case rpc.EarliestBlockNumber.Int64():
   122  			earliest := f.sys.backend.HistoryPruningCutoff()
   123  			hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(earliest))
   124  			if hdr == nil {
   125  				return 0, errors.New("earliest header not found")
   126  			}
   127  			return hdr.Number.Uint64(), nil
   128  		default:
   129  			if number < 0 {
   130  				return 0, errors.New("negative block number")
   131  			}
   132  			return uint64(number), nil
   133  		}
   134  	}
   135  
   136  	// range query need to resolve the special begin/end block number
   137  	begin, err := resolveSpecial(f.begin)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	end, err := resolveSpecial(f.end)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	return f.rangeLogs(ctx, begin, end)
   146  }
   147  
   148  const (
   149  	rangeLogsTestDone      = iota // zero range
   150  	rangeLogsTestSync             // before sync; zero range
   151  	rangeLogsTestSynced           // after sync; valid blocks range
   152  	rangeLogsTestIndexed          // individual search range
   153  	rangeLogsTestUnindexed        // individual search range
   154  	rangeLogsTestResults          // results range after search iteration
   155  	rangeLogsTestReorg            // results range trimmed by reorg
   156  )
   157  
   158  type rangeLogsTestEvent struct {
   159  	event  int
   160  	blocks common.Range[uint64]
   161  }
   162  
   163  // searchSession represents a single search session.
   164  type searchSession struct {
   165  	ctx       context.Context
   166  	filter    *Filter
   167  	mb        filtermaps.MatcherBackend
   168  	syncRange filtermaps.SyncRange  // latest synchronized state with the matcher
   169  	chainView *filtermaps.ChainView // can be more recent than the indexed view in syncRange
   170  	// block ranges always refer to the current chainView
   171  	firstBlock, lastBlock uint64               // specified search range; MaxUint64 means latest block
   172  	searchRange           common.Range[uint64] // actual search range; end trimmed to latest head
   173  	matchRange            common.Range[uint64] // range in which we have results (subset of searchRange)
   174  	matches               []*types.Log         // valid set of matches in matchRange
   175  	forceUnindexed        bool                 // revert to unindexed search
   176  }
   177  
   178  // newSearchSession returns a new searchSession.
   179  func newSearchSession(ctx context.Context, filter *Filter, mb filtermaps.MatcherBackend, firstBlock, lastBlock uint64) (*searchSession, error) {
   180  	s := &searchSession{
   181  		ctx:        ctx,
   182  		filter:     filter,
   183  		mb:         mb,
   184  		firstBlock: firstBlock,
   185  		lastBlock:  lastBlock,
   186  	}
   187  	// enforce a consistent state before starting the search in order to be able
   188  	// to determine valid range later
   189  	var err error
   190  	s.syncRange, err = s.mb.SyncLogIndex(s.ctx)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	if err := s.updateChainView(); err != nil {
   195  		return nil, err
   196  	}
   197  	return s, nil
   198  }
   199  
   200  // updateChainView updates to the latest view of the underlying chain and sets
   201  // searchRange by replacing MaxUint64 (meaning latest block) with actual head
   202  // number in the specified search range.
   203  // If the session already had an existing chain view and set of matches then
   204  // it also trims part of the match set that a chain reorg might have invalidated.
   205  func (s *searchSession) updateChainView() error {
   206  	// update chain view based on current chain head (might be more recent than
   207  	// the indexed view of syncRange as the indexer updates it asynchronously
   208  	// with some delay
   209  	newChainView := s.filter.sys.backend.CurrentView()
   210  	if newChainView == nil {
   211  		return errors.New("head block not available")
   212  	}
   213  	head := newChainView.HeadNumber()
   214  
   215  	// update actual search range based on current head number
   216  	firstBlock, lastBlock := s.firstBlock, s.lastBlock
   217  	if firstBlock == math.MaxUint64 {
   218  		firstBlock = head
   219  	}
   220  	if lastBlock == math.MaxUint64 {
   221  		lastBlock = head
   222  	}
   223  	if firstBlock > lastBlock || lastBlock > head {
   224  		return errInvalidBlockRange
   225  	}
   226  	s.searchRange = common.NewRange(firstBlock, lastBlock+1-firstBlock)
   227  
   228  	// Trim existing match set in case a reorg may have invalidated some results
   229  	if !s.matchRange.IsEmpty() {
   230  		trimRange := newChainView.SharedRange(s.chainView).Intersection(s.searchRange)
   231  		s.matchRange, s.matches = s.trimMatches(trimRange, s.matchRange, s.matches)
   232  	}
   233  	s.chainView = newChainView
   234  	return nil
   235  }
   236  
   237  // trimMatches removes any entries from the specified set of matches that is
   238  // outside the given range.
   239  func (s *searchSession) trimMatches(trimRange, matchRange common.Range[uint64], matches []*types.Log) (common.Range[uint64], []*types.Log) {
   240  	newRange := matchRange.Intersection(trimRange)
   241  	if newRange == matchRange {
   242  		return matchRange, matches
   243  	}
   244  	if newRange.IsEmpty() {
   245  		return newRange, nil
   246  	}
   247  	for len(matches) > 0 && matches[0].BlockNumber < newRange.First() {
   248  		matches = matches[1:]
   249  	}
   250  	for len(matches) > 0 && matches[len(matches)-1].BlockNumber > newRange.Last() {
   251  		matches = matches[:len(matches)-1]
   252  	}
   253  	return newRange, matches
   254  }
   255  
   256  // searchInRange performs a single range search, either indexed or unindexed.
   257  func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) (common.Range[uint64], []*types.Log, error) {
   258  	if indexed {
   259  		if s.filter.rangeLogsTestHook != nil {
   260  			s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestIndexed, r}
   261  		}
   262  		results, err := s.filter.indexedLogs(s.ctx, s.mb, r.First(), r.Last())
   263  		if err != nil && !errors.Is(err, filtermaps.ErrMatchAll) {
   264  			return common.Range[uint64]{}, nil, err
   265  		}
   266  		if err == nil {
   267  			// sync with filtermaps matcher
   268  			if s.filter.rangeLogsTestHook != nil {
   269  				s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSync, common.Range[uint64]{}}
   270  			}
   271  			var syncErr error
   272  			if s.syncRange, syncErr = s.mb.SyncLogIndex(s.ctx); syncErr != nil {
   273  				return common.Range[uint64]{}, nil, syncErr
   274  			}
   275  			if s.filter.rangeLogsTestHook != nil {
   276  				s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSynced, s.syncRange.ValidBlocks}
   277  			}
   278  			// discard everything that might be invalid
   279  			trimRange := s.syncRange.ValidBlocks.Intersection(s.chainView.SharedRange(s.syncRange.IndexedView))
   280  			matchRange, matches := s.trimMatches(trimRange, r, results)
   281  			return matchRange, matches, nil
   282  		}
   283  		// "match all" filters are not supported by filtermaps; fall back to
   284  		// unindexed search which is the most efficient in this case
   285  		s.forceUnindexed = true
   286  		// fall through to unindexed case
   287  	}
   288  	if s.filter.rangeLogsTestHook != nil {
   289  		s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestUnindexed, r}
   290  	}
   291  	matches, err := s.filter.unindexedLogs(s.ctx, s.chainView, r.First(), r.Last())
   292  	if err != nil {
   293  		return common.Range[uint64]{}, nil, err
   294  	}
   295  	return r, matches, nil
   296  }
   297  
   298  // doSearchIteration performs a search on a range missing from an incomplete set
   299  // of results, adds the new section and removes invalidated entries.
   300  func (s *searchSession) doSearchIteration() error {
   301  	switch {
   302  	case s.matchRange.IsEmpty():
   303  		// no results yet; try search in entire range
   304  		indexedSearchRange := s.searchRange.Intersection(s.syncRange.IndexedBlocks)
   305  		if s.forceUnindexed = indexedSearchRange.IsEmpty(); !s.forceUnindexed {
   306  			// indexed search on the intersection of indexed and searched range
   307  			matchRange, matches, err := s.searchInRange(indexedSearchRange, true)
   308  			if err != nil {
   309  				return err
   310  			}
   311  			s.matchRange = matchRange
   312  			s.matches = matches
   313  			return nil
   314  		} else {
   315  			// no intersection of indexed and searched range; unindexed search on
   316  			// the whole searched range
   317  			matchRange, matches, err := s.searchInRange(s.searchRange, false)
   318  			if err != nil {
   319  				return err
   320  			}
   321  			s.matchRange = matchRange
   322  			s.matches = matches
   323  			return nil
   324  		}
   325  
   326  	case !s.matchRange.IsEmpty() && s.matchRange.First() > s.searchRange.First():
   327  		// Results are available, but the tail section is missing. Perform an unindexed
   328  		// search for the missing tail, while still allowing indexed search for the head.
   329  		//
   330  		// The unindexed search is necessary because the tail portion of the indexes
   331  		// has been pruned.
   332  		tailRange := common.NewRange(s.searchRange.First(), s.matchRange.First()-s.searchRange.First())
   333  		_, tailMatches, err := s.searchInRange(tailRange, false)
   334  		if err != nil {
   335  			return err
   336  		}
   337  		s.matches = append(tailMatches, s.matches...)
   338  		s.matchRange = tailRange.Union(s.matchRange)
   339  		return nil
   340  
   341  	case !s.matchRange.IsEmpty() && s.matchRange.First() == s.searchRange.First() && s.searchRange.AfterLast() > s.matchRange.AfterLast():
   342  		// Results are available, but the head section is missing. Try to perform
   343  		// the indexed search for the missing head, or fallback to unindexed search
   344  		// if the tail portion of indexed range has been pruned.
   345  		headRange := common.NewRange(s.matchRange.AfterLast(), s.searchRange.AfterLast()-s.matchRange.AfterLast())
   346  		if !s.forceUnindexed {
   347  			indexedHeadRange := headRange.Intersection(s.syncRange.IndexedBlocks)
   348  			if !indexedHeadRange.IsEmpty() && indexedHeadRange.First() == headRange.First() {
   349  				headRange = indexedHeadRange
   350  			} else {
   351  				// The tail portion of the indexes has been pruned, falling back
   352  				// to unindexed search.
   353  				s.forceUnindexed = true
   354  			}
   355  		}
   356  		headMatchRange, headMatches, err := s.searchInRange(headRange, !s.forceUnindexed)
   357  		if err != nil {
   358  			return err
   359  		}
   360  		if headMatchRange.First() != s.matchRange.AfterLast() {
   361  			// improbable corner case, first part of new head range invalidated by tail unindexing
   362  			s.matches, s.matchRange = headMatches, headMatchRange
   363  			return nil
   364  		}
   365  		s.matches = append(s.matches, headMatches...)
   366  		s.matchRange = s.matchRange.Union(headMatchRange)
   367  		return nil
   368  
   369  	default:
   370  		panic("invalid search session state")
   371  	}
   372  }
   373  
   374  func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([]*types.Log, error) {
   375  	if f.rangeLogsTestHook != nil {
   376  		defer func() {
   377  			f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestDone, common.Range[uint64]{}}
   378  			close(f.rangeLogsTestHook)
   379  		}()
   380  	}
   381  
   382  	if firstBlock > lastBlock {
   383  		return nil, nil
   384  	}
   385  	mb := f.sys.backend.NewMatcherBackend()
   386  	defer mb.Close()
   387  
   388  	session, err := newSearchSession(ctx, f, mb, firstBlock, lastBlock)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  	for session.searchRange != session.matchRange {
   393  		if err := session.doSearchIteration(); err != nil {
   394  			return nil, err
   395  		}
   396  		if f.rangeLogsTestHook != nil {
   397  			f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestResults, session.matchRange}
   398  		}
   399  		mr := session.matchRange
   400  		if err := session.updateChainView(); err != nil {
   401  			return nil, err
   402  		}
   403  		if f.rangeLogsTestHook != nil && session.matchRange != mr {
   404  			f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestReorg, session.matchRange}
   405  		}
   406  	}
   407  	return session.matches, nil
   408  }
   409  
   410  func (f *Filter) indexedLogs(ctx context.Context, mb filtermaps.MatcherBackend, begin, end uint64) ([]*types.Log, error) {
   411  	start := time.Now()
   412  	potentialMatches, err := filtermaps.GetPotentialMatches(ctx, mb, begin, end, f.addresses, f.topics)
   413  	matches := filterLogs(potentialMatches, nil, nil, f.addresses, f.topics)
   414  	log.Trace("Performed indexed log search", "begin", begin, "end", end, "true matches", len(matches), "false positives", len(potentialMatches)-len(matches), "elapsed", common.PrettyDuration(time.Since(start)))
   415  	return matches, err
   416  }
   417  
   418  // unindexedLogs returns the logs matching the filter criteria based on raw block
   419  // iteration and bloom matching.
   420  func (f *Filter) unindexedLogs(ctx context.Context, chainView *filtermaps.ChainView, begin, end uint64) ([]*types.Log, error) {
   421  	start := time.Now()
   422  	log.Debug("Performing unindexed log search", "begin", begin, "end", end)
   423  	var matches []*types.Log
   424  	for blockNumber := begin; blockNumber <= end; blockNumber++ {
   425  		select {
   426  		case <-ctx.Done():
   427  			return matches, ctx.Err()
   428  		default:
   429  		}
   430  		if blockNumber > chainView.HeadNumber() {
   431  			// check here so that we can return matches up until head along with
   432  			// the error
   433  			return matches, errInvalidBlockRange
   434  		}
   435  		header := chainView.Header(blockNumber)
   436  		if header == nil {
   437  			return matches, errors.New("header not found")
   438  		}
   439  		found, err := f.blockLogs(ctx, header)
   440  		if err != nil {
   441  			return matches, err
   442  		}
   443  		matches = append(matches, found...)
   444  	}
   445  	log.Debug("Performed unindexed log search", "begin", begin, "end", end, "matches", len(matches), "elapsed", common.PrettyDuration(time.Since(start)))
   446  	return matches, nil
   447  }
   448  
   449  // blockLogs returns the logs matching the filter criteria within a single block.
   450  func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types.Log, error) {
   451  	if bloomFilter(header.Bloom, f.addresses, f.topics) {
   452  		return f.checkMatches(ctx, header)
   453  	}
   454  	return nil, nil
   455  }
   456  
   457  // checkMatches checks if the receipts belonging to the given header contain any log events that
   458  // match the filter criteria. This function is called when the bloom filter signals a potential match.
   459  // skipFilter signals all logs of the given block are requested.
   460  func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) {
   461  	hash := header.Hash()
   462  	// Logs in cache are partially filled with context data
   463  	// such as tx index, block hash, etc.
   464  	// Notably tx hash is NOT filled in because it needs
   465  	// access to block body data.
   466  	cached, err := f.sys.cachedLogElem(ctx, hash, header.Number.Uint64(), header.Time)
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  	logs := filterLogs(cached.logs, nil, nil, f.addresses, f.topics)
   471  	if len(logs) == 0 {
   472  		return nil, nil
   473  	}
   474  	// Most backends will deliver un-derived logs, but check nevertheless.
   475  	if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) {
   476  		return logs, nil
   477  	}
   478  
   479  	body, err := f.sys.cachedGetBody(ctx, cached, hash, header.Number.Uint64())
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  	for i, log := range logs {
   484  		// Copy log not to modify cache elements
   485  		logcopy := *log
   486  		logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash()
   487  		logs[i] = &logcopy
   488  	}
   489  	return logs, nil
   490  }
   491  
   492  // filterLogs creates a slice of logs matching the given criteria.
   493  func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
   494  	var check = func(log *types.Log) bool {
   495  		if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
   496  			return false
   497  		}
   498  		if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
   499  			return false
   500  		}
   501  		if len(addresses) > 0 && !slices.Contains(addresses, log.Address) {
   502  			return false
   503  		}
   504  		// If the to filtered topics is greater than the amount of topics in logs, skip.
   505  		if len(topics) > len(log.Topics) {
   506  			return false
   507  		}
   508  		for i, sub := range topics {
   509  			if len(sub) == 0 {
   510  				continue // empty rule set == wildcard
   511  			}
   512  			if !slices.Contains(sub, log.Topics[i]) {
   513  				return false
   514  			}
   515  		}
   516  		return true
   517  	}
   518  	var ret []*types.Log
   519  	for _, log := range logs {
   520  		if check(log) {
   521  			ret = append(ret, log)
   522  		}
   523  	}
   524  	return ret
   525  }
   526  
   527  func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
   528  	if len(addresses) > 0 {
   529  		var included bool
   530  		for _, addr := range addresses {
   531  			if types.BloomLookup(bloom, addr) {
   532  				included = true
   533  				break
   534  			}
   535  		}
   536  		if !included {
   537  			return false
   538  		}
   539  	}
   540  
   541  	for _, sub := range topics {
   542  		included := len(sub) == 0 // empty rule set == wildcard
   543  		for _, topic := range sub {
   544  			if types.BloomLookup(bloom, topic) {
   545  				included = true
   546  				break
   547  			}
   548  		}
   549  		if !included {
   550  			return false
   551  		}
   552  	}
   553  	return true
   554  }