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 }