github.com/amazechain/amc@v0.1.3/internal/api/filters/filter.go (about) 1 package filters 2 3 import ( 4 "context" 5 "errors" 6 "github.com/amazechain/amc/common" 7 "github.com/amazechain/amc/common/block" 8 "github.com/amazechain/amc/common/types" 9 "github.com/amazechain/amc/internal" 10 "github.com/amazechain/amc/internal/consensus" 11 vm2 "github.com/amazechain/amc/internal/vm" 12 "github.com/amazechain/amc/internal/vm/evmtypes" 13 "github.com/amazechain/amc/modules/rpc/jsonrpc" 14 "github.com/holiman/uint256" 15 "github.com/ledgerwatch/erigon-lib/kv" 16 "math/big" 17 ) 18 19 type Api interface { 20 TxsPool() common.ITxsPool 21 Database() kv.RwDB 22 Engine() consensus.Engine 23 BlockChain() common.IBlockChain 24 GetEvm(ctx context.Context, msg internal.Message, ibs evmtypes.IntraBlockState, header block.IHeader, vmConfig *vm2.Config) (*vm2.EVM, func() error, error) 25 } 26 27 // Filter can be used to retrieve and filter logs. 28 type Filter struct { 29 api Api 30 31 db kv.RwDB 32 addresses []types.Address 33 topics [][]types.Hash 34 35 block types.Hash // Block hash if filtering a single block 36 begin, end int64 // Range interval if filtering multiple blocks 37 38 //matcher *bloombits.Matcher 39 } 40 41 // NewRangeFilter creates a new filter which uses a bloom filter on blocks to 42 // figure out whether a particular block is interesting or not. 43 func NewRangeFilter(api Api, begin, end int64, addresses []types.Address, topics [][]types.Hash) *Filter { 44 // Flatten the address and topic filter clauses into a single bloombits filter 45 // system. Since the bloombits are not positional, nil topics are permitted, 46 // which get flattened into a nil byte slice. 47 var filters [][][]byte 48 if len(addresses) > 0 { 49 filter := make([][]byte, len(addresses)) 50 for i, address := range addresses { 51 filter[i] = address.Bytes() 52 } 53 filters = append(filters, filter) 54 } 55 for _, topicList := range topics { 56 filter := make([][]byte, len(topicList)) 57 for i, topic := range topicList { 58 filter[i] = topic.Bytes() 59 } 60 filters = append(filters, filter) 61 } 62 //size, _ := api.BloomStatus() 63 64 // Create a generic filter and convert it into a range filter 65 filter := newFilter(api, addresses, topics) 66 67 //filter.matcher = bloombits.NewMatcher(size, filters) 68 filter.begin = begin 69 filter.end = end 70 71 return filter 72 } 73 74 // NewBlockFilter creates a new filter which directly inspects the contents of 75 // a block to figure out whether it is interesting or not. 76 func NewBlockFilter(api Api, block types.Hash, addresses []types.Address, topics [][]types.Hash) *Filter { 77 // Create a generic filter and convert it into a block filter 78 filter := newFilter(api, addresses, topics) 79 filter.block = block 80 return filter 81 } 82 83 // newFilter creates a generic filter that can either filter based on a block hash, 84 // or based on range queries. The search criteria needs to be explicitly set. 85 func newFilter(api Api, addresses []types.Address, topics [][]types.Hash) *Filter { 86 return &Filter{ 87 api: api, 88 addresses: addresses, 89 topics: topics, 90 db: api.Database(), 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) ([]*block.Log, error) { 97 // If we're doing singleton block filtering, execute and return 98 if f.block != (types.Hash{}) { 99 header, err := f.api.BlockChain().GetHeaderByHash(f.block) 100 if err != nil { 101 return nil, err 102 } 103 if header == nil { 104 return nil, errors.New("unknown block") 105 } 106 return f.blockLogs(ctx, header) 107 } 108 // Short-cut if all we care about is pending logs 109 if f.begin == jsonrpc.PendingBlockNumber.Int64() { 110 if f.end != jsonrpc.PendingBlockNumber.Int64() { 111 return nil, errors.New("invalid block range") 112 } 113 return f.pendingLogs() 114 } 115 // Figure out the limits of the filter range 116 header := f.api.BlockChain().CurrentBlock().Header() 117 if header == nil { 118 return nil, nil 119 } 120 var ( 121 head = header.Number64().Uint64() 122 end = uint64(f.end) 123 pending = f.end == jsonrpc.PendingBlockNumber.Int64() 124 ) 125 if f.begin == jsonrpc.LatestBlockNumber.Int64() { 126 f.begin = int64(head) 127 } 128 if f.end == jsonrpc.LatestBlockNumber.Int64() || f.end == jsonrpc.PendingBlockNumber.Int64() { 129 end = head 130 } 131 // Gather all indexed logs, and finish with non indexed ones 132 var ( 133 logs []*block.Log 134 err error 135 size, sections = uint64(4096), uint64(10) 136 //todo 137 ) 138 if indexed := sections * size; indexed > uint64(f.begin) { 139 if indexed > end { 140 logs, err = f.indexedLogs(ctx, end) 141 } else { 142 logs, err = f.indexedLogs(ctx, indexed-1) 143 } 144 if err != nil { 145 return logs, err 146 } 147 } 148 rest, err := f.unindexedLogs(ctx, end) 149 logs = append(logs, rest...) 150 if pending { 151 pendingLogs, err := f.pendingLogs() 152 if err != nil { 153 return nil, err 154 } 155 logs = append(logs, pendingLogs...) 156 } 157 return logs, err 158 } 159 160 // indexedLogs returns the logs matching the filter criteria based on the bloom 161 // bits indexed available locally or via the network. 162 func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*block.Log, error) { 163 // Create a matcher session and request servicing from the backend 164 matches := make(chan uint64, 64) 165 166 //session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) 167 //if err != nil { 168 // return nil, err 169 //} 170 //defer session.Close() 171 172 //f.api.ServiceFilter(ctx, session) 173 174 // Iterate over the matches until exhausted or context closed 175 var logs []*block.Log 176 177 for { 178 select { 179 case number, ok := <-matches: 180 // Abort if all matches have been fulfilled 181 if !ok { 182 //err := session.Error() 183 //if err == nil { 184 // f.begin = int64(end) + 1 185 //} 186 return logs, nil 187 } 188 f.begin = int64(number) + 1 189 190 // Retrieve the suggested block and pull any truly matching logs 191 header := f.api.BlockChain().GetHeaderByNumber(uint256.NewInt(number)) 192 if header == nil { 193 return logs, nil 194 } 195 found, err := f.checkMatches(ctx, header) 196 if err != nil { 197 return logs, err 198 } 199 logs = append(logs, found...) 200 201 case <-ctx.Done(): 202 return logs, ctx.Err() 203 } 204 } 205 } 206 207 // unindexedLogs returns the logs matching the filter criteria based on raw block 208 // iteration and bloom matching. 209 func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*block.Log, error) { 210 var logs []*block.Log 211 212 for ; f.begin <= int64(end); f.begin++ { 213 header := f.api.BlockChain().GetHeaderByNumber(uint256.NewInt(uint64(f.begin))) 214 if header == nil { 215 return logs, nil 216 } 217 found, err := f.blockLogs(ctx, header) 218 if err != nil { 219 return logs, err 220 } 221 logs = append(logs, found...) 222 } 223 return logs, nil 224 } 225 226 // blockLogs returns the logs matching the filter criteria within a single block. 227 func (f *Filter) blockLogs(ctx context.Context, header block.IHeader) (logs []*block.Log, err error) { 228 //todo header.Bloom 229 bloom, _ := types.NewBloom(100) 230 if bloomFilter(bloom, f.addresses, f.topics) { 231 found, err := f.checkMatches(ctx, header) 232 if err != nil { 233 return logs, err 234 } 235 logs = append(logs, found...) 236 } 237 return logs, nil 238 } 239 240 // checkMatches checks if the receipts belonging to the given header contain any log events that 241 // match the filter criteria. This function is called when the bloom filter signals a potential match. 242 func (f *Filter) checkMatches(ctx context.Context, header block.IHeader) (logs []*block.Log, err error) { 243 // Get the logs of the block 244 logsList, err := f.api.BlockChain().GetLogs(header.Hash()) 245 if err != nil { 246 return nil, err 247 } 248 var unfiltered []*block.Log 249 for _, logs := range logsList { 250 unfiltered = append(unfiltered, logs...) 251 } 252 logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 253 if len(logs) > 0 { 254 // We have matching logs, check if we need to resolve full logs via the light client 255 if logs[0].TxHash == (types.Hash{}) { 256 receipts, err := f.api.BlockChain().GetReceipts(header.Hash()) 257 if err != nil { 258 return nil, err 259 } 260 unfiltered = unfiltered[:0] 261 for _, receipt := range receipts { 262 unfiltered = append(unfiltered, receipt.Logs...) 263 } 264 logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) 265 } 266 return logs, nil 267 } 268 return nil, nil 269 } 270 271 // pendingLogs returns the logs matching the filter criteria within the pending block. 272 func (f *Filter) pendingLogs() ([]*block.Log, error) { 273 //todo 274 //pendingBlock, receipts := f.api.PendingBlockAndReceipts() 275 //if bloomFilter(pendingBlock.Bloom(), f.addresses, f.topics) { 276 // var unfiltered []*block.Log 277 // for _, r := range receipts { 278 // unfiltered = append(unfiltered, r.Logs...) 279 // } 280 // return filterLogs(unfiltered, nil, nil, f.addresses, f.topics), nil 281 //} 282 return nil, nil 283 } 284 285 func includes(addresses []types.Address, a types.Address) bool { 286 for _, addr := range addresses { 287 if addr == a { 288 return true 289 } 290 } 291 292 return false 293 } 294 295 // filterLogs creates a slice of logs matching the given criteria. 296 func filterLogs(logs []*block.Log, fromBlock, toBlock *big.Int, addresses []types.Address, topics [][]types.Hash) []*block.Log { 297 var ret []*block.Log 298 Logs: 299 for _, log := range logs { 300 if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber.Uint64() { 301 continue 302 } 303 if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber.Uint64() { 304 continue 305 } 306 307 if len(addresses) > 0 && !includes(addresses, log.Address) { 308 continue 309 } 310 // If the to filtered topics is greater than the amount of topics in logs, skip. 311 if len(topics) > len(log.Topics) { 312 continue 313 } 314 for i, sub := range topics { 315 match := len(sub) == 0 // empty rule set == wildcard 316 for _, topic := range sub { 317 if log.Topics[i] == topic { 318 match = true 319 break 320 } 321 } 322 if !match { 323 continue Logs 324 } 325 } 326 ret = append(ret, log) 327 } 328 return ret 329 } 330 331 func bloomFilter(bloom *types.Bloom, addresses []types.Address, topics [][]types.Hash) bool { 332 if len(addresses) > 0 { 333 var included bool 334 for _, addr := range addresses { 335 if bloom.Contain(addr.Bytes()) { 336 included = true 337 break 338 } 339 } 340 if !included { 341 return false 342 } 343 } 344 345 for _, sub := range topics { 346 included := len(sub) == 0 // empty rule set == wildcard 347 for _, topic := range sub { 348 if bloom.Contain(topic.Bytes()) { 349 included = true 350 break 351 } 352 } 353 if !included { 354 return false 355 } 356 } 357 return true 358 }