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