github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/eth/filters/filter.go (about)

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