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