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