github.com/ethereum/go-ethereum@v1.16.1/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" 23 "math/big" 24 "slices" 25 "time" 26 27 "github.com/ethereum/go-ethereum/common" 28 "github.com/ethereum/go-ethereum/core/filtermaps" 29 "github.com/ethereum/go-ethereum/core/history" 30 "github.com/ethereum/go-ethereum/core/types" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/rpc" 33 ) 34 35 // Filter can be used to retrieve and filter logs. 36 type Filter struct { 37 sys *FilterSystem 38 39 addresses []common.Address 40 topics [][]common.Hash 41 42 block *common.Hash // Block hash if filtering a single block 43 begin, end int64 // Range interval if filtering multiple blocks 44 45 rangeLogsTestHook chan rangeLogsTestEvent 46 } 47 48 // NewRangeFilter creates a new filter which uses a bloom filter on blocks to 49 // figure out whether a particular block is interesting or not. 50 func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { 51 // Create a generic filter and convert it into a range filter 52 filter := newFilter(sys, addresses, topics) 53 filter.begin = begin 54 filter.end = end 55 56 return filter 57 } 58 59 // NewBlockFilter creates a new filter which directly inspects the contents of 60 // a block to figure out whether it is interesting or not. 61 func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { 62 // Create a generic filter and convert it into a block filter 63 filter := newFilter(sys, addresses, topics) 64 filter.block = &block 65 return filter 66 } 67 68 // newFilter creates a generic filter that can either filter based on a block hash, 69 // or based on range queries. The search criteria needs to be explicitly set. 70 func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter { 71 return &Filter{ 72 sys: sys, 73 addresses: addresses, 74 topics: topics, 75 } 76 } 77 78 // Logs searches the blockchain for matching log entries, returning all from the 79 // first block that contains matches, updating the start of the filter accordingly. 80 func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { 81 // If we're doing singleton block filtering, execute and return 82 if f.block != nil { 83 header, err := f.sys.backend.HeaderByHash(ctx, *f.block) 84 if err != nil { 85 return nil, err 86 } 87 if header == nil { 88 return nil, errors.New("unknown block") 89 } 90 if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() { 91 return nil, &history.PrunedHistoryError{} 92 } 93 return f.blockLogs(ctx, header) 94 } 95 96 // Disallow pending logs. 97 if f.begin == rpc.PendingBlockNumber.Int64() || f.end == rpc.PendingBlockNumber.Int64() { 98 return nil, errPendingLogsUnsupported 99 } 100 101 resolveSpecial := func(number int64) (uint64, error) { 102 switch number { 103 case rpc.LatestBlockNumber.Int64(): 104 // when searching from and/or until the current head, we resolve it 105 // to MaxUint64 which is translated by rangeLogs to the actual head 106 // in each iteration, ensuring that the head block will be searched 107 // even if the chain is updated during search. 108 return math.MaxUint64, nil 109 case rpc.FinalizedBlockNumber.Int64(): 110 hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) 111 if hdr == nil { 112 return 0, errors.New("finalized header not found") 113 } 114 return hdr.Number.Uint64(), nil 115 case rpc.SafeBlockNumber.Int64(): 116 hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) 117 if hdr == nil { 118 return 0, errors.New("safe header not found") 119 } 120 return hdr.Number.Uint64(), nil 121 case rpc.EarliestBlockNumber.Int64(): 122 earliest := f.sys.backend.HistoryPruningCutoff() 123 hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(earliest)) 124 if hdr == nil { 125 return 0, errors.New("earliest header not found") 126 } 127 return hdr.Number.Uint64(), nil 128 default: 129 if number < 0 { 130 return 0, errors.New("negative block number") 131 } 132 return uint64(number), nil 133 } 134 } 135 136 // range query need to resolve the special begin/end block number 137 begin, err := resolveSpecial(f.begin) 138 if err != nil { 139 return nil, err 140 } 141 end, err := resolveSpecial(f.end) 142 if err != nil { 143 return nil, err 144 } 145 return f.rangeLogs(ctx, begin, end) 146 } 147 148 const ( 149 rangeLogsTestDone = iota // zero range 150 rangeLogsTestSync // before sync; zero range 151 rangeLogsTestSynced // after sync; valid blocks range 152 rangeLogsTestIndexed // individual search range 153 rangeLogsTestUnindexed // individual search range 154 rangeLogsTestResults // results range after search iteration 155 rangeLogsTestReorg // results range trimmed by reorg 156 ) 157 158 type rangeLogsTestEvent struct { 159 event int 160 blocks common.Range[uint64] 161 } 162 163 // searchSession represents a single search session. 164 type searchSession struct { 165 ctx context.Context 166 filter *Filter 167 mb filtermaps.MatcherBackend 168 syncRange filtermaps.SyncRange // latest synchronized state with the matcher 169 chainView *filtermaps.ChainView // can be more recent than the indexed view in syncRange 170 // block ranges always refer to the current chainView 171 firstBlock, lastBlock uint64 // specified search range; MaxUint64 means latest block 172 searchRange common.Range[uint64] // actual search range; end trimmed to latest head 173 matchRange common.Range[uint64] // range in which we have results (subset of searchRange) 174 matches []*types.Log // valid set of matches in matchRange 175 forceUnindexed bool // revert to unindexed search 176 } 177 178 // newSearchSession returns a new searchSession. 179 func newSearchSession(ctx context.Context, filter *Filter, mb filtermaps.MatcherBackend, firstBlock, lastBlock uint64) (*searchSession, error) { 180 s := &searchSession{ 181 ctx: ctx, 182 filter: filter, 183 mb: mb, 184 firstBlock: firstBlock, 185 lastBlock: lastBlock, 186 } 187 // enforce a consistent state before starting the search in order to be able 188 // to determine valid range later 189 var err error 190 s.syncRange, err = s.mb.SyncLogIndex(s.ctx) 191 if err != nil { 192 return nil, err 193 } 194 if err := s.updateChainView(); err != nil { 195 return nil, err 196 } 197 return s, nil 198 } 199 200 // updateChainView updates to the latest view of the underlying chain and sets 201 // searchRange by replacing MaxUint64 (meaning latest block) with actual head 202 // number in the specified search range. 203 // If the session already had an existing chain view and set of matches then 204 // it also trims part of the match set that a chain reorg might have invalidated. 205 func (s *searchSession) updateChainView() error { 206 // update chain view based on current chain head (might be more recent than 207 // the indexed view of syncRange as the indexer updates it asynchronously 208 // with some delay 209 newChainView := s.filter.sys.backend.CurrentView() 210 if newChainView == nil { 211 return errors.New("head block not available") 212 } 213 head := newChainView.HeadNumber() 214 215 // update actual search range based on current head number 216 firstBlock, lastBlock := s.firstBlock, s.lastBlock 217 if firstBlock == math.MaxUint64 { 218 firstBlock = head 219 } 220 if lastBlock == math.MaxUint64 { 221 lastBlock = head 222 } 223 if firstBlock > lastBlock || lastBlock > head { 224 return errInvalidBlockRange 225 } 226 s.searchRange = common.NewRange(firstBlock, lastBlock+1-firstBlock) 227 228 // Trim existing match set in case a reorg may have invalidated some results 229 if !s.matchRange.IsEmpty() { 230 trimRange := newChainView.SharedRange(s.chainView).Intersection(s.searchRange) 231 s.matchRange, s.matches = s.trimMatches(trimRange, s.matchRange, s.matches) 232 } 233 s.chainView = newChainView 234 return nil 235 } 236 237 // trimMatches removes any entries from the specified set of matches that is 238 // outside the given range. 239 func (s *searchSession) trimMatches(trimRange, matchRange common.Range[uint64], matches []*types.Log) (common.Range[uint64], []*types.Log) { 240 newRange := matchRange.Intersection(trimRange) 241 if newRange == matchRange { 242 return matchRange, matches 243 } 244 if newRange.IsEmpty() { 245 return newRange, nil 246 } 247 for len(matches) > 0 && matches[0].BlockNumber < newRange.First() { 248 matches = matches[1:] 249 } 250 for len(matches) > 0 && matches[len(matches)-1].BlockNumber > newRange.Last() { 251 matches = matches[:len(matches)-1] 252 } 253 return newRange, matches 254 } 255 256 // searchInRange performs a single range search, either indexed or unindexed. 257 func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) (common.Range[uint64], []*types.Log, error) { 258 if indexed { 259 if s.filter.rangeLogsTestHook != nil { 260 s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestIndexed, r} 261 } 262 results, err := s.filter.indexedLogs(s.ctx, s.mb, r.First(), r.Last()) 263 if err != nil && !errors.Is(err, filtermaps.ErrMatchAll) { 264 return common.Range[uint64]{}, nil, err 265 } 266 if err == nil { 267 // sync with filtermaps matcher 268 if s.filter.rangeLogsTestHook != nil { 269 s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSync, common.Range[uint64]{}} 270 } 271 var syncErr error 272 if s.syncRange, syncErr = s.mb.SyncLogIndex(s.ctx); syncErr != nil { 273 return common.Range[uint64]{}, nil, syncErr 274 } 275 if s.filter.rangeLogsTestHook != nil { 276 s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSynced, s.syncRange.ValidBlocks} 277 } 278 // discard everything that might be invalid 279 trimRange := s.syncRange.ValidBlocks.Intersection(s.chainView.SharedRange(s.syncRange.IndexedView)) 280 matchRange, matches := s.trimMatches(trimRange, r, results) 281 return matchRange, matches, nil 282 } 283 // "match all" filters are not supported by filtermaps; fall back to 284 // unindexed search which is the most efficient in this case 285 s.forceUnindexed = true 286 // fall through to unindexed case 287 } 288 if s.filter.rangeLogsTestHook != nil { 289 s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestUnindexed, r} 290 } 291 matches, err := s.filter.unindexedLogs(s.ctx, s.chainView, r.First(), r.Last()) 292 if err != nil { 293 return common.Range[uint64]{}, nil, err 294 } 295 return r, matches, nil 296 } 297 298 // doSearchIteration performs a search on a range missing from an incomplete set 299 // of results, adds the new section and removes invalidated entries. 300 func (s *searchSession) doSearchIteration() error { 301 switch { 302 case s.matchRange.IsEmpty(): 303 // no results yet; try search in entire range 304 indexedSearchRange := s.searchRange.Intersection(s.syncRange.IndexedBlocks) 305 if s.forceUnindexed = indexedSearchRange.IsEmpty(); !s.forceUnindexed { 306 // indexed search on the intersection of indexed and searched range 307 matchRange, matches, err := s.searchInRange(indexedSearchRange, true) 308 if err != nil { 309 return err 310 } 311 s.matchRange = matchRange 312 s.matches = matches 313 return nil 314 } else { 315 // no intersection of indexed and searched range; unindexed search on 316 // the whole searched range 317 matchRange, matches, err := s.searchInRange(s.searchRange, false) 318 if err != nil { 319 return err 320 } 321 s.matchRange = matchRange 322 s.matches = matches 323 return nil 324 } 325 326 case !s.matchRange.IsEmpty() && s.matchRange.First() > s.searchRange.First(): 327 // Results are available, but the tail section is missing. Perform an unindexed 328 // search for the missing tail, while still allowing indexed search for the head. 329 // 330 // The unindexed search is necessary because the tail portion of the indexes 331 // has been pruned. 332 tailRange := common.NewRange(s.searchRange.First(), s.matchRange.First()-s.searchRange.First()) 333 _, tailMatches, err := s.searchInRange(tailRange, false) 334 if err != nil { 335 return err 336 } 337 s.matches = append(tailMatches, s.matches...) 338 s.matchRange = tailRange.Union(s.matchRange) 339 return nil 340 341 case !s.matchRange.IsEmpty() && s.matchRange.First() == s.searchRange.First() && s.searchRange.AfterLast() > s.matchRange.AfterLast(): 342 // Results are available, but the head section is missing. Try to perform 343 // the indexed search for the missing head, or fallback to unindexed search 344 // if the tail portion of indexed range has been pruned. 345 headRange := common.NewRange(s.matchRange.AfterLast(), s.searchRange.AfterLast()-s.matchRange.AfterLast()) 346 if !s.forceUnindexed { 347 indexedHeadRange := headRange.Intersection(s.syncRange.IndexedBlocks) 348 if !indexedHeadRange.IsEmpty() && indexedHeadRange.First() == headRange.First() { 349 headRange = indexedHeadRange 350 } else { 351 // The tail portion of the indexes has been pruned, falling back 352 // to unindexed search. 353 s.forceUnindexed = true 354 } 355 } 356 headMatchRange, headMatches, err := s.searchInRange(headRange, !s.forceUnindexed) 357 if err != nil { 358 return err 359 } 360 if headMatchRange.First() != s.matchRange.AfterLast() { 361 // improbable corner case, first part of new head range invalidated by tail unindexing 362 s.matches, s.matchRange = headMatches, headMatchRange 363 return nil 364 } 365 s.matches = append(s.matches, headMatches...) 366 s.matchRange = s.matchRange.Union(headMatchRange) 367 return nil 368 369 default: 370 panic("invalid search session state") 371 } 372 } 373 374 func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([]*types.Log, error) { 375 if f.rangeLogsTestHook != nil { 376 defer func() { 377 f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestDone, common.Range[uint64]{}} 378 close(f.rangeLogsTestHook) 379 }() 380 } 381 382 if firstBlock > lastBlock { 383 return nil, nil 384 } 385 mb := f.sys.backend.NewMatcherBackend() 386 defer mb.Close() 387 388 session, err := newSearchSession(ctx, f, mb, firstBlock, lastBlock) 389 if err != nil { 390 return nil, err 391 } 392 for session.searchRange != session.matchRange { 393 if err := session.doSearchIteration(); err != nil { 394 return nil, err 395 } 396 if f.rangeLogsTestHook != nil { 397 f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestResults, session.matchRange} 398 } 399 mr := session.matchRange 400 if err := session.updateChainView(); err != nil { 401 return nil, err 402 } 403 if f.rangeLogsTestHook != nil && session.matchRange != mr { 404 f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestReorg, session.matchRange} 405 } 406 } 407 return session.matches, nil 408 } 409 410 func (f *Filter) indexedLogs(ctx context.Context, mb filtermaps.MatcherBackend, begin, end uint64) ([]*types.Log, error) { 411 start := time.Now() 412 potentialMatches, err := filtermaps.GetPotentialMatches(ctx, mb, begin, end, f.addresses, f.topics) 413 matches := filterLogs(potentialMatches, nil, nil, f.addresses, f.topics) 414 log.Trace("Performed indexed log search", "begin", begin, "end", end, "true matches", len(matches), "false positives", len(potentialMatches)-len(matches), "elapsed", common.PrettyDuration(time.Since(start))) 415 return matches, err 416 } 417 418 // unindexedLogs returns the logs matching the filter criteria based on raw block 419 // iteration and bloom matching. 420 func (f *Filter) unindexedLogs(ctx context.Context, chainView *filtermaps.ChainView, begin, end uint64) ([]*types.Log, error) { 421 start := time.Now() 422 log.Debug("Performing unindexed log search", "begin", begin, "end", end) 423 var matches []*types.Log 424 for blockNumber := begin; blockNumber <= end; blockNumber++ { 425 select { 426 case <-ctx.Done(): 427 return matches, ctx.Err() 428 default: 429 } 430 if blockNumber > chainView.HeadNumber() { 431 // check here so that we can return matches up until head along with 432 // the error 433 return matches, errInvalidBlockRange 434 } 435 header := chainView.Header(blockNumber) 436 if header == nil { 437 return matches, errors.New("header not found") 438 } 439 found, err := f.blockLogs(ctx, header) 440 if err != nil { 441 return matches, err 442 } 443 matches = append(matches, found...) 444 } 445 log.Debug("Performed unindexed log search", "begin", begin, "end", end, "matches", len(matches), "elapsed", common.PrettyDuration(time.Since(start))) 446 return matches, nil 447 } 448 449 // blockLogs returns the logs matching the filter criteria within a single block. 450 func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types.Log, error) { 451 if bloomFilter(header.Bloom, f.addresses, f.topics) { 452 return f.checkMatches(ctx, header) 453 } 454 return nil, nil 455 } 456 457 // checkMatches checks if the receipts belonging to the given header contain any log events that 458 // match the filter criteria. This function is called when the bloom filter signals a potential match. 459 // skipFilter signals all logs of the given block are requested. 460 func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) { 461 hash := header.Hash() 462 // Logs in cache are partially filled with context data 463 // such as tx index, block hash, etc. 464 // Notably tx hash is NOT filled in because it needs 465 // access to block body data. 466 cached, err := f.sys.cachedLogElem(ctx, hash, header.Number.Uint64(), header.Time) 467 if err != nil { 468 return nil, err 469 } 470 logs := filterLogs(cached.logs, nil, nil, f.addresses, f.topics) 471 if len(logs) == 0 { 472 return nil, nil 473 } 474 // Most backends will deliver un-derived logs, but check nevertheless. 475 if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) { 476 return logs, nil 477 } 478 479 body, err := f.sys.cachedGetBody(ctx, cached, hash, header.Number.Uint64()) 480 if err != nil { 481 return nil, err 482 } 483 for i, log := range logs { 484 // Copy log not to modify cache elements 485 logcopy := *log 486 logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash() 487 logs[i] = &logcopy 488 } 489 return logs, nil 490 } 491 492 // filterLogs creates a slice of logs matching the given criteria. 493 func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { 494 var check = func(log *types.Log) bool { 495 if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { 496 return false 497 } 498 if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { 499 return false 500 } 501 if len(addresses) > 0 && !slices.Contains(addresses, log.Address) { 502 return false 503 } 504 // If the to filtered topics is greater than the amount of topics in logs, skip. 505 if len(topics) > len(log.Topics) { 506 return false 507 } 508 for i, sub := range topics { 509 if len(sub) == 0 { 510 continue // empty rule set == wildcard 511 } 512 if !slices.Contains(sub, log.Topics[i]) { 513 return false 514 } 515 } 516 return true 517 } 518 var ret []*types.Log 519 for _, log := range logs { 520 if check(log) { 521 ret = append(ret, log) 522 } 523 } 524 return ret 525 } 526 527 func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { 528 if len(addresses) > 0 { 529 var included bool 530 for _, addr := range addresses { 531 if types.BloomLookup(bloom, addr) { 532 included = true 533 break 534 } 535 } 536 if !included { 537 return false 538 } 539 } 540 541 for _, sub := range topics { 542 included := len(sub) == 0 // empty rule set == wildcard 543 for _, topic := range sub { 544 if types.BloomLookup(bloom, topic) { 545 included = true 546 break 547 } 548 } 549 if !included { 550 return false 551 } 552 } 553 return true 554 }