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