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