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