github.com/beyonderyue/gochain@v2.2.26+incompatible/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/gochain-io/gochain" 29 "github.com/gochain-io/gochain/common" 30 "github.com/gochain-io/gochain/common/hexutil" 31 "github.com/gochain-io/gochain/core/types" 32 "github.com/gochain-io/gochain/event" 33 "github.com/gochain-io/gochain/rpc" 34 ) 35 36 var ( 37 deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline 38 ) 39 40 // filter is a helper struct that holds meta information over the filter type 41 // and associated subscription in the event system. 42 type filter struct { 43 typ Type 44 deadline *time.Timer // filter is inactiv when deadline triggers 45 hashes []common.Hash 46 crit FilterCriteria 47 logs []*types.Log 48 s *Subscription // associated subscription in event system 49 } 50 51 // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various 52 // information related to the Ethereum protocol such als blocks, transactions and logs. 53 type PublicFilterAPI struct { 54 backend Backend 55 mux *event.TypeMux 56 quit chan struct{} 57 chainDb common.Database 58 events *EventSystem 59 filtersMu sync.Mutex 60 filters map[rpc.ID]*filter 61 } 62 63 // NewPublicFilterAPI returns a new PublicFilterAPI instance. 64 func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI { 65 api := &PublicFilterAPI{ 66 backend: backend, 67 mux: backend.EventMux(), 68 chainDb: backend.ChainDb(), 69 events: NewEventSystem(backend.EventMux(), 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 ticker := time.NewTicker(5 * time.Minute) 81 for { 82 <-ticker.C 83 api.filtersMu.Lock() 84 for id, f := range api.filters { 85 select { 86 case <-f.deadline.C: 87 f.s.Unsubscribe() 88 delete(api.filters, id) 89 default: 90 continue 91 } 92 } 93 api.filtersMu.Unlock() 94 } 95 } 96 97 // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes 98 // as transactions enter the pending state. 99 // 100 // It is part of the filter package because this filter can be used through the 101 // `eth_getFilterChanges` polling method that is also used for log filters. 102 // 103 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter 104 func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { 105 var ( 106 pendingTxs = make(chan []common.Hash) 107 pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) 108 ) 109 110 api.filtersMu.Lock() 111 api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} 112 api.filtersMu.Unlock() 113 114 go func() { 115 for { 116 select { 117 case ph := <-pendingTxs: 118 api.filtersMu.Lock() 119 if f, found := api.filters[pendingTxSub.ID]; found { 120 f.hashes = append(f.hashes, ph...) 121 } 122 api.filtersMu.Unlock() 123 case <-pendingTxSub.Err(): 124 api.filtersMu.Lock() 125 delete(api.filters, pendingTxSub.ID) 126 api.filtersMu.Unlock() 127 return 128 } 129 } 130 }() 131 132 return pendingTxSub.ID 133 } 134 135 // NewPendingTransactions creates a subscription that is triggered each time a transaction 136 // enters the transaction pool and was signed from one of the transactions this nodes manages. 137 func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { 138 notifier, supported := rpc.NotifierFromContext(ctx) 139 if !supported { 140 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 141 } 142 143 rpcSub := notifier.CreateSubscription() 144 145 go func() { 146 txHashes := make(chan []common.Hash, 128) 147 pendingTxSub := api.events.SubscribePendingTxs(txHashes) 148 149 for { 150 select { 151 case hashes := <-txHashes: 152 // To keep the original behaviour, send a single tx hash in one notification. 153 // TODO(rjl493456442) Send a batch of tx hashes in one notification 154 for _, h := range hashes { 155 notifier.Notify(rpcSub.ID, h) 156 } 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(gochain.FilterQuery(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 // Same as ethereum.FilterQuery but with UnmarshalJSON() method. 275 type FilterCriteria gochain.FilterQuery 276 277 // NewFilter creates a new filter and returns the filter id. It can be 278 // used to retrieve logs when the state changes. This method cannot be 279 // used to fetch logs that are already stored in the state. 280 // 281 // Default criteria for the from and to block are "latest". 282 // Using "latest" as block number will return logs for mined blocks. 283 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 284 // In case logs are removed (chain reorg) previously returned logs are returned 285 // again but with the removed property set to true. 286 // 287 // In case "fromBlock" > "toBlock" an error is returned. 288 // 289 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter 290 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 291 logs := make(chan []*types.Log) 292 logsSub, err := api.events.SubscribeLogs(gochain.FilterQuery(crit), logs) 293 if err != nil { 294 return rpc.ID(""), err 295 } 296 297 api.filtersMu.Lock() 298 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} 299 api.filtersMu.Unlock() 300 301 go func() { 302 for { 303 select { 304 case l := <-logs: 305 api.filtersMu.Lock() 306 if f, found := api.filters[logsSub.ID]; found { 307 f.logs = append(f.logs, l...) 308 } 309 api.filtersMu.Unlock() 310 case <-logsSub.Err(): 311 api.filtersMu.Lock() 312 delete(api.filters, logsSub.ID) 313 api.filtersMu.Unlock() 314 return 315 } 316 } 317 }() 318 319 return logsSub.ID, nil 320 } 321 322 // GetLogs returns logs matching the given argument that are stored within the state. 323 // 324 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs 325 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 326 var filter *Filter 327 if crit.BlockHash != nil { 328 // Block filter requested, construct a single-shot filter 329 filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) 330 } else { 331 // Convert the RPC block numbers into internal representations 332 begin := rpc.LatestBlockNumber.Int64() 333 if crit.FromBlock != nil { 334 begin = crit.FromBlock.Int64() 335 } 336 end := rpc.LatestBlockNumber.Int64() 337 if crit.ToBlock != nil { 338 end = crit.ToBlock.Int64() 339 } 340 // Construct the range filter 341 filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) 342 } 343 // Run the filter and return all the logs 344 logs, err := filter.Logs(ctx) 345 if err != nil { 346 return nil, err 347 } 348 return returnLogs(logs), err 349 } 350 351 // UninstallFilter removes the filter with the given filter id. 352 // 353 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter 354 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { 355 api.filtersMu.Lock() 356 f, found := api.filters[id] 357 if found { 358 delete(api.filters, id) 359 } 360 api.filtersMu.Unlock() 361 if found { 362 f.s.Unsubscribe() 363 } 364 365 return found 366 } 367 368 // GetFilterLogs returns the logs for the filter with the given id. 369 // If the filter could not be found an empty array of logs is returned. 370 // 371 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs 372 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 373 api.filtersMu.Lock() 374 f, found := api.filters[id] 375 api.filtersMu.Unlock() 376 377 if !found || f.typ != LogsSubscription { 378 return nil, fmt.Errorf("filter not found") 379 } 380 381 var filter *Filter 382 if f.crit.BlockHash != nil { 383 // Block filter requested, construct a single-shot filter 384 filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) 385 } else { 386 // Convert the RPC block numbers into internal representations 387 begin := rpc.LatestBlockNumber.Int64() 388 if f.crit.FromBlock != nil { 389 begin = f.crit.FromBlock.Int64() 390 } 391 end := rpc.LatestBlockNumber.Int64() 392 if f.crit.ToBlock != nil { 393 end = f.crit.ToBlock.Int64() 394 } 395 // Construct the range filter 396 filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) 397 } 398 // Run the filter and return all the logs 399 logs, err := filter.Logs(ctx) 400 if err != nil { 401 return nil, err 402 } 403 return returnLogs(logs), nil 404 } 405 406 // GetFilterChanges returns the logs for the filter with the given id since 407 // last time it was called. This can be used for polling. 408 // 409 // For pending transaction and block filters the result is []common.Hash. 410 // (pending)Log filters return []Log. 411 // 412 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges 413 func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { 414 api.filtersMu.Lock() 415 defer api.filtersMu.Unlock() 416 417 if f, found := api.filters[id]; found { 418 if !f.deadline.Stop() { 419 // timer expired but filter is not yet removed in timeout loop 420 // receive timer value and reset timer 421 <-f.deadline.C 422 } 423 f.deadline.Reset(deadline) 424 425 switch f.typ { 426 case PendingTransactionsSubscription, BlocksSubscription: 427 hashes := f.hashes 428 f.hashes = nil 429 return returnHashes(hashes), nil 430 case LogsSubscription: 431 logs := f.logs 432 f.logs = nil 433 return returnLogs(logs), nil 434 } 435 } 436 437 return []interface{}{}, fmt.Errorf("filter not found") 438 } 439 440 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 441 // otherwise the given hashes array is returned. 442 func returnHashes(hashes []common.Hash) []common.Hash { 443 if hashes == nil { 444 return []common.Hash{} 445 } 446 return hashes 447 } 448 449 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 450 // otherwise the given logs array is returned. 451 func returnLogs(logs []*types.Log) []*types.Log { 452 if logs == nil { 453 return []*types.Log{} 454 } 455 return logs 456 } 457 458 // UnmarshalJSON sets *args fields with given data. 459 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 460 type input struct { 461 BlockHash *common.Hash `json:"blockHash"` 462 FromBlock *rpc.BlockNumber `json:"fromBlock"` 463 ToBlock *rpc.BlockNumber `json:"toBlock"` 464 Addresses interface{} `json:"address"` 465 Topics []interface{} `json:"topics"` 466 } 467 468 var raw input 469 if err := json.Unmarshal(data, &raw); err != nil { 470 return err 471 } 472 473 if raw.BlockHash != nil { 474 if raw.FromBlock != nil || raw.ToBlock != nil { 475 // BlockHash is mutually exclusive with FromBlock/ToBlock criteria 476 return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") 477 } 478 args.BlockHash = raw.BlockHash 479 } else { 480 if raw.FromBlock != nil { 481 args.FromBlock = big.NewInt(raw.FromBlock.Int64()) 482 } 483 484 if raw.ToBlock != nil { 485 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 486 } 487 } 488 489 args.Addresses = []common.Address{} 490 491 if raw.Addresses != nil { 492 // raw.Address can contain a single address or an array of addresses 493 switch rawAddr := raw.Addresses.(type) { 494 case []interface{}: 495 for i, addr := range rawAddr { 496 if strAddr, ok := addr.(string); ok { 497 addr, err := decodeAddress(strAddr) 498 if err != nil { 499 return fmt.Errorf("invalid address at index %d: %v", i, err) 500 } 501 args.Addresses = append(args.Addresses, addr) 502 } else { 503 return fmt.Errorf("non-string address at index %d", i) 504 } 505 } 506 case string: 507 addr, err := decodeAddress(rawAddr) 508 if err != nil { 509 return fmt.Errorf("invalid address: %v", err) 510 } 511 args.Addresses = []common.Address{addr} 512 default: 513 return errors.New("invalid addresses in query") 514 } 515 } 516 517 // topics is an array consisting of strings and/or arrays of strings. 518 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 519 if len(raw.Topics) > 0 { 520 args.Topics = make([][]common.Hash, len(raw.Topics)) 521 for i, t := range raw.Topics { 522 switch topic := t.(type) { 523 case nil: 524 // ignore topic when matching logs 525 526 case string: 527 // match specific topic 528 top, err := decodeTopic(topic) 529 if err != nil { 530 return err 531 } 532 args.Topics[i] = []common.Hash{top} 533 534 case []interface{}: 535 // or case e.g. [null, "topic0", "topic1"] 536 for _, rawTopic := range topic { 537 if rawTopic == nil { 538 // null component, match all 539 args.Topics[i] = nil 540 break 541 } 542 if topic, ok := rawTopic.(string); ok { 543 parsed, err := decodeTopic(topic) 544 if err != nil { 545 return err 546 } 547 args.Topics[i] = append(args.Topics[i], parsed) 548 } else { 549 return fmt.Errorf("invalid topic(s)") 550 } 551 } 552 default: 553 return fmt.Errorf("invalid topic(s)") 554 } 555 } 556 } 557 558 return nil 559 } 560 561 func decodeAddress(s string) (common.Address, error) { 562 b, err := hexutil.Decode(s) 563 if err == nil && len(b) != common.AddressLength { 564 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) 565 } 566 return common.BytesToAddress(b), err 567 } 568 569 func decodeTopic(s string) (common.Hash, error) { 570 b, err := hexutil.Decode(s) 571 if err == nil && len(b) != common.HashLength { 572 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) 573 } 574 return common.BytesToHash(b), err 575 }