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