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