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