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