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