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