github.com/luckypickle/go-ethereum-vet@v1.14.2/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 ethereum "github.com/luckypickle/go-ethereum-vet" 29 "github.com/luckypickle/go-ethereum-vet/common" 30 "github.com/luckypickle/go-ethereum-vet/common/hexutil" 31 "github.com/luckypickle/go-ethereum-vet/core/types" 32 "github.com/luckypickle/go-ethereum-vet/ethdb" 33 "github.com/luckypickle/go-ethereum-vet/event" 34 "github.com/luckypickle/go-ethereum-vet/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 mux: backend.EventMux(), 69 chainDb: backend.ChainDb(), 70 events: NewEventSystem(backend.EventMux(), backend, lightMode), 71 filters: make(map[rpc.ID]*filter), 72 } 73 go api.timeoutLoop() 74 75 return api 76 } 77 78 // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. 79 // Tt is started when the api is created. 80 func (api *PublicFilterAPI) timeoutLoop() { 81 ticker := time.NewTicker(5 * time.Minute) 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 248 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) 249 if err != nil { 250 return nil, err 251 } 252 253 go func() { 254 255 for { 256 select { 257 case logs := <-matchedLogs: 258 for _, log := range logs { 259 notifier.Notify(rpcSub.ID, &log) 260 } 261 case <-rpcSub.Err(): // client send an unsubscribe request 262 logsSub.Unsubscribe() 263 return 264 case <-notifier.Closed(): // connection dropped 265 logsSub.Unsubscribe() 266 return 267 } 268 } 269 }() 270 271 return rpcSub, nil 272 } 273 274 // FilterCriteria represents a request to create a new filter. 275 // Same as ethereum.FilterQuery but with UnmarshalJSON() method. 276 type FilterCriteria ethereum.FilterQuery 277 278 // NewFilter creates a new filter and returns the filter id. It can be 279 // used to retrieve logs when the state changes. This method cannot be 280 // used to fetch logs that are already stored in the state. 281 // 282 // Default criteria for the from and to block are "latest". 283 // Using "latest" as block number will return logs for mined blocks. 284 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 285 // In case logs are removed (chain reorg) previously returned logs are returned 286 // again but with the removed property set to true. 287 // 288 // In case "fromBlock" > "toBlock" an error is returned. 289 // 290 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter 291 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 292 logs := make(chan []*types.Log) 293 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) 294 if err != nil { 295 return rpc.ID(""), err 296 } 297 298 api.filtersMu.Lock() 299 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} 300 api.filtersMu.Unlock() 301 302 go func() { 303 for { 304 select { 305 case l := <-logs: 306 api.filtersMu.Lock() 307 if f, found := api.filters[logsSub.ID]; found { 308 f.logs = append(f.logs, l...) 309 } 310 api.filtersMu.Unlock() 311 case <-logsSub.Err(): 312 api.filtersMu.Lock() 313 delete(api.filters, logsSub.ID) 314 api.filtersMu.Unlock() 315 return 316 } 317 } 318 }() 319 320 return logsSub.ID, nil 321 } 322 323 // GetLogs returns logs matching the given argument that are stored within the state. 324 // 325 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs 326 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 327 var filter *Filter 328 if crit.BlockHash != nil { 329 // Block filter requested, construct a single-shot filter 330 filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) 331 } else { 332 // Convert the RPC block numbers into internal representations 333 begin := rpc.LatestBlockNumber.Int64() 334 if crit.FromBlock != nil { 335 begin = crit.FromBlock.Int64() 336 } 337 end := rpc.LatestBlockNumber.Int64() 338 if crit.ToBlock != nil { 339 end = crit.ToBlock.Int64() 340 } 341 // Construct the range filter 342 filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) 343 } 344 // Run the filter and return all the logs 345 logs, err := filter.Logs(ctx) 346 if err != nil { 347 return nil, err 348 } 349 return returnLogs(logs), err 350 } 351 352 // UninstallFilter removes the filter with the given filter id. 353 // 354 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter 355 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { 356 api.filtersMu.Lock() 357 f, found := api.filters[id] 358 if found { 359 delete(api.filters, id) 360 } 361 api.filtersMu.Unlock() 362 if found { 363 f.s.Unsubscribe() 364 } 365 366 return found 367 } 368 369 // GetFilterLogs returns the logs for the filter with the given id. 370 // If the filter could not be found an empty array of logs is returned. 371 // 372 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs 373 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 374 api.filtersMu.Lock() 375 f, found := api.filters[id] 376 api.filtersMu.Unlock() 377 378 if !found || f.typ != LogsSubscription { 379 return nil, fmt.Errorf("filter not found") 380 } 381 382 var filter *Filter 383 if f.crit.BlockHash != nil { 384 // Block filter requested, construct a single-shot filter 385 filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) 386 } else { 387 // Convert the RPC block numbers into internal representations 388 begin := rpc.LatestBlockNumber.Int64() 389 if f.crit.FromBlock != nil { 390 begin = f.crit.FromBlock.Int64() 391 } 392 end := rpc.LatestBlockNumber.Int64() 393 if f.crit.ToBlock != nil { 394 end = f.crit.ToBlock.Int64() 395 } 396 // Construct the range filter 397 filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) 398 } 399 // Run the filter and return all the logs 400 logs, err := filter.Logs(ctx) 401 if err != nil { 402 return nil, err 403 } 404 return returnLogs(logs), nil 405 } 406 407 // GetFilterChanges returns the logs for the filter with the given id since 408 // last time it was called. This can be used for polling. 409 // 410 // For pending transaction and block filters the result is []common.Hash. 411 // (pending)Log filters return []Log. 412 // 413 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges 414 func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { 415 api.filtersMu.Lock() 416 defer api.filtersMu.Unlock() 417 418 if f, found := api.filters[id]; found { 419 if !f.deadline.Stop() { 420 // timer expired but filter is not yet removed in timeout loop 421 // receive timer value and reset timer 422 <-f.deadline.C 423 } 424 f.deadline.Reset(deadline) 425 426 switch f.typ { 427 case PendingTransactionsSubscription, BlocksSubscription: 428 hashes := f.hashes 429 f.hashes = nil 430 return returnHashes(hashes), nil 431 case LogsSubscription: 432 logs := f.logs 433 f.logs = nil 434 return returnLogs(logs), nil 435 } 436 } 437 438 return []interface{}{}, fmt.Errorf("filter not found") 439 } 440 441 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 442 // otherwise the given hashes array is returned. 443 func returnHashes(hashes []common.Hash) []common.Hash { 444 if hashes == nil { 445 return []common.Hash{} 446 } 447 return hashes 448 } 449 450 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 451 // otherwise the given logs array is returned. 452 func returnLogs(logs []*types.Log) []*types.Log { 453 if logs == nil { 454 return []*types.Log{} 455 } 456 return logs 457 } 458 459 // UnmarshalJSON sets *args fields with given data. 460 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 461 type input struct { 462 BlockHash *common.Hash `json:"blockHash"` 463 FromBlock *rpc.BlockNumber `json:"fromBlock"` 464 ToBlock *rpc.BlockNumber `json:"toBlock"` 465 Addresses interface{} `json:"address"` 466 Topics []interface{} `json:"topics"` 467 } 468 469 var raw input 470 if err := json.Unmarshal(data, &raw); err != nil { 471 return err 472 } 473 474 if raw.BlockHash != nil { 475 if raw.FromBlock != nil || raw.ToBlock != nil { 476 // BlockHash is mutually exclusive with FromBlock/ToBlock criteria 477 return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") 478 } 479 args.BlockHash = raw.BlockHash 480 } else { 481 if raw.FromBlock != nil { 482 args.FromBlock = big.NewInt(raw.FromBlock.Int64()) 483 } 484 485 if raw.ToBlock != nil { 486 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 487 } 488 } 489 490 args.Addresses = []common.Address{} 491 492 if raw.Addresses != nil { 493 // raw.Address can contain a single address or an array of addresses 494 switch rawAddr := raw.Addresses.(type) { 495 case []interface{}: 496 for i, addr := range rawAddr { 497 if strAddr, ok := addr.(string); ok { 498 addr, err := decodeAddress(strAddr) 499 if err != nil { 500 return fmt.Errorf("invalid address at index %d: %v", i, err) 501 } 502 args.Addresses = append(args.Addresses, addr) 503 } else { 504 return fmt.Errorf("non-string address at index %d", i) 505 } 506 } 507 case string: 508 addr, err := decodeAddress(rawAddr) 509 if err != nil { 510 return fmt.Errorf("invalid address: %v", err) 511 } 512 args.Addresses = []common.Address{addr} 513 default: 514 return errors.New("invalid addresses in query") 515 } 516 } 517 518 // topics is an array consisting of strings and/or arrays of strings. 519 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 520 if len(raw.Topics) > 0 { 521 args.Topics = make([][]common.Hash, len(raw.Topics)) 522 for i, t := range raw.Topics { 523 switch topic := t.(type) { 524 case nil: 525 // ignore topic when matching logs 526 527 case string: 528 // match specific topic 529 top, err := decodeTopic(topic) 530 if err != nil { 531 return err 532 } 533 args.Topics[i] = []common.Hash{top} 534 535 case []interface{}: 536 // or case e.g. [null, "topic0", "topic1"] 537 for _, rawTopic := range topic { 538 if rawTopic == nil { 539 // null component, match all 540 args.Topics[i] = nil 541 break 542 } 543 if topic, ok := rawTopic.(string); ok { 544 parsed, err := decodeTopic(topic) 545 if err != nil { 546 return err 547 } 548 args.Topics[i] = append(args.Topics[i], parsed) 549 } else { 550 return fmt.Errorf("invalid topic(s)") 551 } 552 } 553 default: 554 return fmt.Errorf("invalid topic(s)") 555 } 556 } 557 } 558 559 return nil 560 } 561 562 func decodeAddress(s string) (common.Address, error) { 563 b, err := hexutil.Decode(s) 564 if err == nil && len(b) != common.AddressLength { 565 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) 566 } 567 return common.BytesToAddress(b), err 568 } 569 570 func decodeTopic(s string) (common.Hash, error) { 571 b, err := hexutil.Decode(s) 572 if err == nil && len(b) != common.HashLength { 573 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) 574 } 575 return common.BytesToHash(b), err 576 }