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