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