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