github.com/klaytn/klaytn@v1.12.1/node/cn/filters/api.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2015 The go-ethereum Authors 3 // This file is part of go-ethereum. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from eth/filters/api.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package filters 22 23 import ( 24 "context" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "math/big" 29 "sync" 30 "time" 31 32 "github.com/klaytn/klaytn/params" 33 34 "github.com/klaytn/klaytn" 35 "github.com/klaytn/klaytn/blockchain/types" 36 "github.com/klaytn/klaytn/common" 37 "github.com/klaytn/klaytn/common/hexutil" 38 "github.com/klaytn/klaytn/event" 39 "github.com/klaytn/klaytn/networks/rpc" 40 "github.com/klaytn/klaytn/storage/database" 41 ) 42 43 var ( 44 defaultFilterDeadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline 45 46 getLogsCxtKeyMaxItems = "maxItems" // the value of the context key should have the type of GetLogsMaxItems 47 GetLogsDeadline = 10 * time.Second // execution deadlines for getLogs and getFilterLogs APIs 48 GetLogsMaxItems = int(10000) // maximum allowed number of return items for getLogs and getFilterLogs APIs 49 ) 50 51 // filter is a helper struct that holds meta information over the filter type 52 // and associated subscription in the event system. 53 type filter struct { 54 typ Type 55 deadline *time.Timer // filter is inactiv when deadline triggers 56 hashes []common.Hash 57 crit FilterCriteria 58 logs []*types.Log 59 s *Subscription // associated subscription in event system 60 } 61 62 // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various 63 // information related to the Klaytn protocol such als blocks, transactions and logs. 64 type PublicFilterAPI struct { 65 backend Backend 66 mux *event.TypeMux 67 quit chan struct{} 68 chainDB database.DBManager 69 events *EventSystem 70 filtersMu sync.Mutex 71 filters map[rpc.ID]*filter 72 73 // this field is for test. it makes the filter timeout more flexible when testing 74 timeout time.Duration 75 } 76 77 // NewPublicFilterAPI returns a new PublicFilterAPI instance. 78 func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI { 79 api := &PublicFilterAPI{ 80 backend: backend, 81 mux: backend.EventMux(), 82 chainDB: backend.ChainDB(), 83 events: NewEventSystem(backend.EventMux(), backend, lightMode), 84 filters: make(map[rpc.ID]*filter), 85 timeout: defaultFilterDeadline, 86 } 87 go api.timeoutLoop() 88 89 return api 90 } 91 92 // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. 93 // Tt is started when the api is created. 94 func (api *PublicFilterAPI) timeoutLoop() { 95 var toUninstall []*Subscription 96 ticker := time.NewTicker(api.timeout) 97 defer ticker.Stop() 98 for { 99 <-ticker.C 100 api.filtersMu.Lock() 101 for id, f := range api.filters { 102 select { 103 case <-f.deadline.C: 104 toUninstall = append(toUninstall, f.s) 105 delete(api.filters, id) 106 default: 107 continue 108 } 109 } 110 api.filtersMu.Unlock() 111 112 // Unsubscribes are processed outside the lock to avoid the following scenario: 113 // event loop attempts broadcasting events to still active filters while 114 // Unsubscribe is waiting for it to process the uninstall request. 115 for _, s := range toUninstall { 116 s.Unsubscribe() 117 } 118 toUninstall = nil 119 } 120 } 121 122 // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes 123 // as transactions enter the pending state. 124 // 125 // It is part of the filter package because this filter can be used through the 126 // `klay_getFilterChanges` polling method that is also used for log filters. 127 func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { 128 var ( 129 pendingTxs = make(chan []common.Hash) 130 pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) 131 ) 132 133 api.filtersMu.Lock() 134 api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub} 135 api.filtersMu.Unlock() 136 137 go func() { 138 for { 139 select { 140 case ph := <-pendingTxs: 141 api.filtersMu.Lock() 142 if f, found := api.filters[pendingTxSub.ID]; found { 143 f.hashes = append(f.hashes, ph...) 144 } 145 api.filtersMu.Unlock() 146 case <-pendingTxSub.Err(): 147 api.filtersMu.Lock() 148 delete(api.filters, pendingTxSub.ID) 149 api.filtersMu.Unlock() 150 return 151 } 152 } 153 }() 154 155 return pendingTxSub.ID 156 } 157 158 // NewPendingTransactions creates a subscription that is triggered each time a transaction 159 // enters the transaction pool and was signed from one of the transactions this nodes manages. 160 func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { 161 notifier, supported := rpc.NotifierFromContext(ctx) 162 if !supported { 163 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 164 } 165 166 rpcSub := notifier.CreateSubscription() 167 168 go func() { 169 txHashes := make(chan []common.Hash, 128) 170 pendingTxSub := api.events.SubscribePendingTxs(txHashes) 171 172 for { 173 select { 174 case hashes := <-txHashes: 175 // To keep the original behaviour, send a single tx hash in one notification. 176 // TODO(rjl493456442) Send a batch of tx hashes in one notification 177 for _, h := range hashes { 178 notifier.Notify(rpcSub.ID, h) 179 } 180 case <-rpcSub.Err(): 181 pendingTxSub.Unsubscribe() 182 return 183 case <-notifier.Closed(): 184 pendingTxSub.Unsubscribe() 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 *PublicFilterAPI) 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 // RPCMarshalHeader converts the given header to the RPC output that includes Klaytn-specific fields. 227 // For klay_getHeaderByNumber and klay_getHeaderByHash APIs. 228 func RPCMarshalHeader(head *types.Header, rules params.Rules) map[string]interface{} { 229 result := map[string]interface{}{ 230 "parentHash": head.ParentHash, 231 "reward": head.Rewardbase, 232 "stateRoot": head.Root, 233 "transactionsRoot": head.TxHash, 234 "receiptsRoot": head.ReceiptHash, 235 "logsBloom": head.Bloom, 236 "blockScore": (*hexutil.Big)(head.BlockScore), 237 "number": (*hexutil.Big)(head.Number), 238 "gasUsed": hexutil.Uint64(head.GasUsed), 239 "timestamp": (*hexutil.Big)(head.Time), 240 "timestampFoS": hexutil.Uint(head.TimeFoS), 241 "extraData": hexutil.Bytes(head.Extra), 242 "governanceData": hexutil.Bytes(head.Governance), 243 "voteData": hexutil.Bytes(head.Vote), 244 "hash": head.Hash(), 245 } 246 247 if rules.IsEthTxType { 248 if head.BaseFee == nil { 249 result["baseFeePerGas"] = (*hexutil.Big)(new(big.Int).SetUint64(params.ZeroBaseFee)) 250 } else { 251 result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) 252 } 253 } 254 if rules.IsRandao { 255 result["randomReveal"] = hexutil.Bytes(head.RandomReveal) 256 result["mixhash"] = hexutil.Bytes(head.MixHash) 257 } 258 259 return result 260 } 261 262 // NewHeads send a notification each time a new (header) block is appended to the chain. 263 func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { 264 notifier, supported := rpc.NotifierFromContext(ctx) 265 if !supported { 266 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 267 } 268 269 rpcSub := notifier.CreateSubscription() 270 271 go func() { 272 headers := make(chan *types.Header) 273 headersSub := api.events.SubscribeNewHeads(headers) 274 275 for { 276 select { 277 case h := <-headers: 278 header := RPCMarshalHeader(h, api.backend.ChainConfig().Rules(h.Number)) 279 notifier.Notify(rpcSub.ID, header) 280 case <-rpcSub.Err(): 281 headersSub.Unsubscribe() 282 return 283 case <-notifier.Closed(): 284 headersSub.Unsubscribe() 285 return 286 } 287 } 288 }() 289 290 return rpcSub, nil 291 } 292 293 // Logs creates a subscription that fires for all new log that match the given filter criteria. 294 func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { 295 notifier, supported := rpc.NotifierFromContext(ctx) 296 if !supported { 297 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 298 } 299 300 var ( 301 rpcSub = notifier.CreateSubscription() 302 matchedLogs = make(chan []*types.Log) 303 ) 304 305 logsSub, err := api.events.SubscribeLogs(klaytn.FilterQuery(crit), matchedLogs) 306 if err != nil { 307 return nil, err 308 } 309 310 go func() { 311 for { 312 select { 313 case logs := <-matchedLogs: 314 for _, log := range logs { 315 notifier.Notify(rpcSub.ID, &log) 316 } 317 case <-rpcSub.Err(): // client send an unsubscribe request 318 logsSub.Unsubscribe() 319 return 320 case <-notifier.Closed(): // connection dropped 321 logsSub.Unsubscribe() 322 return 323 } 324 } 325 }() 326 327 return rpcSub, nil 328 } 329 330 // FilterCriteria represents a request to create a new filter. 331 // Same as klaytn.FilterQuery but with UnmarshalJSON() method. 332 type FilterCriteria klaytn.FilterQuery 333 334 // NewFilter creates a new filter and returns the filter id. It can be 335 // used to retrieve logs when the state changes. This method cannot be 336 // used to fetch logs that are already stored in the state. 337 // 338 // Default criteria for the from and to block are "latest". 339 // Using "latest" as block number will return logs for mined blocks. 340 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 341 // In case logs are removed (chain reorg) previously returned logs are returned 342 // again but with the removed property set to true. 343 // 344 // In case "fromBlock" > "toBlock" an error is returned. 345 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 346 logs := make(chan []*types.Log) 347 logsSub, err := api.events.SubscribeLogs(klaytn.FilterQuery(crit), logs) 348 if err != nil { 349 return rpc.ID(""), err 350 } 351 352 api.filtersMu.Lock() 353 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub} 354 api.filtersMu.Unlock() 355 356 go func() { 357 for { 358 select { 359 case l := <-logs: 360 api.filtersMu.Lock() 361 if f, found := api.filters[logsSub.ID]; found { 362 f.logs = append(f.logs, l...) 363 } 364 api.filtersMu.Unlock() 365 case <-logsSub.Err(): 366 api.filtersMu.Lock() 367 delete(api.filters, logsSub.ID) 368 api.filtersMu.Unlock() 369 return 370 } 371 } 372 }() 373 374 return logsSub.ID, nil 375 } 376 377 // GetLogs returns logs matching the given argument that are stored within the state. 378 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 379 ctx = context.WithValue(ctx, getLogsCxtKeyMaxItems, GetLogsMaxItems) 380 ctx, cancelFnc := context.WithTimeout(ctx, GetLogsDeadline) 381 defer cancelFnc() 382 383 var filter *Filter 384 if crit.BlockHash != nil { 385 // Block filter requested, construct a single-shot filter 386 filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) 387 } else { 388 // Convert the RPC block numbers into internal representations 389 begin := rpc.LatestBlockNumber.Int64() 390 if crit.FromBlock != nil { 391 begin = crit.FromBlock.Int64() 392 } 393 end := rpc.LatestBlockNumber.Int64() 394 if crit.ToBlock != nil { 395 end = crit.ToBlock.Int64() 396 } 397 // Construct the range filter 398 filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) 399 } 400 401 // Run the filter and return all the logs 402 logs, err := filter.Logs(ctx) 403 if err != nil { 404 return nil, err 405 } 406 return returnLogs(logs), err 407 } 408 409 // UninstallFilter removes the filter with the given filter id. 410 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { 411 api.filtersMu.Lock() 412 f, found := api.filters[id] 413 if found { 414 delete(api.filters, id) 415 } 416 api.filtersMu.Unlock() 417 if found { 418 f.s.Unsubscribe() 419 } 420 421 return found 422 } 423 424 // GetFilterLogs returns the logs for the filter with the given id. 425 // If the filter could not be found an empty array of logs is returned. 426 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 427 ctx = context.WithValue(ctx, getLogsCxtKeyMaxItems, GetLogsMaxItems) 428 ctx, cancelFnc := context.WithTimeout(ctx, GetLogsDeadline) 429 defer cancelFnc() 430 431 api.filtersMu.Lock() 432 f, found := api.filters[id] 433 api.filtersMu.Unlock() 434 435 if !found || f.typ != LogsSubscription { 436 return nil, fmt.Errorf("filter not found") 437 } 438 439 begin := rpc.LatestBlockNumber.Int64() 440 if f.crit.FromBlock != nil { 441 begin = f.crit.FromBlock.Int64() 442 } 443 end := rpc.LatestBlockNumber.Int64() 444 if f.crit.ToBlock != nil { 445 end = f.crit.ToBlock.Int64() 446 } 447 // Create and run the filter to get all the logs 448 filter := NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) 449 450 logs, err := filter.Logs(ctx) 451 if err != nil { 452 return nil, err 453 } 454 return returnLogs(logs), nil 455 } 456 457 // GetFilterChanges returns the logs for the filter with the given id since 458 // last time it was called. This can be used for polling. 459 // 460 // For pending transaction and block filters the result is []common.Hash. 461 // (pending)Log filters return []Log. 462 func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { 463 api.filtersMu.Lock() 464 defer api.filtersMu.Unlock() 465 466 if f, found := api.filters[id]; found { 467 if !f.deadline.Stop() { 468 // timer expired but filter is not yet removed in timeout loop 469 // receive timer value and reset timer 470 <-f.deadline.C 471 } 472 f.deadline.Reset(api.timeout) 473 474 switch f.typ { 475 case PendingTransactionsSubscription, BlocksSubscription: 476 hashes := f.hashes 477 f.hashes = nil 478 return returnHashes(hashes), nil 479 case LogsSubscription: 480 logs := f.logs 481 f.logs = nil 482 return returnLogs(logs), nil 483 } 484 } 485 486 return []interface{}{}, fmt.Errorf("filter not found") 487 } 488 489 // Events return private field events of PublicFilterAPI. 490 func (api *PublicFilterAPI) Events() *EventSystem { 491 return api.events 492 } 493 494 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 495 // otherwise the given hashes array is returned. 496 func returnHashes(hashes []common.Hash) []common.Hash { 497 if hashes == nil { 498 return []common.Hash{} 499 } 500 return hashes 501 } 502 503 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 504 // otherwise the given logs array is returned. 505 func returnLogs(logs []*types.Log) []*types.Log { 506 if logs == nil { 507 return []*types.Log{} 508 } 509 return logs 510 } 511 512 // UnmarshalJSON sets *args fields with given data. 513 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 514 type input struct { 515 BlockHash *common.Hash `json:"blockHash"` 516 FromBlock *rpc.BlockNumber `json:"fromBlock"` 517 ToBlock *rpc.BlockNumber `json:"toBlock"` 518 Addresses interface{} `json:"address"` 519 Topics []interface{} `json:"topics"` 520 } 521 522 var raw input 523 if err := json.Unmarshal(data, &raw); err != nil { 524 return err 525 } 526 527 if raw.BlockHash != nil { 528 if raw.FromBlock != nil || raw.ToBlock != nil { 529 // BlockHash is mutually exclusive with FromBlock/ToBlock criteria 530 return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") 531 } 532 args.BlockHash = raw.BlockHash 533 } else { 534 if raw.FromBlock != nil { 535 args.FromBlock = big.NewInt(raw.FromBlock.Int64()) 536 } 537 if raw.ToBlock != nil { 538 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 539 } 540 } 541 542 args.Addresses = []common.Address{} 543 544 if raw.Addresses != nil { 545 // raw.Address can contain a single address or an array of addresses 546 switch rawAddr := raw.Addresses.(type) { 547 case []interface{}: 548 for i, addr := range rawAddr { 549 if strAddr, ok := addr.(string); ok { 550 addr, err := decodeAddress(strAddr) 551 if err != nil { 552 return fmt.Errorf("invalid address at index %d: %v", i, err) 553 } 554 args.Addresses = append(args.Addresses, addr) 555 } else { 556 return fmt.Errorf("non-string address at index %d", i) 557 } 558 } 559 case string: 560 addr, err := decodeAddress(rawAddr) 561 if err != nil { 562 return fmt.Errorf("invalid address: %v", err) 563 } 564 args.Addresses = []common.Address{addr} 565 default: 566 return errors.New("invalid addresses in query") 567 } 568 } 569 570 // topics is an array consisting of strings and/or arrays of strings. 571 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 572 if len(raw.Topics) > 0 { 573 args.Topics = make([][]common.Hash, len(raw.Topics)) 574 for i, t := range raw.Topics { 575 switch topic := t.(type) { 576 case nil: 577 // ignore topic when matching logs 578 579 case string: 580 // match specific topic 581 top, err := decodeTopic(topic) 582 if err != nil { 583 return err 584 } 585 args.Topics[i] = []common.Hash{top} 586 587 case []interface{}: 588 // or case e.g. [null, "topic0", "topic1"] 589 for _, rawTopic := range topic { 590 if rawTopic == nil { 591 // null component, match all 592 args.Topics[i] = nil 593 break 594 } 595 if topic, ok := rawTopic.(string); ok { 596 parsed, err := decodeTopic(topic) 597 if err != nil { 598 return err 599 } 600 args.Topics[i] = append(args.Topics[i], parsed) 601 } else { 602 return fmt.Errorf("invalid topic(s)") 603 } 604 } 605 default: 606 return fmt.Errorf("invalid topic(s)") 607 } 608 } 609 } 610 611 return nil 612 } 613 614 func decodeAddress(s string) (common.Address, error) { 615 b, err := hexutil.Decode(s) 616 if err == nil && len(b) != common.AddressLength { 617 err = fmt.Errorf("hex has invalid length %d after decoding", len(b)) 618 } 619 return common.BytesToAddress(b), err 620 } 621 622 func decodeTopic(s string) (common.Hash, error) { 623 b, err := hexutil.Decode(s) 624 if err == nil && len(b) != common.HashLength { 625 err = fmt.Errorf("hex has invalid length %d after decoding", len(b)) 626 } 627 return common.BytesToHash(b), err 628 }