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