github.com/gregpr07/bsc@v1.1.2/eth/filters/api.go (about) 1 // Copyright 2015 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 "encoding/json" 22 "errors" 23 "fmt" 24 "math/big" 25 "sync" 26 "time" 27 28 "github.com/gregpr07/bsc" 29 "github.com/gregpr07/bsc/common" 30 "github.com/gregpr07/bsc/common/gopool" 31 "github.com/gregpr07/bsc/common/hexutil" 32 "github.com/gregpr07/bsc/core/types" 33 "github.com/gregpr07/bsc/ethdb" 34 "github.com/gregpr07/bsc/event" 35 "github.com/gregpr07/bsc/rpc" 36 ) 37 38 // filter is a helper struct that holds meta information over the filter type 39 // and associated subscription in the event system. 40 type filter struct { 41 typ Type 42 deadline *time.Timer // filter is inactiv when deadline triggers 43 hashes []common.Hash 44 crit FilterCriteria 45 logs []*types.Log 46 s *Subscription // associated subscription in event system 47 } 48 49 // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various 50 // information related to the Ethereum protocol such als blocks, transactions and logs. 51 type PublicFilterAPI struct { 52 backend Backend 53 mux *event.TypeMux 54 quit chan struct{} 55 chainDb ethdb.Database 56 events *EventSystem 57 filtersMu sync.Mutex 58 filters map[rpc.ID]*filter 59 timeout time.Duration 60 rangeLimit bool 61 } 62 63 // NewPublicFilterAPI returns a new PublicFilterAPI instance. 64 func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration, rangeLimit bool) *PublicFilterAPI { 65 api := &PublicFilterAPI{ 66 backend: backend, 67 chainDb: backend.ChainDb(), 68 events: NewEventSystem(backend, lightMode), 69 filters: make(map[rpc.ID]*filter), 70 timeout: timeout, 71 rangeLimit: rangeLimit, 72 } 73 go api.timeoutLoop(timeout) 74 75 return api 76 } 77 78 // timeoutLoop runs at the interval set by 'timeout' and deletes filters 79 // that have not been recently used. It is started when the API is created. 80 func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) { 81 var toUninstall []*Subscription 82 ticker := time.NewTicker(timeout) 83 defer ticker.Stop() 84 for { 85 <-ticker.C 86 api.filtersMu.Lock() 87 for id, f := range api.filters { 88 select { 89 case <-f.deadline.C: 90 toUninstall = append(toUninstall, f.s) 91 delete(api.filters, id) 92 default: 93 continue 94 } 95 } 96 api.filtersMu.Unlock() 97 98 // Unsubscribes are processed outside the lock to avoid the following scenario: 99 // event loop attempts broadcasting events to still active filters while 100 // Unsubscribe is waiting for it to process the uninstall request. 101 for _, s := range toUninstall { 102 s.Unsubscribe() 103 } 104 toUninstall = nil 105 } 106 } 107 108 // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes 109 // as transactions enter the pending state. 110 // 111 // It is part of the filter package because this filter can be used through the 112 // `eth_getFilterChanges` polling method that is also used for log filters. 113 // 114 // https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter 115 func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { 116 var ( 117 pendingTxs = make(chan []common.Hash) 118 pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) 119 ) 120 f := &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub} 121 api.filtersMu.Lock() 122 api.filters[pendingTxSub.ID] = f 123 api.filtersMu.Unlock() 124 125 gopool.Submit(func() { 126 for { 127 select { 128 case ph := <-pendingTxs: 129 f.hashes = append(f.hashes, ph...) 130 case <-pendingTxSub.Err(): 131 api.filtersMu.Lock() 132 delete(api.filters, pendingTxSub.ID) 133 api.filtersMu.Unlock() 134 return 135 } 136 } 137 }) 138 139 return pendingTxSub.ID 140 } 141 142 // NewPendingTransactions creates a subscription that is triggered each time a transaction 143 // enters the transaction pool and was signed from one of the transactions this nodes manages. 144 func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { 145 notifier, supported := rpc.NotifierFromContext(ctx) 146 if !supported { 147 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 148 } 149 150 rpcSub := notifier.CreateSubscription() 151 152 gopool.Submit(func() { 153 txHashes := make(chan []common.Hash, 128) 154 pendingTxSub := api.events.SubscribePendingTxs(txHashes) 155 156 for { 157 select { 158 case hashes := <-txHashes: 159 // To keep the original behaviour, send a single tx hash in one notification. 160 // TODO(rjl493456442) Send a batch of tx hashes in one notification 161 for _, h := range hashes { 162 notifier.Notify(rpcSub.ID, h) 163 } 164 case <-rpcSub.Err(): 165 pendingTxSub.Unsubscribe() 166 return 167 case <-notifier.Closed(): 168 pendingTxSub.Unsubscribe() 169 return 170 } 171 } 172 }) 173 174 return rpcSub, nil 175 } 176 177 // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. 178 // It is part of the filter package since polling goes with eth_getFilterChanges. 179 // 180 // https://eth.wiki/json-rpc/API#eth_newblockfilter 181 func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { 182 var ( 183 headers = make(chan *types.Header) 184 headerSub = api.events.SubscribeNewHeads(headers) 185 ) 186 187 api.filtersMu.Lock() 188 api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub} 189 api.filtersMu.Unlock() 190 191 gopool.Submit(func() { 192 for { 193 select { 194 case h := <-headers: 195 api.filtersMu.Lock() 196 if f, found := api.filters[headerSub.ID]; found { 197 f.hashes = append(f.hashes, h.Hash()) 198 } 199 api.filtersMu.Unlock() 200 case <-headerSub.Err(): 201 api.filtersMu.Lock() 202 delete(api.filters, headerSub.ID) 203 api.filtersMu.Unlock() 204 return 205 } 206 } 207 }) 208 209 return headerSub.ID 210 } 211 212 // NewHeads send a notification each time a new (header) block is appended to the chain. 213 func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { 214 notifier, supported := rpc.NotifierFromContext(ctx) 215 if !supported { 216 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 217 } 218 219 rpcSub := notifier.CreateSubscription() 220 221 gopool.Submit(func() { 222 headers := make(chan *types.Header) 223 headersSub := api.events.SubscribeNewHeads(headers) 224 225 for { 226 select { 227 case h := <-headers: 228 notifier.Notify(rpcSub.ID, h) 229 case <-rpcSub.Err(): 230 headersSub.Unsubscribe() 231 return 232 case <-notifier.Closed(): 233 headersSub.Unsubscribe() 234 return 235 } 236 } 237 }) 238 239 return rpcSub, nil 240 } 241 242 // Logs creates a subscription that fires for all new log that match the given filter criteria. 243 func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { 244 notifier, supported := rpc.NotifierFromContext(ctx) 245 if !supported { 246 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 247 } 248 249 var ( 250 rpcSub = notifier.CreateSubscription() 251 matchedLogs = make(chan []*types.Log) 252 ) 253 254 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) 255 if err != nil { 256 return nil, err 257 } 258 259 gopool.Submit(func() { 260 261 for { 262 select { 263 case logs := <-matchedLogs: 264 for _, log := range logs { 265 notifier.Notify(rpcSub.ID, &log) 266 } 267 case <-rpcSub.Err(): // client send an unsubscribe request 268 logsSub.Unsubscribe() 269 return 270 case <-notifier.Closed(): // connection dropped 271 logsSub.Unsubscribe() 272 return 273 } 274 } 275 }) 276 277 return rpcSub, nil 278 } 279 280 // FilterCriteria represents a request to create a new filter. 281 // Same as ethereum.FilterQuery but with UnmarshalJSON() method. 282 type FilterCriteria ethereum.FilterQuery 283 284 // NewFilter creates a new filter and returns the filter id. It can be 285 // used to retrieve logs when the state changes. This method cannot be 286 // used to fetch logs that are already stored in the state. 287 // 288 // Default criteria for the from and to block are "latest". 289 // Using "latest" as block number will return logs for mined blocks. 290 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 291 // In case logs are removed (chain reorg) previously returned logs are returned 292 // again but with the removed property set to true. 293 // 294 // In case "fromBlock" > "toBlock" an error is returned. 295 // 296 // https://eth.wiki/json-rpc/API#eth_newfilter 297 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 298 logs := make(chan []*types.Log) 299 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) 300 if err != nil { 301 return "", err 302 } 303 304 api.filtersMu.Lock() 305 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub} 306 api.filtersMu.Unlock() 307 308 gopool.Submit(func() { 309 for { 310 select { 311 case l := <-logs: 312 api.filtersMu.Lock() 313 if f, found := api.filters[logsSub.ID]; found { 314 f.logs = append(f.logs, l...) 315 } 316 api.filtersMu.Unlock() 317 case <-logsSub.Err(): 318 api.filtersMu.Lock() 319 delete(api.filters, logsSub.ID) 320 api.filtersMu.Unlock() 321 return 322 } 323 } 324 }) 325 326 return logsSub.ID, nil 327 } 328 329 // GetLogs returns logs matching the given argument that are stored within the state. 330 // 331 // https://eth.wiki/json-rpc/API#eth_getlogs 332 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 333 var filter *Filter 334 if crit.BlockHash != nil { 335 // Block filter requested, construct a single-shot filter 336 filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) 337 } else { 338 // Convert the RPC block numbers into internal representations 339 begin := rpc.LatestBlockNumber.Int64() 340 if crit.FromBlock != nil { 341 begin = crit.FromBlock.Int64() 342 } 343 end := rpc.LatestBlockNumber.Int64() 344 if crit.ToBlock != nil { 345 end = crit.ToBlock.Int64() 346 } 347 // Construct the range filter 348 filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics, api.rangeLimit) 349 } 350 // Run the filter and return all the logs 351 logs, err := filter.Logs(ctx) 352 if err != nil { 353 return nil, err 354 } 355 return returnLogs(logs), err 356 } 357 358 // UninstallFilter removes the filter with the given filter id. 359 // 360 // https://eth.wiki/json-rpc/API#eth_uninstallfilter 361 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { 362 api.filtersMu.Lock() 363 f, found := api.filters[id] 364 if found { 365 delete(api.filters, id) 366 } 367 api.filtersMu.Unlock() 368 if found { 369 f.s.Unsubscribe() 370 } 371 372 return found 373 } 374 375 // GetFilterLogs returns the logs for the filter with the given id. 376 // If the filter could not be found an empty array of logs is returned. 377 // 378 // https://eth.wiki/json-rpc/API#eth_getfilterlogs 379 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 380 api.filtersMu.Lock() 381 f, found := api.filters[id] 382 api.filtersMu.Unlock() 383 384 if !found || f.typ != LogsSubscription { 385 return nil, fmt.Errorf("filter not found") 386 } 387 388 var filter *Filter 389 if f.crit.BlockHash != nil { 390 // Block filter requested, construct a single-shot filter 391 filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) 392 } else { 393 // Convert the RPC block numbers into internal representations 394 begin := rpc.LatestBlockNumber.Int64() 395 if f.crit.FromBlock != nil { 396 begin = f.crit.FromBlock.Int64() 397 } 398 end := rpc.LatestBlockNumber.Int64() 399 if f.crit.ToBlock != nil { 400 end = f.crit.ToBlock.Int64() 401 } 402 // Construct the range filter 403 filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit) 404 } 405 // Run the filter and return all the logs 406 logs, err := filter.Logs(ctx) 407 if err != nil { 408 return nil, err 409 } 410 return returnLogs(logs), nil 411 } 412 413 // GetFilterChanges returns the logs for the filter with the given id since 414 // last time it was called. This can be used for polling. 415 // 416 // For pending transaction and block filters the result is []common.Hash. 417 // (pending)Log filters return []Log. 418 // 419 // https://eth.wiki/json-rpc/API#eth_getfilterchanges 420 func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { 421 api.filtersMu.Lock() 422 defer api.filtersMu.Unlock() 423 424 if f, found := api.filters[id]; found { 425 if !f.deadline.Stop() { 426 // timer expired but filter is not yet removed in timeout loop 427 // receive timer value and reset timer 428 <-f.deadline.C 429 } 430 f.deadline.Reset(api.timeout) 431 432 switch f.typ { 433 case PendingTransactionsSubscription, BlocksSubscription: 434 hashes := f.hashes 435 f.hashes = nil 436 return returnHashes(hashes), nil 437 case LogsSubscription, MinedAndPendingLogsSubscription: 438 logs := f.logs 439 f.logs = nil 440 return returnLogs(logs), nil 441 } 442 } 443 444 return []interface{}{}, fmt.Errorf("filter not found") 445 } 446 447 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 448 // otherwise the given hashes array is returned. 449 func returnHashes(hashes []common.Hash) []common.Hash { 450 if hashes == nil { 451 return []common.Hash{} 452 } 453 return hashes 454 } 455 456 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 457 // otherwise the given logs array is returned. 458 func returnLogs(logs []*types.Log) []*types.Log { 459 if logs == nil { 460 return []*types.Log{} 461 } 462 return logs 463 } 464 465 // UnmarshalJSON sets *args fields with given data. 466 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 467 type input struct { 468 BlockHash *common.Hash `json:"blockHash"` 469 FromBlock *rpc.BlockNumber `json:"fromBlock"` 470 ToBlock *rpc.BlockNumber `json:"toBlock"` 471 Addresses interface{} `json:"address"` 472 Topics []interface{} `json:"topics"` 473 } 474 475 var raw input 476 if err := json.Unmarshal(data, &raw); err != nil { 477 return err 478 } 479 480 if raw.BlockHash != nil { 481 if raw.FromBlock != nil || raw.ToBlock != nil { 482 // BlockHash is mutually exclusive with FromBlock/ToBlock criteria 483 return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") 484 } 485 args.BlockHash = raw.BlockHash 486 } else { 487 if raw.FromBlock != nil { 488 args.FromBlock = big.NewInt(raw.FromBlock.Int64()) 489 } 490 491 if raw.ToBlock != nil { 492 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 493 } 494 } 495 496 args.Addresses = []common.Address{} 497 498 if raw.Addresses != nil { 499 // raw.Address can contain a single address or an array of addresses 500 switch rawAddr := raw.Addresses.(type) { 501 case []interface{}: 502 for i, addr := range rawAddr { 503 if strAddr, ok := addr.(string); ok { 504 addr, err := decodeAddress(strAddr) 505 if err != nil { 506 return fmt.Errorf("invalid address at index %d: %v", i, err) 507 } 508 args.Addresses = append(args.Addresses, addr) 509 } else { 510 return fmt.Errorf("non-string address at index %d", i) 511 } 512 } 513 case string: 514 addr, err := decodeAddress(rawAddr) 515 if err != nil { 516 return fmt.Errorf("invalid address: %v", err) 517 } 518 args.Addresses = []common.Address{addr} 519 default: 520 return errors.New("invalid addresses in query") 521 } 522 } 523 524 // topics is an array consisting of strings and/or arrays of strings. 525 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 526 if len(raw.Topics) > 0 { 527 args.Topics = make([][]common.Hash, len(raw.Topics)) 528 for i, t := range raw.Topics { 529 switch topic := t.(type) { 530 case nil: 531 // ignore topic when matching logs 532 533 case string: 534 // match specific topic 535 top, err := decodeTopic(topic) 536 if err != nil { 537 return err 538 } 539 args.Topics[i] = []common.Hash{top} 540 541 case []interface{}: 542 // or case e.g. [null, "topic0", "topic1"] 543 for _, rawTopic := range topic { 544 if rawTopic == nil { 545 // null component, match all 546 args.Topics[i] = nil 547 break 548 } 549 if topic, ok := rawTopic.(string); ok { 550 parsed, err := decodeTopic(topic) 551 if err != nil { 552 return err 553 } 554 args.Topics[i] = append(args.Topics[i], parsed) 555 } else { 556 return fmt.Errorf("invalid topic(s)") 557 } 558 } 559 default: 560 return fmt.Errorf("invalid topic(s)") 561 } 562 } 563 } 564 565 return nil 566 } 567 568 func decodeAddress(s string) (common.Address, error) { 569 b, err := hexutil.Decode(s) 570 if err == nil && len(b) != common.AddressLength { 571 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) 572 } 573 return common.BytesToAddress(b), err 574 } 575 576 func decodeTopic(s string) (common.Hash, error) { 577 b, err := hexutil.Decode(s) 578 if err == nil && len(b) != common.HashLength { 579 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) 580 } 581 return common.BytesToHash(b), err 582 }