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