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