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