github.com/Night-mk/quorum@v21.1.0+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 ethereum "github.com/ethereum/go-ethereum" 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/multitenancy" 35 "github.com/ethereum/go-ethereum/rpc" 36 ) 37 38 var ( 39 deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline 40 ) 41 42 // filter is a helper struct that holds meta information over the filter type 43 // and associated subscription in the event system. 44 type filter struct { 45 typ Type 46 deadline *time.Timer // filter is inactiv when deadline triggers 47 hashes []common.Hash 48 crit FilterCriteria 49 logs []*types.Log 50 s *Subscription // associated subscription in event system 51 } 52 53 // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various 54 // information related to the Ethereum protocol such als blocks, transactions and logs. 55 type PublicFilterAPI struct { 56 backend Backend 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 mux: backend.EventMux(), 70 chainDb: backend.ChainDb(), 71 events: NewEventSystem(backend.EventMux(), backend, lightMode), 72 filters: make(map[rpc.ID]*filter), 73 } 74 go api.timeoutLoop() 75 76 return api 77 } 78 79 // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. 80 // Tt is started when the api is created. 81 func (api *PublicFilterAPI) timeoutLoop() { 82 ticker := time.NewTicker(5 * time.Minute) 83 for { 84 <-ticker.C 85 api.filtersMu.Lock() 86 for id, f := range api.filters { 87 select { 88 case <-f.deadline.C: 89 f.s.Unsubscribe() 90 delete(api.filters, id) 91 default: 92 continue 93 } 94 } 95 api.filtersMu.Unlock() 96 } 97 } 98 99 // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes 100 // as transactions enter the pending state. 101 // 102 // It is part of the filter package because this filter can be used through the 103 // `eth_getFilterChanges` polling method that is also used for log filters. 104 // 105 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter 106 func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { 107 var ( 108 pendingTxs = make(chan []common.Hash) 109 pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) 110 ) 111 112 api.filtersMu.Lock() 113 api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} 114 api.filtersMu.Unlock() 115 116 go func() { 117 for { 118 select { 119 case ph := <-pendingTxs: 120 api.filtersMu.Lock() 121 if f, found := api.filters[pendingTxSub.ID]; found { 122 f.hashes = append(f.hashes, ph...) 123 } 124 api.filtersMu.Unlock() 125 case <-pendingTxSub.Err(): 126 api.filtersMu.Lock() 127 delete(api.filters, pendingTxSub.ID) 128 api.filtersMu.Unlock() 129 return 130 } 131 } 132 }() 133 134 return pendingTxSub.ID 135 } 136 137 // NewPendingTransactions creates a subscription that is triggered each time a transaction 138 // enters the transaction pool and was signed from one of the transactions this nodes manages. 139 func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { 140 notifier, supported := rpc.NotifierFromContext(ctx) 141 if !supported { 142 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 143 } 144 145 rpcSub := notifier.CreateSubscription() 146 147 go func() { 148 txHashes := make(chan []common.Hash, 128) 149 pendingTxSub := api.events.SubscribePendingTxs(txHashes) 150 151 for { 152 select { 153 case hashes := <-txHashes: 154 // To keep the original behaviour, send a single tx hash in one notification. 155 // TODO(rjl493456442) Send a batch of tx hashes in one notification 156 for _, h := range hashes { 157 notifier.Notify(rpcSub.ID, h) 158 } 159 case <-rpcSub.Err(): 160 pendingTxSub.Unsubscribe() 161 return 162 case <-notifier.Closed(): 163 pendingTxSub.Unsubscribe() 164 return 165 } 166 } 167 }() 168 169 return rpcSub, nil 170 } 171 172 // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. 173 // It is part of the filter package since polling goes with eth_getFilterChanges. 174 // 175 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter 176 func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { 177 var ( 178 headers = make(chan *types.Header) 179 headerSub = api.events.SubscribeNewHeads(headers) 180 ) 181 182 api.filtersMu.Lock() 183 api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} 184 api.filtersMu.Unlock() 185 186 go func() { 187 for { 188 select { 189 case h := <-headers: 190 api.filtersMu.Lock() 191 if f, found := api.filters[headerSub.ID]; found { 192 f.hashes = append(f.hashes, h.Hash()) 193 } 194 api.filtersMu.Unlock() 195 case <-headerSub.Err(): 196 api.filtersMu.Lock() 197 delete(api.filters, headerSub.ID) 198 api.filtersMu.Unlock() 199 return 200 } 201 } 202 }() 203 204 return headerSub.ID 205 } 206 207 // NewHeads send a notification each time a new (header) block is appended to the chain. 208 func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { 209 notifier, supported := rpc.NotifierFromContext(ctx) 210 if !supported { 211 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 212 } 213 214 rpcSub := notifier.CreateSubscription() 215 216 go func() { 217 headers := make(chan *types.Header) 218 headersSub := api.events.SubscribeNewHeads(headers) 219 220 for { 221 select { 222 case h := <-headers: 223 notifier.Notify(rpcSub.ID, h) 224 case <-rpcSub.Err(): 225 headersSub.Unsubscribe() 226 return 227 case <-notifier.Closed(): 228 headersSub.Unsubscribe() 229 return 230 } 231 } 232 }() 233 234 return rpcSub, nil 235 } 236 237 // Logs creates a subscription that fires for all new log that match the given filter criteria. 238 func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { 239 notifier, supported := rpc.NotifierFromContext(ctx) 240 if !supported { 241 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 242 } 243 244 var ( 245 rpcSub = notifier.CreateSubscription() 246 matchedLogs = make(chan []*types.Log) 247 ) 248 249 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) 250 if err != nil { 251 return nil, err 252 } 253 254 go func() { 255 256 for { 257 select { 258 case logs := <-matchedLogs: 259 for _, log := range logs { 260 notifier.Notify(rpcSub.ID, &log) 261 } 262 case <-rpcSub.Err(): // client send an unsubscribe request 263 logsSub.Unsubscribe() 264 return 265 case <-notifier.Closed(): // connection dropped 266 logsSub.Unsubscribe() 267 return 268 } 269 } 270 }() 271 272 return rpcSub, nil 273 } 274 275 // FilterCriteria represents a request to create a new filter. 276 // Same as ethereum.FilterQuery but with UnmarshalJSON() method. 277 type FilterCriteria ethereum.FilterQuery 278 279 // NewFilter creates a new filter and returns the filter id. It can be 280 // used to retrieve logs when the state changes. This method cannot be 281 // used to fetch logs that are already stored in the state. 282 // 283 // Default criteria for the from and to block are "latest". 284 // Using "latest" as block number will return logs for mined blocks. 285 // Using "pending" as block number returns logs for not yet mined (pending) blocks. 286 // In case logs are removed (chain reorg) previously returned logs are returned 287 // again but with the removed property set to true. 288 // 289 // In case "fromBlock" > "toBlock" an error is returned. 290 // 291 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter 292 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { 293 logs := make(chan []*types.Log) 294 logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) 295 if err != nil { 296 return rpc.ID(""), err 297 } 298 299 api.filtersMu.Lock() 300 api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} 301 api.filtersMu.Unlock() 302 303 go func() { 304 for { 305 select { 306 case l := <-logs: 307 api.filtersMu.Lock() 308 if f, found := api.filters[logsSub.ID]; found { 309 f.logs = append(f.logs, l...) 310 } 311 api.filtersMu.Unlock() 312 case <-logsSub.Err(): 313 api.filtersMu.Lock() 314 delete(api.filters, logsSub.ID) 315 api.filtersMu.Unlock() 316 return 317 } 318 } 319 }() 320 321 return logsSub.ID, nil 322 } 323 324 // GetLogs returns logs matching the given argument that are stored within the state. 325 // 326 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs 327 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { 328 var filter *Filter 329 if crit.BlockHash != nil { 330 // Block filter requested, construct a single-shot filter 331 filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) 332 } else { 333 // Convert the RPC block numbers into internal representations 334 begin := rpc.LatestBlockNumber.Int64() 335 if crit.FromBlock != nil { 336 begin = crit.FromBlock.Int64() 337 } 338 end := rpc.LatestBlockNumber.Int64() 339 if crit.ToBlock != nil { 340 end = crit.ToBlock.Int64() 341 } 342 // Construct the range filter 343 filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) 344 } 345 // Run the filter and return all the logs 346 logs, err := filter.Logs(ctx) 347 if err != nil { 348 return nil, err 349 } 350 authLogs, err := api.filterUnAuthorized(ctx, logs) 351 if err != nil { 352 return nil, err 353 } 354 return returnLogs(authLogs), err 355 } 356 357 // UninstallFilter removes the filter with the given filter id. 358 // 359 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter 360 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { 361 api.filtersMu.Lock() 362 f, found := api.filters[id] 363 if found { 364 delete(api.filters, id) 365 } 366 api.filtersMu.Unlock() 367 if found { 368 f.s.Unsubscribe() 369 } 370 371 return found 372 } 373 374 // GetFilterLogs returns the logs for the filter with the given id. 375 // If the filter could not be found an empty array of logs is returned. 376 // 377 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs 378 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { 379 api.filtersMu.Lock() 380 f, found := api.filters[id] 381 api.filtersMu.Unlock() 382 383 if !found || f.typ != LogsSubscription { 384 return nil, fmt.Errorf("filter not found") 385 } 386 387 var filter *Filter 388 if f.crit.BlockHash != nil { 389 // Block filter requested, construct a single-shot filter 390 filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) 391 } else { 392 // Convert the RPC block numbers into internal representations 393 begin := rpc.LatestBlockNumber.Int64() 394 if f.crit.FromBlock != nil { 395 begin = f.crit.FromBlock.Int64() 396 } 397 end := rpc.LatestBlockNumber.Int64() 398 if f.crit.ToBlock != nil { 399 end = f.crit.ToBlock.Int64() 400 } 401 // Construct the range filter 402 filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) 403 } 404 // Run the filter and return all the logs 405 logs, err := filter.Logs(ctx) 406 if err != nil { 407 return nil, err 408 } 409 authLogs, err := api.filterUnAuthorized(ctx, logs) 410 if err != nil { 411 return nil, err 412 } 413 return returnLogs(authLogs), nil 414 } 415 416 // GetFilterChanges returns the logs for the filter with the given id since 417 // last time it was called. This can be used for polling. 418 // 419 // For pending transaction and block filters the result is []common.Hash. 420 // (pending)Log filters return []Log. 421 // 422 // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges 423 func (api *PublicFilterAPI) GetFilterChanges(ctx context.Context, id rpc.ID) (interface{}, error) { 424 api.filtersMu.Lock() 425 defer api.filtersMu.Unlock() 426 427 if f, found := api.filters[id]; found { 428 if !f.deadline.Stop() { 429 // timer expired but filter is not yet removed in timeout loop 430 // receive timer value and reset timer 431 <-f.deadline.C 432 } 433 f.deadline.Reset(deadline) 434 435 switch f.typ { 436 case PendingTransactionsSubscription, BlocksSubscription: 437 hashes := f.hashes 438 f.hashes = nil 439 return returnHashes(hashes), nil 440 case LogsSubscription: 441 logs := f.logs 442 f.logs = nil 443 authLogs, err := api.filterUnAuthorized(ctx, logs) 444 if err != nil { 445 return nil, err 446 } 447 return returnLogs(authLogs), nil 448 } 449 } 450 451 return []interface{}{}, fmt.Errorf("filter not found") 452 } 453 454 // returnHashes is a helper that will return an empty hash array case the given hash array is nil, 455 // otherwise the given hashes array is returned. 456 func returnHashes(hashes []common.Hash) []common.Hash { 457 if hashes == nil { 458 return []common.Hash{} 459 } 460 return hashes 461 } 462 463 // returnLogs is a helper that will return an empty log array in case the given logs array is nil, 464 // otherwise the given logs array is returned. 465 func returnLogs(logs []*types.Log) []*types.Log { 466 if logs == nil { 467 return []*types.Log{} 468 } 469 return logs 470 } 471 472 // UnmarshalJSON sets *args fields with given data. 473 func (args *FilterCriteria) UnmarshalJSON(data []byte) error { 474 type input struct { 475 BlockHash *common.Hash `json:"blockHash"` 476 FromBlock *rpc.BlockNumber `json:"fromBlock"` 477 ToBlock *rpc.BlockNumber `json:"toBlock"` 478 Addresses interface{} `json:"address"` 479 Topics []interface{} `json:"topics"` 480 } 481 482 var raw input 483 if err := json.Unmarshal(data, &raw); err != nil { 484 return err 485 } 486 487 if raw.BlockHash != nil { 488 if raw.FromBlock != nil || raw.ToBlock != nil { 489 // BlockHash is mutually exclusive with FromBlock/ToBlock criteria 490 return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") 491 } 492 args.BlockHash = raw.BlockHash 493 } else { 494 if raw.FromBlock != nil { 495 args.FromBlock = big.NewInt(raw.FromBlock.Int64()) 496 } 497 498 if raw.ToBlock != nil { 499 args.ToBlock = big.NewInt(raw.ToBlock.Int64()) 500 } 501 } 502 503 args.Addresses = []common.Address{} 504 505 if raw.Addresses != nil { 506 // raw.Address can contain a single address or an array of addresses 507 switch rawAddr := raw.Addresses.(type) { 508 case []interface{}: 509 for i, addr := range rawAddr { 510 if strAddr, ok := addr.(string); ok { 511 addr, err := decodeAddress(strAddr) 512 if err != nil { 513 return fmt.Errorf("invalid address at index %d: %v", i, err) 514 } 515 args.Addresses = append(args.Addresses, addr) 516 } else { 517 return fmt.Errorf("non-string address at index %d", i) 518 } 519 } 520 case string: 521 addr, err := decodeAddress(rawAddr) 522 if err != nil { 523 return fmt.Errorf("invalid address: %v", err) 524 } 525 args.Addresses = []common.Address{addr} 526 default: 527 return errors.New("invalid addresses in query") 528 } 529 } 530 531 // topics is an array consisting of strings and/or arrays of strings. 532 // JSON null values are converted to common.Hash{} and ignored by the filter manager. 533 if len(raw.Topics) > 0 { 534 args.Topics = make([][]common.Hash, len(raw.Topics)) 535 for i, t := range raw.Topics { 536 switch topic := t.(type) { 537 case nil: 538 // ignore topic when matching logs 539 540 case string: 541 // match specific topic 542 top, err := decodeTopic(topic) 543 if err != nil { 544 return err 545 } 546 args.Topics[i] = []common.Hash{top} 547 548 case []interface{}: 549 // or case e.g. [null, "topic0", "topic1"] 550 for _, rawTopic := range topic { 551 if rawTopic == nil { 552 // null component, match all 553 args.Topics[i] = nil 554 break 555 } 556 if topic, ok := rawTopic.(string); ok { 557 parsed, err := decodeTopic(topic) 558 if err != nil { 559 return err 560 } 561 args.Topics[i] = append(args.Topics[i], parsed) 562 } else { 563 return fmt.Errorf("invalid topic(s)") 564 } 565 } 566 default: 567 return fmt.Errorf("invalid topic(s)") 568 } 569 } 570 } 571 572 return nil 573 } 574 575 func decodeAddress(s string) (common.Address, error) { 576 b, err := hexutil.Decode(s) 577 if err == nil && len(b) != common.AddressLength { 578 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) 579 } 580 return common.BytesToAddress(b), err 581 } 582 583 func decodeTopic(s string) (common.Hash, error) { 584 b, err := hexutil.Decode(s) 585 if err == nil && len(b) != common.HashLength { 586 err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) 587 } 588 return common.BytesToHash(b), err 589 } 590 591 // Quorum 592 // Perform authorization check for each logs based on the contract addresses 593 func (api *PublicFilterAPI) filterUnAuthorized(ctx context.Context, logs []*types.Log) ([]*types.Log, error) { 594 if len(logs) == 0 { 595 return logs, nil 596 } 597 if authToken, ok := api.backend.SupportsMultitenancy(ctx); ok { 598 filteredLogs := make([]*types.Log, 0) 599 for _, l := range logs { 600 extraDataReader, err := api.backend.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(l.BlockNumber)) 601 if err != nil { 602 return nil, fmt.Errorf("no account extra data reader at block %v: %w", l.BlockNumber, err) 603 } 604 attrBuilder := multitenancy.NewContractSecurityAttributeBuilder().Read().Private() 605 managedParties, err := extraDataReader.GetManagedParties(l.Address) 606 if errors.Is(err, common.ErrNotPrivateContract) { 607 attrBuilder.Public() 608 } else if err != nil { 609 return nil, fmt.Errorf("contract %s not found in the index due to %s", l.Address.Hex(), err.Error()) 610 } 611 if ok, _ := api.backend.IsAuthorized(ctx, authToken, attrBuilder.Parties(managedParties).Build()); ok { 612 filteredLogs = append(filteredLogs, l) 613 } 614 } 615 return filteredLogs, nil 616 } 617 return logs, nil 618 }