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