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