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