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