github.com/ava-labs/subnet-evm@v0.6.4/eth/filters/api.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2015 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package filters 28 29 import ( 30 "context" 31 "encoding/json" 32 "errors" 33 "fmt" 34 "math/big" 35 "sync" 36 "time" 37 38 "github.com/ava-labs/subnet-evm/core/types" 39 "github.com/ava-labs/subnet-evm/interfaces" 40 "github.com/ava-labs/subnet-evm/internal/ethapi" 41 "github.com/ava-labs/subnet-evm/rpc" 42 "github.com/ethereum/go-ethereum/common" 43 "github.com/ethereum/go-ethereum/common/hexutil" 44 "github.com/ethereum/go-ethereum/event" 45 ) 46 47 var ( 48 errInvalidTopic = errors.New("invalid topic(s)") 49 errFilterNotFound = errors.New("filter not found") 50 ) 51 52 // filter is a helper struct that holds meta information over the filter type 53 // and associated subscription in the event system. 54 type filter struct { 55 typ Type 56 deadline *time.Timer // filter is inactive when deadline triggers 57 hashes []common.Hash 58 fullTx bool 59 txs []*types.Transaction 60 crit FilterCriteria 61 logs []*types.Log 62 s *Subscription // associated subscription in event system 63 } 64 65 // FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various 66 // information related to the Ethereum protocol such as blocks, transactions and logs. 67 type FilterAPI struct { 68 sys *FilterSystem 69 events *EventSystem 70 filtersMu sync.Mutex 71 filters map[rpc.ID]*filter 72 timeout time.Duration 73 } 74 75 // NewFilterAPI returns a new FilterAPI instance. 76 func NewFilterAPI(system *FilterSystem) *FilterAPI { 77 api := &FilterAPI{ 78 sys: system, 79 events: NewEventSystem(system), 80 filters: make(map[rpc.ID]*filter), 81 timeout: system.cfg.Timeout, 82 } 83 go api.timeoutLoop(system.cfg.Timeout) 84 85 return api 86 } 87 88 // timeoutLoop runs at the interval set by 'timeout' and deletes filters 89 // that have not been recently used. It is started when the API is created. 90 func (api *FilterAPI) timeoutLoop(timeout time.Duration) { 91 var toUninstall []*Subscription 92 ticker := time.NewTicker(timeout) 93 defer ticker.Stop() 94 for { 95 <-ticker.C 96 api.filtersMu.Lock() 97 for id, f := range api.filters { 98 select { 99 case <-f.deadline.C: 100 toUninstall = append(toUninstall, f.s) 101 delete(api.filters, id) 102 default: 103 continue 104 } 105 } 106 api.filtersMu.Unlock() 107 108 // Unsubscribes are processed outside the lock to avoid the following scenario: 109 // event loop attempts broadcasting events to still active filters while 110 // Unsubscribe is waiting for it to process the uninstall request. 111 for _, s := range toUninstall { 112 s.Unsubscribe() 113 } 114 toUninstall = nil 115 } 116 } 117 118 // NewPendingTransactionFilter creates a filter that fetches pending transactions 119 // as transactions enter the pending state. 120 // 121 // It is part of the filter package because this filter can be used through the 122 // `eth_getFilterChanges` polling method that is also used for log filters. 123 func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { 124 var ( 125 pendingTxs = make(chan []*types.Transaction) 126 pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) 127 ) 128 129 api.filtersMu.Lock() 130 api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, fullTx: fullTx != nil && *fullTx, deadline: time.NewTimer(api.timeout), txs: make([]*types.Transaction, 0), s: pendingTxSub} 131 api.filtersMu.Unlock() 132 133 go func() { 134 for { 135 select { 136 case pTx := <-pendingTxs: 137 api.filtersMu.Lock() 138 if f, found := api.filters[pendingTxSub.ID]; found { 139 f.txs = append(f.txs, pTx...) 140 } 141 api.filtersMu.Unlock() 142 case <-pendingTxSub.Err(): 143 api.filtersMu.Lock() 144 delete(api.filters, pendingTxSub.ID) 145 api.filtersMu.Unlock() 146 return 147 } 148 } 149 }() 150 151 return pendingTxSub.ID 152 } 153 154 // NewPendingTransactions creates a subscription that is triggered each time a 155 // transaction enters the transaction pool. If fullTx is true the full tx is 156 // sent to the client, otherwise the hash is sent. 157 func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { 158 notifier, supported := rpc.NotifierFromContext(ctx) 159 if !supported { 160 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 161 } 162 163 rpcSub := notifier.CreateSubscription() 164 165 go func() { 166 txs := make(chan []*types.Transaction, 128) 167 pendingTxSub := api.events.SubscribePendingTxs(txs) 168 chainConfig := api.sys.backend.ChainConfig() 169 170 for { 171 select { 172 case txs := <-txs: 173 // To keep the original behaviour, send a single tx hash in one notification. 174 // TODO(rjl493456442) Send a batch of tx hashes in one notification 175 latest := api.sys.backend.CurrentHeader() 176 for _, tx := range txs { 177 if fullTx != nil && *fullTx { 178 rpcTx := ethapi.NewRPCTransaction(tx, latest, latest.BaseFee, chainConfig) 179 notifier.Notify(rpcSub.ID, rpcTx) 180 } else { 181 notifier.Notify(rpcSub.ID, tx.Hash()) 182 } 183 } 184 case <-rpcSub.Err(): 185 pendingTxSub.Unsubscribe() 186 return 187 case <-notifier.Closed(): 188 pendingTxSub.Unsubscribe() 189 return 190 } 191 } 192 }() 193 194 return rpcSub, nil 195 } 196 197 // NewAcceptedTransactions creates a subscription that is triggered each time a 198 // transaction is accepted. If fullTx is true the full tx is 199 // sent to the client, otherwise the hash is sent. 200 func (api *FilterAPI) NewAcceptedTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { 201 notifier, supported := rpc.NotifierFromContext(ctx) 202 if !supported { 203 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 204 } 205 206 rpcSub := notifier.CreateSubscription() 207 208 go func() { 209 txs := make(chan []*types.Transaction, 128) 210 acceptedTxSub := api.events.SubscribeAcceptedTxs(txs) 211 chainConfig := api.sys.backend.ChainConfig() 212 213 for { 214 select { 215 case txs := <-txs: 216 // To keep the original behaviour, send a single tx hash in one notification. 217 // TODO(rjl493456442) Send a batch of tx hashes in one notification 218 latest := api.sys.backend.LastAcceptedBlock().Header() 219 for _, tx := range txs { 220 if fullTx != nil && *fullTx { 221 rpcTx := ethapi.NewRPCTransaction(tx, latest, latest.BaseFee, chainConfig) 222 notifier.Notify(rpcSub.ID, rpcTx) 223 } else { 224 notifier.Notify(rpcSub.ID, tx.Hash()) 225 } 226 } 227 case <-rpcSub.Err(): 228 acceptedTxSub.Unsubscribe() 229 return 230 case <-notifier.Closed(): 231 acceptedTxSub.Unsubscribe() 232 return 233 } 234 } 235 }() 236 237 return rpcSub, nil 238 } 239 240 // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. 241 // It is part of the filter package since polling goes with eth_getFilterChanges. 242 func (api *FilterAPI) NewBlockFilter() rpc.ID { 243 var ( 244 headers = make(chan *types.Header) 245 headerSub *Subscription 246 ) 247 248 if api.sys.backend.IsAllowUnfinalizedQueries() { 249 headerSub = api.events.SubscribeNewHeads(headers) 250 } else { 251 headerSub = api.events.SubscribeAcceptedHeads(headers) 252 } 253 254 api.filtersMu.Lock() 255 api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub} 256 api.filtersMu.Unlock() 257 258 go func() { 259 for { 260 select { 261 case h := <-headers: 262 api.filtersMu.Lock() 263 if f, found := api.filters[headerSub.ID]; found { 264 f.hashes = append(f.hashes, h.Hash()) 265 } 266 api.filtersMu.Unlock() 267 case <-headerSub.Err(): 268 api.filtersMu.Lock() 269 delete(api.filters, headerSub.ID) 270 api.filtersMu.Unlock() 271 return 272 } 273 } 274 }() 275 276 return headerSub.ID 277 } 278 279 // NewHeads send a notification each time a new (header) block is appended to the chain. 280 func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { 281 notifier, supported := rpc.NotifierFromContext(ctx) 282 if !supported { 283 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 284 } 285 286 rpcSub := notifier.CreateSubscription() 287 288 go func() { 289 var ( 290 headers = make(chan *types.Header) 291 headersSub event.Subscription 292 ) 293 294 if api.sys.backend.IsAllowUnfinalizedQueries() { 295 headersSub = api.events.SubscribeNewHeads(headers) 296 } else { 297 headersSub = api.events.SubscribeAcceptedHeads(headers) 298 } 299 300 for { 301 select { 302 case h := <-headers: 303 notifier.Notify(rpcSub.ID, h) 304 case <-rpcSub.Err(): 305 headersSub.Unsubscribe() 306 return 307 case <-notifier.Closed(): 308 headersSub.Unsubscribe() 309 return 310 } 311 } 312 }() 313 314 return rpcSub, nil 315 } 316 317 // Logs creates a subscription that fires for all new log that match the given filter criteria. 318 func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { 319 notifier, supported := rpc.NotifierFromContext(ctx) 320 if !supported { 321 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 322 } 323 324 var ( 325 rpcSub = notifier.CreateSubscription() 326 matchedLogs = make(chan []*types.Log) 327 logsSub event.Subscription 328 err error 329 ) 330 331 if api.sys.backend.IsAllowUnfinalizedQueries() { 332 logsSub, err = api.events.SubscribeLogs(interfaces.FilterQuery(crit), matchedLogs) 333 if err != nil { 334 return nil, err 335 } 336 } else { 337 logsSub, err = api.events.SubscribeAcceptedLogs(interfaces.FilterQuery(crit), matchedLogs) 338 if err != nil { 339 return nil, err 340 } 341 } 342 343 go func() { 344 for { 345 select { 346 case logs := <-matchedLogs: 347 for _, log := range logs { 348 log := log 349 notifier.Notify(rpcSub.ID, &log) 350 } 351 case <-rpcSub.Err(): // client send an unsubscribe request 352 logsSub.Unsubscribe() 353 return 354 case <-notifier.Closed(): // connection dropped 355 logsSub.Unsubscribe() 356 return 357 } 358 } 359 }() 360 361 return rpcSub, nil 362 } 363 364 // FilterCriteria represents a request to create a new filter. 365 // Same as interfaces.FilterQuery but with UnmarshalJSON() method. 366 type FilterCriteria interfaces.FilterQuery 367 368 // NewFilter creates a new filter and returns the filter id. It can be 369 // used to retrieve logs when the state changes. This method cannot be 370 // used to fetch logs that are already stored in the state. 371 // 372 // Default criteria for the from and to block are "latest". 373 // Using "latest" as block number will return logs for mined blocks. 374 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 375 // In case logs are removed (chain reorg) previously returned logs are returned 376 // again but with the removed property set to true. 377 // 378 // In case "fromBlock" > "toBlock" an error is returned. 379 func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 380 var ( 381 logs = make(chan []*types.Log) 382 logsSub *Subscription 383 err error 384 ) 385 386 if api.sys.backend.IsAllowUnfinalizedQueries() { 387 logsSub, err = api.events.SubscribeLogs(interfaces.FilterQuery(crit), logs) 388 if err != nil { 389 return "", err 390 } 391 } else { 392 logsSub, err = api.events.SubscribeAcceptedLogs(interfaces.FilterQuery(crit), logs) 393 if err != nil { 394 return "", err 395 } 396 } 397 398 api.filtersMu.Lock() 399 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub} 400 api.filtersMu.Unlock() 401 402 go func() { 403 for { 404 select { 405 case l := <-logs: 406 api.filtersMu.Lock() 407 if f, found := api.filters[logsSub.ID]; found { 408 f.logs = append(f.logs, l...) 409 } 410 api.filtersMu.Unlock() 411 case <-logsSub.Err(): 412 api.filtersMu.Lock() 413 delete(api.filters, logsSub.ID) 414 api.filtersMu.Unlock() 415 return 416 } 417 } 418 }() 419 420 return logsSub.ID, nil 421 } 422 423 // GetLogs returns logs matching the given argument that are stored within the state. 424 func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 425 var filter *Filter 426 if crit.BlockHash != nil { 427 // Block filter requested, construct a single-shot filter 428 filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics) 429 } else { 430 // Convert the RPC block numbers into internal representations 431 // LatestBlockNumber is left in place here to be handled 432 // correctly within NewRangeFilter 433 begin := rpc.LatestBlockNumber.Int64() 434 if crit.FromBlock != nil { 435 begin = crit.FromBlock.Int64() 436 } 437 end := rpc.LatestBlockNumber.Int64() 438 if crit.ToBlock != nil { 439 end = crit.ToBlock.Int64() 440 } 441 // Construct the range filter 442 filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) 443 } 444 // Run the filter and return all the logs 445 logs, err := filter.Logs(ctx) 446 if err != nil { 447 return nil, err 448 } 449 return returnLogs(logs), err 450 } 451 452 // UninstallFilter removes the filter with the given filter id. 453 func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { 454 api.filtersMu.Lock() 455 f, found := api.filters[id] 456 if found { 457 delete(api.filters, id) 458 } 459 api.filtersMu.Unlock() 460 if found { 461 f.s.Unsubscribe() 462 } 463 464 return found 465 } 466 467 // GetFilterLogs returns the logs for the filter with the given id. 468 // If the filter could not be found an empty array of logs is returned. 469 func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 470 api.filtersMu.Lock() 471 f, found := api.filters[id] 472 api.filtersMu.Unlock() 473 474 if !found || f.typ != LogsSubscription { 475 return nil, errFilterNotFound 476 } 477 478 var filter *Filter 479 if f.crit.BlockHash != nil { 480 // Block filter requested, construct a single-shot filter 481 filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) 482 } else { 483 // Convert the RPC block numbers into internal representations 484 // Leave LatestBlockNumber in place here as the defaults 485 // Should be handled correctly as request for the last 486 // accepted block instead throughout all APIs. 487 begin := rpc.LatestBlockNumber.Int64() 488 if f.crit.FromBlock != nil { 489 begin = f.crit.FromBlock.Int64() 490 } 491 end := rpc.LatestBlockNumber.Int64() 492 if f.crit.ToBlock != nil { 493 end = f.crit.ToBlock.Int64() 494 } 495 // Construct the range filter 496 filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics) 497 } 498 // Run the filter and return all the logs 499 logs, err := filter.Logs(ctx) 500 if err != nil { 501 return nil, err 502 } 503 return returnLogs(logs), nil 504 } 505 506 // GetFilterChanges returns the logs for the filter with the given id since 507 // last time it was called. This can be used for polling. 508 // 509 // For pending transaction and block filters the result is []common.Hash. 510 // (pending)Log filters return []Log. 511 func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { 512 api.filtersMu.Lock() 513 defer api.filtersMu.Unlock() 514 515 chainConfig := api.sys.backend.ChainConfig() 516 latest := api.sys.backend.CurrentHeader() 517 518 var baseFee *big.Int 519 if latest != nil { 520 baseFee = latest.BaseFee 521 } 522 523 if f, found := api.filters[id]; found { 524 if !f.deadline.Stop() { 525 // timer expired but filter is not yet removed in timeout loop 526 // receive timer value and reset timer 527 <-f.deadline.C 528 } 529 f.deadline.Reset(api.timeout) 530 531 switch f.typ { 532 case BlocksSubscription, AcceptedBlocksSubscription: 533 hashes := f.hashes 534 f.hashes = nil 535 return returnHashes(hashes), nil 536 case PendingTransactionsSubscription, AcceptedTransactionsSubscription: 537 if f.fullTx { 538 txs := make([]*ethapi.RPCTransaction, 0, len(f.txs)) 539 for _, tx := range f.txs { 540 txs = append(txs, ethapi.NewRPCTransaction(tx, latest, baseFee, chainConfig)) 541 } 542 f.txs = nil 543 return txs, nil 544 } else { 545 hashes := make([]common.Hash, 0, len(f.txs)) 546 for _, tx := range f.txs { 547 hashes = append(hashes, tx.Hash()) 548 } 549 f.txs = nil 550 return hashes, nil 551 } 552 case LogsSubscription, AcceptedLogsSubscription, MinedAndPendingLogsSubscription: 553 logs := f.logs 554 f.logs = nil 555 return returnLogs(logs), nil 556 } 557 } 558 559 return []interface{}{}, errFilterNotFound 560 } 561 562 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 563 // otherwise the given hashes array is returned. 564 func returnHashes(hashes []common.Hash) []common.Hash { 565 if hashes == nil { 566 return []common.Hash{} 567 } 568 return hashes 569 } 570 571 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 572 // otherwise the given logs array is returned. 573 func returnLogs(logs []*types.Log) []*types.Log { 574 if logs == nil { 575 return []*types.Log{} 576 } 577 return logs 578 } 579 580 // UnmarshalJSON sets *args fields with given data. 581 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 582 type input struct { 583 BlockHash *common.Hash `json:"blockHash"` 584 FromBlock *rpc.BlockNumber `json:"fromBlock"` 585 ToBlock *rpc.BlockNumber `json:"toBlock"` 586 Addresses interface{} `json:"address"` 587 Topics []interface{} `json:"topics"` 588 } 589 590 var raw input 591 if err := json.Unmarshal(data, &raw); err != nil { 592 return err 593 } 594 595 if raw.BlockHash != nil { 596 if raw.FromBlock != nil || raw.ToBlock != nil { 597 // BlockHash is mutually exclusive with FromBlock/ToBlock criteria 598 return errors.New("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") 599 } 600 args.BlockHash = raw.BlockHash 601 } else { 602 if raw.FromBlock != nil { 603 args.FromBlock = big.NewInt(raw.FromBlock.Int64()) 604 } 605 606 if raw.ToBlock != nil { 607 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 608 } 609 } 610 611 args.Addresses = []common.Address{} 612 613 if raw.Addresses != nil { 614 // raw.Address can contain a single address or an array of addresses 615 switch rawAddr := raw.Addresses.(type) { 616 case []interface{}: 617 for i, addr := range rawAddr { 618 if strAddr, ok := addr.(string); ok { 619 addr, err := decodeAddress(strAddr) 620 if err != nil { 621 return fmt.Errorf("invalid address at index %d: %v", i, err) 622 } 623 args.Addresses = append(args.Addresses, addr) 624 } else { 625 return fmt.Errorf("non-string address at index %d", i) 626 } 627 } 628 case string: 629 addr, err := decodeAddress(rawAddr) 630 if err != nil { 631 return fmt.Errorf("invalid address: %v", err) 632 } 633 args.Addresses = []common.Address{addr} 634 default: 635 return errors.New("invalid addresses in query") 636 } 637 } 638 639 // topics is an array consisting of strings and/or arrays of strings. 640 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 641 if len(raw.Topics) > 0 { 642 args.Topics = make([][]common.Hash, len(raw.Topics)) 643 for i, t := range raw.Topics { 644 switch topic := t.(type) { 645 case nil: 646 // ignore topic when matching logs 647 648 case string: 649 // match specific topic 650 top, err := decodeTopic(topic) 651 if err != nil { 652 return err 653 } 654 args.Topics[i] = []common.Hash{top} 655 656 case []interface{}: 657 // or case e.g. [null, "topic0", "topic1"] 658 for _, rawTopic := range topic { 659 if rawTopic == nil { 660 // null component, match all 661 args.Topics[i] = nil 662 break 663 } 664 if topic, ok := rawTopic.(string); ok { 665 parsed, err := decodeTopic(topic) 666 if err != nil { 667 return err 668 } 669 args.Topics[i] = append(args.Topics[i], parsed) 670 } else { 671 return errInvalidTopic 672 } 673 } 674 default: 675 return errInvalidTopic 676 } 677 } 678 } 679 680 return nil 681 } 682 683 func decodeAddress(s string) (common.Address, error) { 684 b, err := hexutil.Decode(s) 685 if err == nil && len(b) != common.AddressLength { 686 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) 687 } 688 return common.BytesToAddress(b), err 689 } 690 691 func decodeTopic(s string) (common.Hash, error) { 692 b, err := hexutil.Decode(s) 693 if err == nil && len(b) != common.HashLength { 694 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) 695 } 696 return common.BytesToHash(b), err 697 }