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