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