github.com/klaytn/klaytn@v1.12.1/node/cn/filters/filter.go (about)

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