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