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