github.com/tuotoo/go-ethereum@v1.7.4-0.20171121184211-049797d40a24/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 "math/big" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/core" 25 "github.com/ethereum/go-ethereum/core/bloombits" 26 "github.com/ethereum/go-ethereum/core/types" 27 "github.com/ethereum/go-ethereum/ethdb" 28 "github.com/ethereum/go-ethereum/event" 29 "github.com/ethereum/go-ethereum/rpc" 30 ) 31 32 type Backend interface { 33 ChainDb() ethdb.Database 34 EventMux() *event.TypeMux 35 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 36 GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) 37 38 SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription 39 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription 40 SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription 41 SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 42 43 BloomStatus() (uint64, uint64) 44 ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) 45 } 46 47 // Filter can be used to retrieve and filter logs. 48 type Filter struct { 49 backend Backend 50 51 db ethdb.Database 52 begin, end int64 53 addresses []common.Address 54 topics [][]common.Hash 55 56 matcher *bloombits.Matcher 57 } 58 59 // New creates a new filter which uses a bloom filter on blocks to figure out whether 60 // a particular block is interesting or not. 61 func New(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { 62 // Flatten the address and topic filter clauses into a single bloombits filter 63 // system. Since the bloombits are not positional, nil topics are permitted, 64 // which get flattened into a nil byte slice. 65 var filters [][][]byte 66 if len(addresses) > 0 { 67 filter := make([][]byte, len(addresses)) 68 for i, address := range addresses { 69 filter[i] = address.Bytes() 70 } 71 filters = append(filters, filter) 72 } 73 for _, topicList := range topics { 74 filter := make([][]byte, len(topicList)) 75 for i, topic := range topicList { 76 filter[i] = topic.Bytes() 77 } 78 filters = append(filters, filter) 79 } 80 // Assemble and return the filter 81 size, _ := backend.BloomStatus() 82 83 return &Filter{ 84 backend: backend, 85 begin: begin, 86 end: end, 87 addresses: addresses, 88 topics: topics, 89 db: backend.ChainDb(), 90 matcher: bloombits.NewMatcher(size, filters), 91 } 92 } 93 94 // Logs searches the blockchain for matching log entries, returning all from the 95 // first block that contains matches, updating the start of the filter accordingly. 96 func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { 97 // Figure out the limits of the filter range 98 header, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 99 if header == nil { 100 return nil, nil 101 } 102 head := header.Number.Uint64() 103 104 if f.begin == -1 { 105 f.begin = int64(head) 106 } 107 end := uint64(f.end) 108 if f.end == -1 { 109 end = head 110 } 111 // Gather all indexed logs, and finish with non indexed ones 112 var ( 113 logs []*types.Log 114 err error 115 ) 116 size, sections := f.backend.BloomStatus() 117 if indexed := sections * size; indexed > uint64(f.begin) { 118 if indexed > end { 119 logs, err = f.indexedLogs(ctx, end) 120 } else { 121 logs, err = f.indexedLogs(ctx, indexed-1) 122 } 123 if err != nil { 124 return logs, err 125 } 126 } 127 rest, err := f.unindexedLogs(ctx, end) 128 logs = append(logs, rest...) 129 return logs, err 130 } 131 132 // indexedLogs returns the logs matching the filter criteria based on the bloom 133 // bits indexed available locally or via the network. 134 func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { 135 // Create a matcher session and request servicing from the backend 136 matches := make(chan uint64, 64) 137 138 session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) 139 if err != nil { 140 return nil, err 141 } 142 defer session.Close() 143 144 f.backend.ServiceFilter(ctx, session) 145 146 // Iterate over the matches until exhausted or context closed 147 var logs []*types.Log 148 149 for { 150 select { 151 case number, ok := <-matches: 152 // Abort if all matches have been fulfilled 153 if !ok { 154 err := session.Error() 155 if err == nil { 156 f.begin = int64(end) + 1 157 } 158 return logs, err 159 } 160 f.begin = int64(number) + 1 161 162 // Retrieve the suggested block and pull any truly matching logs 163 header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) 164 if header == nil || err != nil { 165 return logs, err 166 } 167 found, err := f.checkMatches(ctx, header) 168 if err != nil { 169 return logs, err 170 } 171 logs = append(logs, found...) 172 173 case <-ctx.Done(): 174 return logs, ctx.Err() 175 } 176 } 177 } 178 179 // indexedLogs returns the logs matching the filter criteria based on raw block 180 // iteration and bloom matching. 181 func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { 182 var logs []*types.Log 183 184 for ; f.begin <= int64(end); f.begin++ { 185 header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) 186 if header == nil || err != nil { 187 return logs, err 188 } 189 if bloomFilter(header.Bloom, f.addresses, f.topics) { 190 found, err := f.checkMatches(ctx, header) 191 if err != nil { 192 return logs, err 193 } 194 logs = append(logs, found...) 195 } 196 } 197 return logs, nil 198 } 199 200 // checkMatches checks if the receipts belonging to the given header contain any log events that 201 // match the filter criteria. This function is called when the bloom filter signals a potential match. 202 func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { 203 // Get the logs of the block 204 receipts, err := f.backend.GetReceipts(ctx, header.Hash()) 205 if err != nil { 206 return nil, err 207 } 208 var unfiltered []*types.Log 209 for _, receipt := range receipts { 210 unfiltered = append(unfiltered, receipt.Logs...) 211 } 212 logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 213 if len(logs) > 0 { 214 return logs, nil 215 } 216 return nil, nil 217 } 218 219 func includes(addresses []common.Address, a common.Address) bool { 220 for _, addr := range addresses { 221 if addr == a { 222 return true 223 } 224 } 225 226 return false 227 } 228 229 // filterLogs creates a slice of logs matching the given criteria. 230 func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { 231 var ret []*types.Log 232 Logs: 233 for _, log := range logs { 234 if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { 235 continue 236 } 237 if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { 238 continue 239 } 240 241 if len(addresses) > 0 && !includes(addresses, log.Address) { 242 continue 243 } 244 // If the to filtered topics is greater than the amount of topics in logs, skip. 245 if len(topics) > len(log.Topics) { 246 continue Logs 247 } 248 for i, topics := range topics { 249 match := len(topics) == 0 // empty rule set == wildcard 250 for _, topic := range topics { 251 if log.Topics[i] == topic { 252 match = true 253 break 254 } 255 } 256 if !match { 257 continue Logs 258 } 259 } 260 ret = append(ret, log) 261 } 262 return ret 263 } 264 265 func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { 266 if len(addresses) > 0 { 267 var included bool 268 for _, addr := range addresses { 269 if types.BloomLookup(bloom, addr) { 270 included = true 271 break 272 } 273 } 274 if !included { 275 return false 276 } 277 } 278 279 for _, sub := range topics { 280 included := len(sub) == 0 // empty rule set == wildcard 281 for _, topic := range sub { 282 if types.BloomLookup(bloom, topic) { 283 included = true 284 break 285 } 286 } 287 if !included { 288 return false 289 } 290 } 291 return true 292 }