github.com/MetalBlockchain/subnet-evm@v0.4.9/eth/filters/filter.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2014 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package filters 28 29 import ( 30 "context" 31 "errors" 32 "fmt" 33 "math/big" 34 35 "github.com/MetalBlockchain/subnet-evm/core/bloombits" 36 "github.com/MetalBlockchain/subnet-evm/core/types" 37 "github.com/MetalBlockchain/subnet-evm/rpc" 38 "github.com/ethereum/go-ethereum/common" 39 ) 40 41 // Filter can be used to retrieve and filter logs. 42 type Filter struct { 43 sys *FilterSystem 44 45 addresses []common.Address 46 topics [][]common.Hash 47 48 block *common.Hash // Block hash if filtering a single block 49 begin, end int64 // Range interval if filtering multiple blocks 50 51 matcher *bloombits.Matcher 52 } 53 54 // NewRangeFilter creates a new filter which uses a bloom filter on blocks to 55 // figure out whether a particular block is interesting or not. 56 func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) (*Filter, error) { 57 allowUnfinalizedQueries := sys.backend.GetVMConfig().AllowUnfinalizedQueries 58 acceptedBlock := sys.backend.LastAcceptedBlock() 59 60 // Flatten the address and topic filter clauses into a single bloombits filter 61 // system. Since the bloombits are not positional, nil topics are permitted, 62 // which get flattened into a nil byte slice. 63 var filters [][][]byte 64 if len(addresses) > 0 { 65 filter := make([][]byte, len(addresses)) 66 for i, address := range addresses { 67 filter[i] = address.Bytes() 68 } 69 filters = append(filters, filter) 70 } 71 for _, topicList := range topics { 72 filter := make([][]byte, len(topicList)) 73 for i, topic := range topicList { 74 filter[i] = topic.Bytes() 75 } 76 filters = append(filters, filter) 77 } 78 size, _ := sys.backend.BloomStatus() 79 80 if !allowUnfinalizedQueries && acceptedBlock != nil { 81 lastAccepted := acceptedBlock.Number().Int64() 82 if begin >= 0 && begin > lastAccepted { 83 return nil, fmt.Errorf("requested from block %d after last accepted block %d", begin, lastAccepted) 84 } 85 if end >= 0 && end > lastAccepted { 86 return nil, fmt.Errorf("requested to block %d after last accepted block %d", end, lastAccepted) 87 } 88 } 89 90 // Create a generic filter and convert it into a range filter 91 filter := newFilter(sys, addresses, topics) 92 93 filter.matcher = bloombits.NewMatcher(size, filters) 94 filter.begin = begin 95 filter.end = end 96 97 return filter, nil 98 } 99 100 // NewBlockFilter creates a new filter which directly inspects the contents of 101 // a block to figure out whether it is interesting or not. 102 func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { 103 // Create a generic filter and convert it into a block filter 104 filter := newFilter(sys, addresses, topics) 105 filter.block = &block 106 return filter 107 } 108 109 // newFilter creates a generic filter that can either filter based on a block hash, 110 // or based on range queries. The search criteria needs to be explicitly set. 111 func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter { 112 return &Filter{ 113 sys: sys, 114 addresses: addresses, 115 topics: topics, 116 } 117 } 118 119 // Logs searches the blockchain for matching log entries, returning all from the 120 // first block that contains matches, updating the start of the filter accordingly. 121 func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { 122 // If we're doing singleton block filtering, execute and return 123 if f.block != nil { 124 header, err := f.sys.backend.HeaderByHash(ctx, *f.block) 125 if err != nil { 126 return nil, err 127 } 128 if header == nil { 129 return nil, errors.New("unknown block") 130 } 131 return f.blockLogs(ctx, header, false) 132 } 133 // Short-cut if all we care about is pending logs 134 if f.begin == rpc.PendingBlockNumber.Int64() { 135 if f.end != rpc.PendingBlockNumber.Int64() { 136 return nil, errors.New("invalid block range") 137 } 138 // There is no pending block, if the request specifies only the pending block, then return nil. 139 return nil, nil 140 } 141 // Figure out the limits of the filter range 142 // LatestBlockNumber is transformed into the last accepted block in HeaderByNumber 143 // so it is left in place here. 144 header, err := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 145 if err != nil { 146 return nil, err 147 } 148 if header == nil { 149 return nil, nil 150 } 151 var ( 152 head = header.Number.Uint64() 153 end = uint64(f.end) 154 ) 155 if f.begin < 0 { 156 f.begin = int64(head) 157 } 158 if f.end < 0 { 159 end = head 160 } 161 162 // When querying unfinalized data without a populated end block, it is 163 // possible that the begin will be greater than the end. 164 // 165 // We error in this case to prevent a bad UX where the caller thinks there 166 // are no logs from the specified beginning to end (when in reality there may 167 // be some). 168 if end < uint64(f.begin) { 169 return nil, fmt.Errorf("begin block %d is greater than end block %d", f.begin, end) 170 } 171 172 // If the requested range of blocks exceeds the maximum number of blocks allowed by the backend 173 // return an error instead of searching for the logs. 174 if maxBlocks := f.sys.backend.GetMaxBlocksPerRequest(); int64(end)-f.begin > maxBlocks && maxBlocks > 0 { 175 return nil, fmt.Errorf("requested too many blocks from %d to %d, maximum is set to %d", f.begin, int64(end), maxBlocks) 176 } 177 // Gather all indexed logs, and finish with non indexed ones 178 var ( 179 logs []*types.Log 180 size, sections = f.sys.backend.BloomStatus() 181 ) 182 if indexed := sections * size; indexed > uint64(f.begin) { 183 if indexed > end { 184 logs, err = f.indexedLogs(ctx, end) 185 } else { 186 logs, err = f.indexedLogs(ctx, indexed-1) 187 } 188 if err != nil { 189 return logs, err 190 } 191 } 192 rest, err := f.unindexedLogs(ctx, end) 193 logs = append(logs, rest...) 194 return logs, err 195 } 196 197 // indexedLogs returns the logs matching the filter criteria based on the bloom 198 // bits indexed available locally or via the network. 199 func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { 200 // Create a matcher session and request servicing from the backend 201 matches := make(chan uint64, 64) 202 203 session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) 204 if err != nil { 205 return nil, err 206 } 207 defer session.Close() 208 209 f.sys.backend.ServiceFilter(ctx, session) 210 211 // Iterate over the matches until exhausted or context closed 212 var logs []*types.Log 213 214 for { 215 select { 216 case number, ok := <-matches: 217 // Abort if all matches have been fulfilled 218 if !ok { 219 err := session.Error() 220 if err == nil { 221 f.begin = int64(end) + 1 222 } 223 return logs, err 224 } 225 f.begin = int64(number) + 1 226 227 // Retrieve the suggested block and pull any truly matching logs 228 header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) 229 if header == nil || err != nil { 230 return logs, err 231 } 232 found, err := f.blockLogs(ctx, header, true) 233 if err != nil { 234 return logs, err 235 } 236 logs = append(logs, found...) 237 238 case <-ctx.Done(): 239 return logs, ctx.Err() 240 } 241 } 242 } 243 244 // unindexedLogs returns the logs matching the filter criteria based on raw block 245 // iteration and bloom matching. 246 func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { 247 var logs []*types.Log 248 249 for ; f.begin <= int64(end); f.begin++ { 250 header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) 251 if header == nil || err != nil { 252 return logs, err 253 } 254 found, err := f.blockLogs(ctx, header, false) 255 if err != nil { 256 return logs, err 257 } 258 logs = append(logs, found...) 259 } 260 return logs, nil 261 } 262 263 // blockLogs returns the logs matching the filter criteria within a single block. 264 func (f *Filter) blockLogs(ctx context.Context, header *types.Header, skipBloom bool) ([]*types.Log, error) { 265 // Fast track: no filtering criteria 266 if len(f.addresses) == 0 && len(f.topics) == 0 { 267 list, err := f.sys.getLogs(ctx, header.Hash(), header.Number.Uint64()) 268 if err != nil { 269 return nil, err 270 } 271 return types.FlattenLogs(list), nil 272 } else if skipBloom || bloomFilter(header.Bloom, f.addresses, f.topics) { 273 return f.checkMatches(ctx, header) 274 } 275 return nil, nil 276 } 277 278 // checkMatches checks if the receipts belonging to the given header contain any log events that 279 // match the filter criteria. This function is called when the bloom filter signals a potential match. 280 func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) { 281 logsList, err := f.sys.getLogs(ctx, header.Hash(), header.Number.Uint64()) 282 if err != nil { 283 return nil, err 284 } 285 286 unfiltered := types.FlattenLogs(logsList) 287 logs := filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 288 if len(logs) > 0 { 289 // We have matching logs, check if we need to resolve full logs via the light client 290 if logs[0].TxHash == (common.Hash{}) { 291 receipts, err := f.sys.backend.GetReceipts(ctx, header.Hash()) 292 if err != nil { 293 return nil, err 294 } 295 unfiltered = unfiltered[:0] 296 for _, receipt := range receipts { 297 unfiltered = append(unfiltered, receipt.Logs...) 298 } 299 logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 300 } 301 return logs, nil 302 } 303 return nil, nil 304 } 305 306 func includes(addresses []common.Address, a common.Address) bool { 307 for _, addr := range addresses { 308 if addr == a { 309 return true 310 } 311 } 312 313 return false 314 } 315 316 // filterLogs creates a slice of logs matching the given criteria. 317 func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { 318 var ret []*types.Log 319 Logs: 320 for _, log := range logs { 321 if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { 322 continue 323 } 324 if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { 325 continue 326 } 327 328 if len(addresses) > 0 && !includes(addresses, log.Address) { 329 continue 330 } 331 // If the to filtered topics is greater than the amount of topics in logs, skip. 332 if len(topics) > len(log.Topics) { 333 continue 334 } 335 for i, sub := range topics { 336 match := len(sub) == 0 // empty rule set == wildcard 337 for _, topic := range sub { 338 if log.Topics[i] == topic { 339 match = true 340 break 341 } 342 } 343 if !match { 344 continue Logs 345 } 346 } 347 ret = append(ret, log) 348 } 349 return ret 350 } 351 352 func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { 353 if len(addresses) > 0 { 354 var included bool 355 for _, addr := range addresses { 356 if types.BloomLookup(bloom, addr) { 357 included = true 358 break 359 } 360 } 361 if !included { 362 return false 363 } 364 } 365 366 for _, sub := range topics { 367 included := len(sub) == 0 // empty rule set == wildcard 368 for _, topic := range sub { 369 if types.BloomLookup(bloom, topic) { 370 included = true 371 break 372 } 373 } 374 if !included { 375 return false 376 } 377 } 378 return true 379 }