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