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