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