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