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