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