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