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