github.com/status-im/status-go@v1.1.0/services/rpcfilters/logs_filter.go (about)

     1  package rpcfilters
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  	"sync"
     8  	"time"
     9  
    10  	ethereum "github.com/ethereum/go-ethereum"
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	"github.com/ethereum/go-ethereum/rpc"
    14  )
    15  
    16  type logsFilter struct {
    17  	mu   sync.RWMutex
    18  	logs []types.Log
    19  	crit ethereum.FilterQuery // will be modified and different from original
    20  
    21  	originalCrit ethereum.FilterQuery // not modified version of the criteria
    22  
    23  	logsCache *cache
    24  
    25  	id    rpc.ID
    26  	timer *time.Timer
    27  
    28  	ctx    context.Context
    29  	cancel context.CancelFunc
    30  	done   chan struct{}
    31  }
    32  
    33  func (f *logsFilter) criteria() ethereum.FilterQuery {
    34  	f.mu.RLock()
    35  	defer f.mu.RUnlock()
    36  	return f.crit
    37  }
    38  
    39  func (f *logsFilter) add(data interface{}) error {
    40  	logs, ok := data.([]types.Log)
    41  	if !ok {
    42  		return fmt.Errorf("can't cast %v to types.Log", data)
    43  	}
    44  	filtered := filterLogs(logs, f.crit)
    45  	if len(filtered) > 0 {
    46  		f.mu.Lock()
    47  		defer f.mu.Unlock()
    48  		added, replaced, err := f.logsCache.add(filtered)
    49  		if err != nil {
    50  			return err
    51  		}
    52  		for _, log := range replaced {
    53  			log.Removed = true
    54  			f.logs = append(f.logs, log)
    55  		}
    56  		if len(added) > 0 {
    57  			f.logs = append(f.logs, added...)
    58  		}
    59  		// if there was no replaced logs - keep polling only latest logs
    60  		if len(replaced) == 0 {
    61  			adjustFromBlock(&f.crit)
    62  		} else {
    63  			// otherwise poll earliest known block in cache
    64  			earliest := f.logsCache.earliestBlockNum()
    65  			if earliest != 0 {
    66  				f.crit.FromBlock = new(big.Int).SetUint64(earliest)
    67  			}
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  func (f *logsFilter) pop() interface{} {
    74  	f.mu.Lock()
    75  	defer f.mu.Unlock()
    76  	rst := f.logs
    77  	f.logs = nil
    78  	return rst
    79  }
    80  
    81  func (f *logsFilter) stop() {
    82  	select {
    83  	case <-f.done:
    84  		return
    85  	default:
    86  		close(f.done)
    87  		if f.cancel != nil {
    88  			f.cancel()
    89  		}
    90  	}
    91  }
    92  
    93  func (f *logsFilter) deadline() *time.Timer {
    94  	return f.timer
    95  }
    96  
    97  // adjustFromBlock adjusts crit.FromBlock to latest to avoid querying same logs.
    98  func adjustFromBlock(crit *ethereum.FilterQuery) {
    99  	latest := big.NewInt(rpc.LatestBlockNumber.Int64())
   100  	// don't adjust if filter is not interested in newer blocks
   101  	if crit.ToBlock != nil && crit.ToBlock.Cmp(latest) == 1 {
   102  		return
   103  	}
   104  	// don't adjust if from block is already pending
   105  	if crit.FromBlock != nil && crit.FromBlock.Cmp(latest) == -1 {
   106  		return
   107  	}
   108  	crit.FromBlock = latest
   109  }
   110  
   111  func includes(addresses []common.Address, a common.Address) bool {
   112  	for _, addr := range addresses {
   113  		if addr == a {
   114  			return true
   115  		}
   116  	}
   117  	return false
   118  }
   119  
   120  // filterLogs creates a slice of logs matching the given criteria.
   121  func filterLogs(logs []types.Log, crit ethereum.FilterQuery) (
   122  	ret []types.Log) {
   123  	for _, log := range logs {
   124  		if matchLog(log, crit) {
   125  			ret = append(ret, log)
   126  		}
   127  	}
   128  	return
   129  }
   130  
   131  func matchLog(log types.Log, crit ethereum.FilterQuery) bool {
   132  	if crit.FromBlock != nil && crit.FromBlock.Int64() >= 0 && crit.FromBlock.Uint64() > log.BlockNumber {
   133  		return false
   134  	}
   135  	if crit.ToBlock != nil && crit.ToBlock.Int64() >= 0 && crit.ToBlock.Uint64() < log.BlockNumber {
   136  		return false
   137  	}
   138  	if len(crit.Addresses) > 0 && !includes(crit.Addresses, log.Address) {
   139  		return false
   140  	}
   141  	if len(crit.Topics) > len(log.Topics) {
   142  		return false
   143  	}
   144  	return matchTopics(log, crit.Topics)
   145  }
   146  
   147  func matchTopics(log types.Log, topics [][]common.Hash) bool {
   148  	for i, sub := range topics {
   149  		match := len(sub) == 0 // empty rule set == wildcard
   150  		for _, topic := range sub {
   151  			if log.Topics[i] == topic {
   152  				match = true
   153  				break
   154  			}
   155  		}
   156  		if !match {
   157  			return false
   158  		}
   159  	}
   160  	return true
   161  }