gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/aqua/filters/filter.go (about)

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