github.com/jimmyx0x/go-ethereum@v1.10.28/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/big"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/core/bloombits"
    26  	"github.com/ethereum/go-ethereum/core/types"
    27  	"github.com/ethereum/go-ethereum/rpc"
    28  )
    29  
    30  // Filter can be used to retrieve and filter logs.
    31  type Filter struct {
    32  	sys *FilterSystem
    33  
    34  	addresses []common.Address
    35  	topics    [][]common.Hash
    36  
    37  	block      *common.Hash // Block hash if filtering a single block
    38  	begin, end int64        // Range interval if filtering multiple blocks
    39  
    40  	matcher *bloombits.Matcher
    41  }
    42  
    43  // NewRangeFilter creates a new filter which uses a bloom filter on blocks to
    44  // figure out whether a particular block is interesting or not.
    45  func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
    46  	// Flatten the address and topic filter clauses into a single bloombits filter
    47  	// system. Since the bloombits are not positional, nil topics are permitted,
    48  	// which get flattened into a nil byte slice.
    49  	var filters [][][]byte
    50  	if len(addresses) > 0 {
    51  		filter := make([][]byte, len(addresses))
    52  		for i, address := range addresses {
    53  			filter[i] = address.Bytes()
    54  		}
    55  		filters = append(filters, filter)
    56  	}
    57  	for _, topicList := range topics {
    58  		filter := make([][]byte, len(topicList))
    59  		for i, topic := range topicList {
    60  			filter[i] = topic.Bytes()
    61  		}
    62  		filters = append(filters, filter)
    63  	}
    64  	size, _ := sys.backend.BloomStatus()
    65  
    66  	// Create a generic filter and convert it into a range filter
    67  	filter := newFilter(sys, addresses, topics)
    68  
    69  	filter.matcher = bloombits.NewMatcher(size, filters)
    70  	filter.begin = begin
    71  	filter.end = end
    72  
    73  	return filter
    74  }
    75  
    76  // NewBlockFilter creates a new filter which directly inspects the contents of
    77  // a block to figure out whether it is interesting or not.
    78  func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter {
    79  	// Create a generic filter and convert it into a block filter
    80  	filter := newFilter(sys, addresses, topics)
    81  	filter.block = &block
    82  	return filter
    83  }
    84  
    85  // newFilter creates a generic filter that can either filter based on a block hash,
    86  // or based on range queries. The search criteria needs to be explicitly set.
    87  func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter {
    88  	return &Filter{
    89  		sys:       sys,
    90  		addresses: addresses,
    91  		topics:    topics,
    92  	}
    93  }
    94  
    95  // Logs searches the blockchain for matching log entries, returning all from the
    96  // first block that contains matches, updating the start of the filter accordingly.
    97  func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
    98  	// If we're doing singleton block filtering, execute and return
    99  	if f.block != nil {
   100  		header, err := f.sys.backend.HeaderByHash(ctx, *f.block)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		if header == nil {
   105  			return nil, errors.New("unknown block")
   106  		}
   107  		return f.blockLogs(ctx, header, false)
   108  	}
   109  	// Short-cut if all we care about is pending logs
   110  	if f.begin == rpc.PendingBlockNumber.Int64() {
   111  		if f.end != rpc.PendingBlockNumber.Int64() {
   112  			return nil, errors.New("invalid block range")
   113  		}
   114  		return f.pendingLogs()
   115  	}
   116  	// Figure out the limits of the filter range
   117  	header, _ := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   118  	if header == nil {
   119  		return nil, nil
   120  	}
   121  	var (
   122  		err     error
   123  		head    = header.Number.Int64()
   124  		pending = f.end == rpc.PendingBlockNumber.Int64()
   125  	)
   126  	resolveSpecial := func(number int64) (int64, error) {
   127  		var hdr *types.Header
   128  		switch number {
   129  		case rpc.LatestBlockNumber.Int64():
   130  			return head, nil
   131  		case rpc.PendingBlockNumber.Int64():
   132  			// we should return head here since we've already captured
   133  			// that we need to get the pending logs in the pending boolean above
   134  			return head, nil
   135  		case rpc.FinalizedBlockNumber.Int64():
   136  			hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber)
   137  			if hdr == nil {
   138  				return 0, errors.New("finalized header not found")
   139  			}
   140  		case rpc.SafeBlockNumber.Int64():
   141  			hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber)
   142  			if hdr == nil {
   143  				return 0, errors.New("safe header not found")
   144  			}
   145  		default:
   146  			return number, nil
   147  		}
   148  		return hdr.Number.Int64(), nil
   149  	}
   150  	if f.begin, err = resolveSpecial(f.begin); err != nil {
   151  		return nil, err
   152  	}
   153  	if f.end, err = resolveSpecial(f.end); err != nil {
   154  		return nil, err
   155  	}
   156  	// Gather all indexed logs, and finish with non indexed ones
   157  	var (
   158  		logs           []*types.Log
   159  		end            = uint64(f.end)
   160  		size, sections = f.sys.backend.BloomStatus()
   161  	)
   162  	if indexed := sections * size; indexed > uint64(f.begin) {
   163  		if indexed > end {
   164  			logs, err = f.indexedLogs(ctx, end)
   165  		} else {
   166  			logs, err = f.indexedLogs(ctx, indexed-1)
   167  		}
   168  		if err != nil {
   169  			return logs, err
   170  		}
   171  	}
   172  	rest, err := f.unindexedLogs(ctx, end)
   173  	logs = append(logs, rest...)
   174  	if pending {
   175  		pendingLogs, err := f.pendingLogs()
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		logs = append(logs, pendingLogs...)
   180  	}
   181  	return logs, err
   182  }
   183  
   184  // indexedLogs returns the logs matching the filter criteria based on the bloom
   185  // bits indexed available locally or via the network.
   186  func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) {
   187  	// Create a matcher session and request servicing from the backend
   188  	matches := make(chan uint64, 64)
   189  
   190  	session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	defer session.Close()
   195  
   196  	f.sys.backend.ServiceFilter(ctx, session)
   197  
   198  	// Iterate over the matches until exhausted or context closed
   199  	var logs []*types.Log
   200  
   201  	for {
   202  		select {
   203  		case number, ok := <-matches:
   204  			// Abort if all matches have been fulfilled
   205  			if !ok {
   206  				err := session.Error()
   207  				if err == nil {
   208  					f.begin = int64(end) + 1
   209  				}
   210  				return logs, err
   211  			}
   212  			f.begin = int64(number) + 1
   213  
   214  			// Retrieve the suggested block and pull any truly matching logs
   215  			header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
   216  			if header == nil || err != nil {
   217  				return logs, err
   218  			}
   219  			found, err := f.blockLogs(ctx, header, true)
   220  			if err != nil {
   221  				return logs, err
   222  			}
   223  			logs = append(logs, found...)
   224  
   225  		case <-ctx.Done():
   226  			return logs, ctx.Err()
   227  		}
   228  	}
   229  }
   230  
   231  // unindexedLogs returns the logs matching the filter criteria based on raw block
   232  // iteration and bloom matching.
   233  func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) {
   234  	var logs []*types.Log
   235  
   236  	for ; f.begin <= int64(end); f.begin++ {
   237  		if f.begin%10 == 0 && ctx.Err() != nil {
   238  			return logs, ctx.Err()
   239  		}
   240  		header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin))
   241  		if header == nil || err != nil {
   242  			return logs, err
   243  		}
   244  		found, err := f.blockLogs(ctx, header, false)
   245  		if err != nil {
   246  			return logs, err
   247  		}
   248  		logs = append(logs, found...)
   249  	}
   250  	return logs, nil
   251  }
   252  
   253  // blockLogs returns the logs matching the filter criteria within a single block.
   254  func (f *Filter) blockLogs(ctx context.Context, header *types.Header, skipBloom bool) ([]*types.Log, error) {
   255  	// Fast track: no filtering criteria
   256  	if len(f.addresses) == 0 && len(f.topics) == 0 {
   257  		list, err := f.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64())
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  		return flatten(list), nil
   262  	} else if skipBloom || bloomFilter(header.Bloom, f.addresses, f.topics) {
   263  		return f.checkMatches(ctx, header)
   264  	}
   265  	return nil, nil
   266  }
   267  
   268  // checkMatches checks if the receipts belonging to the given header contain any log events that
   269  // match the filter criteria. This function is called when the bloom filter signals a potential match.
   270  func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) {
   271  	logsList, err := f.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64())
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	unfiltered := flatten(logsList)
   277  	logs := filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
   278  	if len(logs) > 0 {
   279  		// We have matching logs, check if we need to resolve full logs via the light client
   280  		if logs[0].TxHash == (common.Hash{}) {
   281  			receipts, err := f.sys.backend.GetReceipts(ctx, header.Hash())
   282  			if err != nil {
   283  				return nil, err
   284  			}
   285  			unfiltered = unfiltered[:0]
   286  			for _, receipt := range receipts {
   287  				unfiltered = append(unfiltered, receipt.Logs...)
   288  			}
   289  			logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
   290  		}
   291  		return logs, nil
   292  	}
   293  	return nil, nil
   294  }
   295  
   296  // pendingLogs returns the logs matching the filter criteria within the pending block.
   297  func (f *Filter) pendingLogs() ([]*types.Log, error) {
   298  	block, receipts := f.sys.backend.PendingBlockAndReceipts()
   299  	if bloomFilter(block.Bloom(), f.addresses, f.topics) {
   300  		var unfiltered []*types.Log
   301  		for _, r := range receipts {
   302  			unfiltered = append(unfiltered, r.Logs...)
   303  		}
   304  		return filterLogs(unfiltered, nil, nil, f.addresses, f.topics), nil
   305  	}
   306  	return nil, nil
   307  }
   308  
   309  func includes(addresses []common.Address, a common.Address) bool {
   310  	for _, addr := range addresses {
   311  		if addr == a {
   312  			return true
   313  		}
   314  	}
   315  
   316  	return false
   317  }
   318  
   319  // filterLogs creates a slice of logs matching the given criteria.
   320  func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
   321  	var ret []*types.Log
   322  Logs:
   323  	for _, log := range logs {
   324  		if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
   325  			continue
   326  		}
   327  		if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
   328  			continue
   329  		}
   330  
   331  		if len(addresses) > 0 && !includes(addresses, log.Address) {
   332  			continue
   333  		}
   334  		// If the to filtered topics is greater than the amount of topics in logs, skip.
   335  		if len(topics) > len(log.Topics) {
   336  			continue
   337  		}
   338  		for i, sub := range topics {
   339  			match := len(sub) == 0 // empty rule set == wildcard
   340  			for _, topic := range sub {
   341  				if log.Topics[i] == topic {
   342  					match = true
   343  					break
   344  				}
   345  			}
   346  			if !match {
   347  				continue Logs
   348  			}
   349  		}
   350  		ret = append(ret, log)
   351  	}
   352  	return ret
   353  }
   354  
   355  func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
   356  	if len(addresses) > 0 {
   357  		var included bool
   358  		for _, addr := range addresses {
   359  			if types.BloomLookup(bloom, addr) {
   360  				included = true
   361  				break
   362  			}
   363  		}
   364  		if !included {
   365  			return false
   366  		}
   367  	}
   368  
   369  	for _, sub := range topics {
   370  		included := len(sub) == 0 // empty rule set == wildcard
   371  		for _, topic := range sub {
   372  			if types.BloomLookup(bloom, topic) {
   373  				included = true
   374  				break
   375  			}
   376  		}
   377  		if !included {
   378  			return false
   379  		}
   380  	}
   381  	return true
   382  }
   383  
   384  func flatten(list [][]*types.Log) []*types.Log {
   385  	var flat []*types.Log
   386  	for _, logs := range list {
   387  		flat = append(flat, logs...)
   388  	}
   389  	return flat
   390  }