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