github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/eth/filters/filter.go (about)

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