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