github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/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  	electroneum "github.com/electroneum/electroneum-sc"
    29  	"github.com/electroneum/electroneum-sc/common"
    30  	"github.com/electroneum/electroneum-sc/common/hexutil"
    31  	"github.com/electroneum/electroneum-sc/core/types"
    32  	"github.com/electroneum/electroneum-sc/rpc"
    33  )
    34  
    35  // filter is a helper struct that holds meta information over the filter type
    36  // and associated subscription in the event system.
    37  type filter struct {
    38  	typ      Type
    39  	deadline *time.Timer // filter is inactiv when deadline triggers
    40  	hashes   []common.Hash
    41  	crit     FilterCriteria
    42  	logs     []*types.Log
    43  	s        *Subscription // associated subscription in event system
    44  }
    45  
    46  // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
    47  // information related to the Ethereum protocol such als blocks, transactions and logs.
    48  type PublicFilterAPI struct {
    49  	backend   Backend
    50  	events    *EventSystem
    51  	filtersMu sync.Mutex
    52  	filters   map[rpc.ID]*filter
    53  	timeout   time.Duration
    54  }
    55  
    56  // NewPublicFilterAPI returns a new PublicFilterAPI instance.
    57  func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *PublicFilterAPI {
    58  	api := &PublicFilterAPI{
    59  		backend: backend,
    60  		events:  NewEventSystem(backend, lightMode),
    61  		filters: make(map[rpc.ID]*filter),
    62  		timeout: timeout,
    63  	}
    64  	go api.timeoutLoop(timeout)
    65  
    66  	return api
    67  }
    68  
    69  // timeoutLoop runs at the interval set by 'timeout' and deletes filters
    70  // that have not been recently used. It is started when the API is created.
    71  func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) {
    72  	var toUninstall []*Subscription
    73  	ticker := time.NewTicker(timeout)
    74  	defer ticker.Stop()
    75  	for {
    76  		<-ticker.C
    77  		api.filtersMu.Lock()
    78  		for id, f := range api.filters {
    79  			select {
    80  			case <-f.deadline.C:
    81  				toUninstall = append(toUninstall, f.s)
    82  				delete(api.filters, id)
    83  			default:
    84  				continue
    85  			}
    86  		}
    87  		api.filtersMu.Unlock()
    88  
    89  		// Unsubscribes are processed outside the lock to avoid the following scenario:
    90  		// event loop attempts broadcasting events to still active filters while
    91  		// Unsubscribe is waiting for it to process the uninstall request.
    92  		for _, s := range toUninstall {
    93  			s.Unsubscribe()
    94  		}
    95  		toUninstall = nil
    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://eth.wiki/json-rpc/API#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(api.timeout), 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://eth.wiki/json-rpc/API#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(api.timeout), 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(electroneum.FilterQuery(crit), matchedLogs)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  	go func() {
   254  		for {
   255  			select {
   256  			case logs := <-matchedLogs:
   257  				for _, log := range logs {
   258  					log := log
   259  					notifier.Notify(rpcSub.ID, &log)
   260  				}
   261  			case <-rpcSub.Err(): // client send an unsubscribe request
   262  				logsSub.Unsubscribe()
   263  				return
   264  			case <-notifier.Closed(): // connection dropped
   265  				logsSub.Unsubscribe()
   266  				return
   267  			}
   268  		}
   269  	}()
   270  
   271  	return rpcSub, nil
   272  }
   273  
   274  // FilterCriteria represents a request to create a new filter.
   275  // Same as ethereum.FilterQuery but with UnmarshalJSON() method.
   276  type FilterCriteria electroneum.FilterQuery
   277  
   278  // NewFilter creates a new filter and returns the filter id. It can be
   279  // used to retrieve logs when the state changes. This method cannot be
   280  // used to fetch logs that are already stored in the state.
   281  //
   282  // Default criteria for the from and to block are "latest".
   283  // Using "latest" as block number will return logs for mined blocks.
   284  // Using "pending" as block number returns logs for not yet mined (pending) blocks.
   285  // In case logs are removed (chain reorg) previously returned logs are returned
   286  // again but with the removed property set to true.
   287  //
   288  // In case "fromBlock" > "toBlock" an error is returned.
   289  //
   290  // https://eth.wiki/json-rpc/API#eth_newfilter
   291  func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
   292  	logs := make(chan []*types.Log)
   293  	logsSub, err := api.events.SubscribeLogs(electroneum.FilterQuery(crit), logs)
   294  	if err != nil {
   295  		return "", err
   296  	}
   297  
   298  	api.filtersMu.Lock()
   299  	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub}
   300  	api.filtersMu.Unlock()
   301  
   302  	go func() {
   303  		for {
   304  			select {
   305  			case l := <-logs:
   306  				api.filtersMu.Lock()
   307  				if f, found := api.filters[logsSub.ID]; found {
   308  					f.logs = append(f.logs, l...)
   309  				}
   310  				api.filtersMu.Unlock()
   311  			case <-logsSub.Err():
   312  				api.filtersMu.Lock()
   313  				delete(api.filters, logsSub.ID)
   314  				api.filtersMu.Unlock()
   315  				return
   316  			}
   317  		}
   318  	}()
   319  
   320  	return logsSub.ID, nil
   321  }
   322  
   323  // GetLogs returns logs matching the given argument that are stored within the state.
   324  //
   325  // https://eth.wiki/json-rpc/API#eth_getlogs
   326  func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
   327  	var filter *Filter
   328  	if crit.BlockHash != nil {
   329  		// Block filter requested, construct a single-shot filter
   330  		filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics)
   331  	} else {
   332  		// Convert the RPC block numbers into internal representations
   333  		begin := rpc.LatestBlockNumber.Int64()
   334  		if crit.FromBlock != nil {
   335  			begin = crit.FromBlock.Int64()
   336  		}
   337  		end := rpc.LatestBlockNumber.Int64()
   338  		if crit.ToBlock != nil {
   339  			end = crit.ToBlock.Int64()
   340  		}
   341  		// Construct the range filter
   342  		filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics)
   343  	}
   344  	// Run the filter and return all the logs
   345  	logs, err := filter.Logs(ctx)
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  	return returnLogs(logs), err
   350  }
   351  
   352  // UninstallFilter removes the filter with the given filter id.
   353  //
   354  // https://eth.wiki/json-rpc/API#eth_uninstallfilter
   355  func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
   356  	api.filtersMu.Lock()
   357  	f, found := api.filters[id]
   358  	if found {
   359  		delete(api.filters, id)
   360  	}
   361  	api.filtersMu.Unlock()
   362  	if found {
   363  		f.s.Unsubscribe()
   364  	}
   365  
   366  	return found
   367  }
   368  
   369  // GetFilterLogs returns the logs for the filter with the given id.
   370  // If the filter could not be found an empty array of logs is returned.
   371  //
   372  // https://eth.wiki/json-rpc/API#eth_getfilterlogs
   373  func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
   374  	api.filtersMu.Lock()
   375  	f, found := api.filters[id]
   376  	api.filtersMu.Unlock()
   377  
   378  	if !found || f.typ != LogsSubscription {
   379  		return nil, fmt.Errorf("filter not found")
   380  	}
   381  
   382  	var filter *Filter
   383  	if f.crit.BlockHash != nil {
   384  		// Block filter requested, construct a single-shot filter
   385  		filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics)
   386  	} else {
   387  		// Convert the RPC block numbers into internal representations
   388  		begin := rpc.LatestBlockNumber.Int64()
   389  		if f.crit.FromBlock != nil {
   390  			begin = f.crit.FromBlock.Int64()
   391  		}
   392  		end := rpc.LatestBlockNumber.Int64()
   393  		if f.crit.ToBlock != nil {
   394  			end = f.crit.ToBlock.Int64()
   395  		}
   396  		// Construct the range filter
   397  		filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics)
   398  	}
   399  	// Run the filter and return all the logs
   400  	logs, err := filter.Logs(ctx)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	return returnLogs(logs), nil
   405  }
   406  
   407  // GetFilterChanges returns the logs for the filter with the given id since
   408  // last time it was called. This can be used for polling.
   409  //
   410  // For pending transaction and block filters the result is []common.Hash.
   411  // (pending)Log filters return []Log.
   412  //
   413  // https://eth.wiki/json-rpc/API#eth_getfilterchanges
   414  func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
   415  	api.filtersMu.Lock()
   416  	defer api.filtersMu.Unlock()
   417  
   418  	if f, found := api.filters[id]; found {
   419  		if !f.deadline.Stop() {
   420  			// timer expired but filter is not yet removed in timeout loop
   421  			// receive timer value and reset timer
   422  			<-f.deadline.C
   423  		}
   424  		f.deadline.Reset(api.timeout)
   425  
   426  		switch f.typ {
   427  		case PendingTransactionsSubscription, BlocksSubscription:
   428  			hashes := f.hashes
   429  			f.hashes = nil
   430  			return returnHashes(hashes), nil
   431  		case LogsSubscription, MinedAndPendingLogsSubscription:
   432  			logs := f.logs
   433  			f.logs = nil
   434  			return returnLogs(logs), nil
   435  		}
   436  	}
   437  
   438  	return []interface{}{}, fmt.Errorf("filter not found")
   439  }
   440  
   441  // returnHashes is a helper that will return an empty hash array case the given hash array is nil,
   442  // otherwise the given hashes array is returned.
   443  func returnHashes(hashes []common.Hash) []common.Hash {
   444  	if hashes == nil {
   445  		return []common.Hash{}
   446  	}
   447  	return hashes
   448  }
   449  
   450  // returnLogs is a helper that will return an empty log array in case the given logs array is nil,
   451  // otherwise the given logs array is returned.
   452  func returnLogs(logs []*types.Log) []*types.Log {
   453  	if logs == nil {
   454  		return []*types.Log{}
   455  	}
   456  	return logs
   457  }
   458  
   459  // UnmarshalJSON sets *args fields with given data.
   460  func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
   461  	type input struct {
   462  		BlockHash *common.Hash     `json:"blockHash"`
   463  		FromBlock *rpc.BlockNumber `json:"fromBlock"`
   464  		ToBlock   *rpc.BlockNumber `json:"toBlock"`
   465  		Addresses interface{}      `json:"address"`
   466  		Topics    []interface{}    `json:"topics"`
   467  	}
   468  
   469  	var raw input
   470  	if err := json.Unmarshal(data, &raw); err != nil {
   471  		return err
   472  	}
   473  
   474  	if raw.BlockHash != nil {
   475  		if raw.FromBlock != nil || raw.ToBlock != nil {
   476  			// BlockHash is mutually exclusive with FromBlock/ToBlock criteria
   477  			return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other")
   478  		}
   479  		args.BlockHash = raw.BlockHash
   480  	} else {
   481  		if raw.FromBlock != nil {
   482  			args.FromBlock = big.NewInt(raw.FromBlock.Int64())
   483  		}
   484  
   485  		if raw.ToBlock != nil {
   486  			args.ToBlock = big.NewInt(raw.ToBlock.Int64())
   487  		}
   488  	}
   489  
   490  	args.Addresses = []common.Address{}
   491  
   492  	if raw.Addresses != nil {
   493  		// raw.Address can contain a single address or an array of addresses
   494  		switch rawAddr := raw.Addresses.(type) {
   495  		case []interface{}:
   496  			for i, addr := range rawAddr {
   497  				if strAddr, ok := addr.(string); ok {
   498  					addr, err := decodeAddress(strAddr)
   499  					if err != nil {
   500  						return fmt.Errorf("invalid address at index %d: %v", i, err)
   501  					}
   502  					args.Addresses = append(args.Addresses, addr)
   503  				} else {
   504  					return fmt.Errorf("non-string address at index %d", i)
   505  				}
   506  			}
   507  		case string:
   508  			addr, err := decodeAddress(rawAddr)
   509  			if err != nil {
   510  				return fmt.Errorf("invalid address: %v", err)
   511  			}
   512  			args.Addresses = []common.Address{addr}
   513  		default:
   514  			return errors.New("invalid addresses in query")
   515  		}
   516  	}
   517  
   518  	// topics is an array consisting of strings and/or arrays of strings.
   519  	// JSON null values are converted to common.Hash{} and ignored by the filter manager.
   520  	if len(raw.Topics) > 0 {
   521  		args.Topics = make([][]common.Hash, len(raw.Topics))
   522  		for i, t := range raw.Topics {
   523  			switch topic := t.(type) {
   524  			case nil:
   525  				// ignore topic when matching logs
   526  
   527  			case string:
   528  				// match specific topic
   529  				top, err := decodeTopic(topic)
   530  				if err != nil {
   531  					return err
   532  				}
   533  				args.Topics[i] = []common.Hash{top}
   534  
   535  			case []interface{}:
   536  				// or case e.g. [null, "topic0", "topic1"]
   537  				for _, rawTopic := range topic {
   538  					if rawTopic == nil {
   539  						// null component, match all
   540  						args.Topics[i] = nil
   541  						break
   542  					}
   543  					if topic, ok := rawTopic.(string); ok {
   544  						parsed, err := decodeTopic(topic)
   545  						if err != nil {
   546  							return err
   547  						}
   548  						args.Topics[i] = append(args.Topics[i], parsed)
   549  					} else {
   550  						return fmt.Errorf("invalid topic(s)")
   551  					}
   552  				}
   553  			default:
   554  				return fmt.Errorf("invalid topic(s)")
   555  			}
   556  		}
   557  	}
   558  
   559  	return nil
   560  }
   561  
   562  func decodeAddress(s string) (common.Address, error) {
   563  	b, err := hexutil.Decode(s)
   564  	if err == nil && len(b) != common.AddressLength {
   565  		err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength)
   566  	}
   567  	return common.BytesToAddress(b), err
   568  }
   569  
   570  func decodeTopic(s string) (common.Hash, error) {
   571  	b, err := hexutil.Decode(s)
   572  	if err == nil && len(b) != common.HashLength {
   573  		err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength)
   574  	}
   575  	return common.BytesToHash(b), err
   576  }