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