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