github.com/klaytn/klaytn@v1.12.1/node/cn/filters/filter.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2014 The go-ethereum Authors 3 // This file is part of go-ethereum. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from eth/filters/filter.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package filters 22 23 import ( 24 "context" 25 "errors" 26 "math" 27 "math/big" 28 "strconv" 29 30 "github.com/klaytn/klaytn/params" 31 32 "github.com/klaytn/klaytn/blockchain" 33 "github.com/klaytn/klaytn/blockchain/bloombits" 34 "github.com/klaytn/klaytn/blockchain/types" 35 "github.com/klaytn/klaytn/common" 36 "github.com/klaytn/klaytn/event" 37 "github.com/klaytn/klaytn/networks/rpc" 38 "github.com/klaytn/klaytn/storage/database" 39 ) 40 41 //go:generate mockgen -destination=mock/backend_mock.go -package=cn github.com/klaytn/klaytn/node/cn/filters Backend 42 type Backend interface { 43 ChainDB() database.DBManager 44 EventMux() *event.TypeMux 45 HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) 46 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 47 GetBlockReceipts(ctx context.Context, blockHash common.Hash) types.Receipts 48 GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) 49 50 SubscribeNewTxsEvent(chan<- blockchain.NewTxsEvent) event.Subscription 51 SubscribeChainEvent(ch chan<- blockchain.ChainEvent) event.Subscription 52 SubscribeRemovedLogsEvent(ch chan<- blockchain.RemovedLogsEvent) event.Subscription 53 SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 54 55 BloomStatus() (uint64, uint64) 56 ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) 57 58 ChainConfig() *params.ChainConfig 59 } 60 61 // Filter can be used to retrieve and filter logs. 62 type Filter struct { 63 backend Backend 64 65 block common.Hash 66 begin, end int64 67 addresses []common.Address 68 topics [][]common.Hash 69 70 matcher *bloombits.Matcher 71 } 72 73 // NewBlockFilter creates a new filter which directly inspects the contents of 74 // a block to figure out whether it is interesting or not. 75 func NewBlockFilter(backend Backend, block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { 76 // Create a generic filter and convert it into a block filter 77 filter := newFilter(backend, addresses, topics) 78 filter.block = block 79 return filter 80 } 81 82 // NewRangeFilter creates a new filter which uses a bloom filter on blocks to 83 // figure out whether a particular block is interesting or not. 84 func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { 85 // Flatten the address and topic filter clauses into a single bloombits filter 86 // system. Since the bloombits are not positional, nil topics are permitted, 87 // which get flattened into a nil byte slice. 88 var filters [][][]byte 89 if len(addresses) > 0 { 90 filter := make([][]byte, len(addresses)) 91 for i, address := range addresses { 92 filter[i] = address.Bytes() 93 } 94 filters = append(filters, filter) 95 } 96 for _, topicList := range topics { 97 filter := make([][]byte, len(topicList)) 98 for i, topic := range topicList { 99 filter[i] = topic.Bytes() 100 } 101 filters = append(filters, filter) 102 } 103 // Assemble and return the filter 104 size, _ := backend.BloomStatus() 105 106 // Create a generic filter and convert it into a range filter 107 filter := newFilter(backend, addresses, topics) 108 109 filter.matcher = bloombits.NewMatcher(size, filters) 110 filter.begin = begin 111 filter.end = end 112 113 return filter 114 } 115 116 // newFilter creates a generic filter that can either filter based on a block hash, 117 // or based on range queries. The search criteria needs to be explicitly set. 118 func newFilter(backend Backend, addresses []common.Address, topics [][]common.Hash) *Filter { 119 return &Filter{ 120 backend: backend, 121 addresses: addresses, 122 topics: topics, 123 } 124 } 125 126 // Logs searches the blockchain for matching log entries, returning all from the 127 // first block that contains matches, updating the start of the filter accordingly. 128 func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { 129 // If we're doing singleton block filtering, execute and return 130 if f.block != (common.Hash{}) { 131 header, err := f.backend.HeaderByHash(ctx, f.block) 132 if err != nil { 133 return nil, err 134 } 135 if header == nil { 136 return nil, errors.New("unknown block") 137 } 138 return f.blockLogs(ctx, header) 139 } 140 141 // Figure out the limits of the filter range 142 header, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 143 if header == nil { 144 return nil, nil 145 } 146 head := header.Number.Uint64() 147 148 if f.begin == -1 { 149 f.begin = int64(head) 150 } 151 end := uint64(f.end) 152 if f.end == -1 { 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 ) 160 size, sections := f.backend.BloomStatus() 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 return logs, err 174 } 175 176 // blockLogs returns the logs matching the filter criteria within a single block. 177 func (f *Filter) blockLogs(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { 178 if bloomFilter(header.Bloom, f.addresses, f.topics) { 179 found, err := f.checkMatches(ctx, header) 180 if err != nil { 181 return logs, err 182 } 183 logs = append(logs, found...) 184 } 185 return logs, nil 186 } 187 188 // indexedLogs returns the logs matching the filter criteria based on the bloom 189 // bits indexed available locally or via the network. 190 func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { 191 // Create a matcher session and request servicing from the backend 192 matches := make(chan uint64, 64) 193 194 session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) 195 if err != nil { 196 return nil, err 197 } 198 defer session.Close() 199 200 f.backend.ServiceFilter(ctx, session) 201 202 // Iterate over the matches until exhausted or context closed 203 var logs []*types.Log 204 205 maxItems := getMaxItems(ctx) 206 207 for { 208 select { 209 case number, ok := <-matches: 210 // Abort if all matches have been fulfilled 211 if !ok { 212 err := session.Error() 213 if err == nil { 214 f.begin = int64(end) + 1 215 } 216 return logs, err 217 } 218 f.begin = int64(number) + 1 219 220 // Retrieve the suggested block and pull any truly matching logs 221 header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) 222 if header == nil || err != nil { 223 return logs, err 224 } 225 found, err := f.checkMatches(ctx, header) 226 if err != nil { 227 return logs, err 228 } 229 logs = append(logs, found...) 230 if len(logs) > maxItems { 231 return logs, errors.New("query returned more than " + strconv.Itoa(maxItems) + " results") 232 } 233 case <-ctx.Done(): 234 if ctx.Err() == context.DeadlineExceeded { 235 return logs, errors.New("query timeout exceeded") 236 } 237 return logs, errors.New("query is canceled. " + ctx.Err().Error()) 238 } 239 } 240 } 241 242 // indexedLogs returns the logs matching the filter criteria based on raw block 243 // iteration and bloom matching. 244 func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { 245 var logs []*types.Log 246 247 maxItems := getMaxItems(ctx) 248 249 for ; f.begin <= int64(end); f.begin++ { 250 header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) 251 if header == nil || err != nil { 252 return logs, err 253 } 254 if bloomFilter(header.Bloom, f.addresses, f.topics) { 255 found, err := f.checkMatches(ctx, header) 256 if err != nil { 257 return logs, err 258 } 259 logs = append(logs, found...) 260 if len(logs) > maxItems { 261 return logs, errors.New("query returned more than " + strconv.Itoa(maxItems) + " results") 262 } 263 } 264 select { 265 case <-ctx.Done(): 266 if ctx.Err() == context.DeadlineExceeded { 267 return logs, errors.New("query timeout exceeded") 268 } 269 return logs, errors.New("query is canceled. " + ctx.Err().Error()) 270 default: 271 } 272 } 273 return logs, nil 274 } 275 276 // checkMatches checks if the receipts belonging to the given header contain any log events that 277 // match the filter criteria. This function is called when the bloom filter signals a potential match. 278 func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { 279 // Get the logs of the block 280 logsList, err := f.backend.GetLogs(ctx, header.Hash()) 281 if err != nil { 282 return nil, err 283 } 284 var unfiltered []*types.Log 285 for _, logs := range logsList { 286 unfiltered = append(unfiltered, logs...) 287 } 288 logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 289 if len(logs) > 0 { 290 // We have matching logs, check if we need to resolve full logs via the light client 291 if logs[0].TxHash == (common.Hash{}) { 292 receipts := f.backend.GetBlockReceipts(ctx, header.Hash()) 293 unfiltered = unfiltered[:0] 294 for _, receipt := range receipts { 295 unfiltered = append(unfiltered, receipt.Logs...) 296 } 297 logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 298 } 299 return logs, nil 300 } 301 return nil, nil 302 } 303 304 func includes(addresses []common.Address, a common.Address) bool { 305 for _, addr := range addresses { 306 if addr == a { 307 return true 308 } 309 } 310 311 return false 312 } 313 314 // filterLogs creates a slice of logs matching the given criteria. 315 func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { 316 var ret []*types.Log 317 Logs: 318 for _, log := range logs { 319 if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { 320 continue 321 } 322 if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { 323 continue 324 } 325 326 if len(addresses) > 0 && !includes(addresses, log.Address) { 327 continue 328 } 329 // If the to filtered topics is greater than the amount of topics in logs, skip. 330 if len(topics) > len(log.Topics) { 331 continue Logs 332 } 333 for i, topics := range topics { 334 match := len(topics) == 0 // empty rule set == wildcard 335 for _, topic := range topics { 336 if log.Topics[i] == topic { 337 match = true 338 break 339 } 340 } 341 if !match { 342 continue Logs 343 } 344 } 345 ret = append(ret, log) 346 } 347 return ret 348 } 349 350 func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { 351 if len(addresses) > 0 { 352 var included bool 353 for _, addr := range addresses { 354 if types.BloomLookup(bloom, addr) { 355 included = true 356 break 357 } 358 } 359 if !included { 360 return false 361 } 362 } 363 364 for _, sub := range topics { 365 included := len(sub) == 0 // empty rule set == wildcard 366 for _, topic := range sub { 367 if types.BloomLookup(bloom, topic) { 368 included = true 369 break 370 } 371 } 372 if !included { 373 return false 374 } 375 } 376 return true 377 } 378 379 // getMaxItems returns the value of getLogsCxtKeyMaxItems set in the given context. 380 // If the value is not set in the context, it will returns MaxInt32-1. 381 func getMaxItems(ctx context.Context) int { 382 maxItems := math.MaxInt32 - 1 383 if val := ctx.Value(getLogsCxtKeyMaxItems); val != nil { 384 val, ok := val.(int) 385 if ok { 386 maxItems = val 387 } 388 } 389 return maxItems 390 }