github.com/eduardonunesp/go-ethereum@v1.8.9-0.20180514135602-f6bc65fc6811/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/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/ethdb" 33 "github.com/ethereum/go-ethereum/event" 34 "github.com/ethereum/go-ethereum/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.SubscribePendingTxEvents(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) 148 pendingTxSub := api.events.SubscribePendingTxEvents(txHashes) 149 150 for { 151 select { 152 case h := <-txHashes: 153 notifier.Notify(rpcSub.ID, h) 154 case <-rpcSub.Err(): 155 pendingTxSub.Unsubscribe() 156 return 157 case <-notifier.Closed(): 158 pendingTxSub.Unsubscribe() 159 return 160 } 161 } 162 }() 163 164 return rpcSub, nil 165 } 166 167 // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. 168 // It is part of the filter package since polling goes with eth_getFilterChanges. 169 // 170 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter 171 func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { 172 var ( 173 headers = make(chan *types.Header) 174 headerSub = api.events.SubscribeNewHeads(headers) 175 ) 176 177 api.filtersMu.Lock() 178 api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} 179 api.filtersMu.Unlock() 180 181 go func() { 182 for { 183 select { 184 case h := <-headers: 185 api.filtersMu.Lock() 186 if f, found := api.filters[headerSub.ID]; found { 187 f.hashes = append(f.hashes, h.Hash()) 188 } 189 api.filtersMu.Unlock() 190 case <-headerSub.Err(): 191 api.filtersMu.Lock() 192 delete(api.filters, headerSub.ID) 193 api.filtersMu.Unlock() 194 return 195 } 196 } 197 }() 198 199 return headerSub.ID 200 } 201 202 // NewHeads send a notification each time a new (header) block is appended to the chain. 203 func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { 204 notifier, supported := rpc.NotifierFromContext(ctx) 205 if !supported { 206 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 207 } 208 209 rpcSub := notifier.CreateSubscription() 210 211 go func() { 212 headers := make(chan *types.Header) 213 headersSub := api.events.SubscribeNewHeads(headers) 214 215 for { 216 select { 217 case h := <-headers: 218 notifier.Notify(rpcSub.ID, h) 219 case <-rpcSub.Err(): 220 headersSub.Unsubscribe() 221 return 222 case <-notifier.Closed(): 223 headersSub.Unsubscribe() 224 return 225 } 226 } 227 }() 228 229 return rpcSub, nil 230 } 231 232 // Logs creates a subscription that fires for all new log that match the given filter criteria. 233 func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { 234 notifier, supported := rpc.NotifierFromContext(ctx) 235 if !supported { 236 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 237 } 238 239 var ( 240 rpcSub = notifier.CreateSubscription() 241 matchedLogs = make(chan []*types.Log) 242 ) 243 244 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) 245 if err != nil { 246 return nil, err 247 } 248 249 go func() { 250 251 for { 252 select { 253 case logs := <-matchedLogs: 254 for _, log := range logs { 255 notifier.Notify(rpcSub.ID, &log) 256 } 257 case <-rpcSub.Err(): // client send an unsubscribe request 258 logsSub.Unsubscribe() 259 return 260 case <-notifier.Closed(): // connection dropped 261 logsSub.Unsubscribe() 262 return 263 } 264 } 265 }() 266 267 return rpcSub, nil 268 } 269 270 // FilterCriteria represents a request to create a new filter. 271 // Same as ethereum.FilterQuery but with UnmarshalJSON() method. 272 type FilterCriteria ethereum.FilterQuery 273 274 // NewFilter creates a new filter and returns the filter id. It can be 275 // used to retrieve logs when the state changes. This method cannot be 276 // used to fetch logs that are already stored in the state. 277 // 278 // Default criteria for the from and to block are "latest". 279 // Using "latest" as block number will return logs for mined blocks. 280 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 281 // In case logs are removed (chain reorg) previously returned logs are returned 282 // again but with the removed property set to true. 283 // 284 // In case "fromBlock" > "toBlock" an error is returned. 285 // 286 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter 287 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 288 logs := make(chan []*types.Log) 289 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) 290 if err != nil { 291 return rpc.ID(""), err 292 } 293 294 api.filtersMu.Lock() 295 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} 296 api.filtersMu.Unlock() 297 298 go func() { 299 for { 300 select { 301 case l := <-logs: 302 api.filtersMu.Lock() 303 if f, found := api.filters[logsSub.ID]; found { 304 f.logs = append(f.logs, l...) 305 } 306 api.filtersMu.Unlock() 307 case <-logsSub.Err(): 308 api.filtersMu.Lock() 309 delete(api.filters, logsSub.ID) 310 api.filtersMu.Unlock() 311 return 312 } 313 } 314 }() 315 316 return logsSub.ID, nil 317 } 318 319 // GetLogs returns logs matching the given argument that are stored within the state. 320 // 321 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs 322 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 323 // Convert the RPC block numbers into internal representations 324 if crit.FromBlock == nil { 325 crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) 326 } 327 if crit.ToBlock == nil { 328 crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) 329 } 330 // Create and run the filter to get all the logs 331 filter := New(api.backend, crit.FromBlock.Int64(), crit.ToBlock.Int64(), crit.Addresses, crit.Topics) 332 333 logs, err := filter.Logs(ctx) 334 if err != nil { 335 return nil, err 336 } 337 return returnLogs(logs), err 338 } 339 340 // UninstallFilter removes the filter with the given filter id. 341 // 342 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter 343 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { 344 api.filtersMu.Lock() 345 f, found := api.filters[id] 346 if found { 347 delete(api.filters, id) 348 } 349 api.filtersMu.Unlock() 350 if found { 351 f.s.Unsubscribe() 352 } 353 354 return found 355 } 356 357 // GetFilterLogs returns the logs for the filter with the given id. 358 // If the filter could not be found an empty array of logs is returned. 359 // 360 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs 361 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 362 api.filtersMu.Lock() 363 f, found := api.filters[id] 364 api.filtersMu.Unlock() 365 366 if !found || f.typ != LogsSubscription { 367 return nil, fmt.Errorf("filter not found") 368 } 369 370 begin := rpc.LatestBlockNumber.Int64() 371 if f.crit.FromBlock != nil { 372 begin = f.crit.FromBlock.Int64() 373 } 374 end := rpc.LatestBlockNumber.Int64() 375 if f.crit.ToBlock != nil { 376 end = f.crit.ToBlock.Int64() 377 } 378 // Create and run the filter to get all the logs 379 filter := New(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) 380 381 logs, err := filter.Logs(ctx) 382 if err != nil { 383 return nil, err 384 } 385 return returnLogs(logs), nil 386 } 387 388 // GetFilterChanges returns the logs for the filter with the given id since 389 // last time it was called. This can be used for polling. 390 // 391 // For pending transaction and block filters the result is []common.Hash. 392 // (pending)Log filters return []Log. 393 // 394 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges 395 func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { 396 api.filtersMu.Lock() 397 defer api.filtersMu.Unlock() 398 399 if f, found := api.filters[id]; found { 400 if !f.deadline.Stop() { 401 // timer expired but filter is not yet removed in timeout loop 402 // receive timer value and reset timer 403 <-f.deadline.C 404 } 405 f.deadline.Reset(deadline) 406 407 switch f.typ { 408 case PendingTransactionsSubscription, BlocksSubscription: 409 hashes := f.hashes 410 f.hashes = nil 411 return returnHashes(hashes), nil 412 case LogsSubscription: 413 logs := f.logs 414 f.logs = nil 415 return returnLogs(logs), nil 416 } 417 } 418 419 return []interface{}{}, fmt.Errorf("filter not found") 420 } 421 422 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 423 // otherwise the given hashes array is returned. 424 func returnHashes(hashes []common.Hash) []common.Hash { 425 if hashes == nil { 426 return []common.Hash{} 427 } 428 return hashes 429 } 430 431 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 432 // otherwise the given logs array is returned. 433 func returnLogs(logs []*types.Log) []*types.Log { 434 if logs == nil { 435 return []*types.Log{} 436 } 437 return logs 438 } 439 440 // UnmarshalJSON sets *args fields with given data. 441 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 442 type input struct { 443 From *rpc.BlockNumber `json:"fromBlock"` 444 ToBlock *rpc.BlockNumber `json:"toBlock"` 445 Addresses interface{} `json:"address"` 446 Topics []interface{} `json:"topics"` 447 } 448 449 var raw input 450 if err := json.Unmarshal(data, &raw); err != nil { 451 return err 452 } 453 454 if raw.From != nil { 455 args.FromBlock = big.NewInt(raw.From.Int64()) 456 } 457 458 if raw.ToBlock != nil { 459 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 460 } 461 462 args.Addresses = []common.Address{} 463 464 if raw.Addresses != nil { 465 // raw.Address can contain a single address or an array of addresses 466 switch rawAddr := raw.Addresses.(type) { 467 case []interface{}: 468 for i, addr := range rawAddr { 469 if strAddr, ok := addr.(string); ok { 470 addr, err := decodeAddress(strAddr) 471 if err != nil { 472 return fmt.Errorf("invalid address at index %d: %v", i, err) 473 } 474 args.Addresses = append(args.Addresses, addr) 475 } else { 476 return fmt.Errorf("non-string address at index %d", i) 477 } 478 } 479 case string: 480 addr, err := decodeAddress(rawAddr) 481 if err != nil { 482 return fmt.Errorf("invalid address: %v", err) 483 } 484 args.Addresses = []common.Address{addr} 485 default: 486 return errors.New("invalid addresses in query") 487 } 488 } 489 490 // topics is an array consisting of strings and/or arrays of strings. 491 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 492 if len(raw.Topics) > 0 { 493 args.Topics = make([][]common.Hash, len(raw.Topics)) 494 for i, t := range raw.Topics { 495 switch topic := t.(type) { 496 case nil: 497 // ignore topic when matching logs 498 499 case string: 500 // match specific topic 501 top, err := decodeTopic(topic) 502 if err != nil { 503 return err 504 } 505 args.Topics[i] = []common.Hash{top} 506 507 case []interface{}: 508 // or case e.g. [null, "topic0", "topic1"] 509 for _, rawTopic := range topic { 510 if rawTopic == nil { 511 // null component, match all 512 args.Topics[i] = nil 513 break 514 } 515 if topic, ok := rawTopic.(string); ok { 516 parsed, err := decodeTopic(topic) 517 if err != nil { 518 return err 519 } 520 args.Topics[i] = append(args.Topics[i], parsed) 521 } else { 522 return fmt.Errorf("invalid topic(s)") 523 } 524 } 525 default: 526 return fmt.Errorf("invalid topic(s)") 527 } 528 } 529 } 530 531 return nil 532 } 533 534 func decodeAddress(s string) (common.Address, error) { 535 b, err := hexutil.Decode(s) 536 if err == nil && len(b) != common.AddressLength { 537 err = fmt.Errorf("hex has invalid length %d after decoding", len(b)) 538 } 539 return common.BytesToAddress(b), err 540 } 541 542 func decodeTopic(s string) (common.Hash, error) { 543 b, err := hexutil.Decode(s) 544 if err == nil && len(b) != common.HashLength { 545 err = fmt.Errorf("hex has invalid length %d after decoding", len(b)) 546 } 547 return common.BytesToHash(b), err 548 }