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