github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/qct/filters/filter.go (about)

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