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