gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/aqua/filters/api.go (about)

     1  // Copyright 2018 The aquachain Authors
     2  // This file is part of the aquachain library.
     3  //
     4  // The aquachain 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 aquachain 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 aquachain 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  	aquachain "gitlab.com/aquachain/aquachain"
    29  	"gitlab.com/aquachain/aquachain/aqua/event"
    30  	"gitlab.com/aquachain/aquachain/aquadb"
    31  	"gitlab.com/aquachain/aquachain/common"
    32  	"gitlab.com/aquachain/aquachain/common/hexutil"
    33  	"gitlab.com/aquachain/aquachain/core/types"
    34  	"gitlab.com/aquachain/aquachain/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 Aquachain protocol such als blocks, transactions and logs.
    54  type PublicFilterAPI struct {
    55  	backend   Backend
    56  	mux       *event.TypeMux
    57  	chainDb   aquadb.Database
    58  	events    *EventSystem
    59  	filtersMu sync.Mutex
    60  	filters   map[rpc.ID]*filter
    61  }
    62  
    63  // NewPublicFilterAPI returns a new PublicFilterAPI instance.
    64  func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI {
    65  	api := &PublicFilterAPI{
    66  		backend: backend,
    67  		mux:     backend.EventMux(),
    68  		chainDb: backend.ChainDb(),
    69  		events:  NewEventSystem(backend.EventMux(), backend, lightMode),
    70  		filters: make(map[rpc.ID]*filter),
    71  	}
    72  	go api.timeoutLoop()
    73  
    74  	return api
    75  }
    76  
    77  // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used.
    78  // Tt is started when the api is created.
    79  func (api *PublicFilterAPI) timeoutLoop() {
    80  	ticker := time.NewTicker(5 * time.Minute)
    81  	for {
    82  		<-ticker.C
    83  		api.filtersMu.Lock()
    84  		for id, f := range api.filters {
    85  			select {
    86  			case <-f.deadline.C:
    87  				f.s.Unsubscribe()
    88  				delete(api.filters, id)
    89  			default:
    90  				continue
    91  			}
    92  		}
    93  		api.filtersMu.Unlock()
    94  	}
    95  }
    96  
    97  // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes
    98  // as transactions enter the pending state.
    99  //
   100  // It is part of the filter package because this filter can be used throug the
   101  // `aqua_getFilterChanges` polling method that is also used for log filters.
   102  //
   103  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_newpendingtransactionfilter
   104  func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
   105  	var (
   106  		pendingTxs   = make(chan common.Hash)
   107  		pendingTxSub = api.events.SubscribePendingTxEvents(pendingTxs)
   108  	)
   109  
   110  	api.filtersMu.Lock()
   111  	api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), 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 *PublicFilterAPI) 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)
   147  		pendingTxSub := api.events.SubscribePendingTxEvents(txHashes)
   148  
   149  		for {
   150  			select {
   151  			case h := <-txHashes:
   152  				notifier.Notify(rpcSub.ID, h)
   153  			case <-rpcSub.Err():
   154  				pendingTxSub.Unsubscribe()
   155  				return
   156  			case <-notifier.Closed():
   157  				pendingTxSub.Unsubscribe()
   158  				return
   159  			}
   160  		}
   161  	}()
   162  
   163  	return rpcSub, nil
   164  }
   165  
   166  // NewBlockFilter creates a filter that fetches blocks that are imported into the chain.
   167  // It is part of the filter package since polling goes with aqua_getFilterChanges.
   168  //
   169  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_newblockfilter
   170  func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
   171  	var (
   172  		headers   = make(chan *types.Header)
   173  		headerSub = api.events.SubscribeNewHeads(headers)
   174  	)
   175  
   176  	api.filtersMu.Lock()
   177  	api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub}
   178  	api.filtersMu.Unlock()
   179  
   180  	go func() {
   181  		for {
   182  			select {
   183  			case h := <-headers:
   184  				api.filtersMu.Lock()
   185  				if f, found := api.filters[headerSub.ID]; found {
   186  					f.hashes = append(f.hashes, h.Hash())
   187  				}
   188  				api.filtersMu.Unlock()
   189  			case <-headerSub.Err():
   190  				api.filtersMu.Lock()
   191  				delete(api.filters, headerSub.ID)
   192  				api.filtersMu.Unlock()
   193  				return
   194  			}
   195  		}
   196  	}()
   197  
   198  	return headerSub.ID
   199  }
   200  
   201  // NewHeads send a notification each time a new (header) block is appended to the chain.
   202  func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
   203  	notifier, supported := rpc.NotifierFromContext(ctx)
   204  	if !supported {
   205  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   206  	}
   207  
   208  	rpcSub := notifier.CreateSubscription()
   209  
   210  	go func() {
   211  		headers := make(chan *types.Header)
   212  		headersSub := api.events.SubscribeNewHeads(headers)
   213  
   214  		for {
   215  			select {
   216  			case h := <-headers:
   217  				notifier.Notify(rpcSub.ID, h)
   218  			case <-rpcSub.Err():
   219  				headersSub.Unsubscribe()
   220  				return
   221  			case <-notifier.Closed():
   222  				headersSub.Unsubscribe()
   223  				return
   224  			}
   225  		}
   226  	}()
   227  
   228  	return rpcSub, nil
   229  }
   230  
   231  // Logs creates a subscription that fires for all new log that match the given filter criteria.
   232  func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) {
   233  	notifier, supported := rpc.NotifierFromContext(ctx)
   234  	if !supported {
   235  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   236  	}
   237  
   238  	var (
   239  		rpcSub      = notifier.CreateSubscription()
   240  		matchedLogs = make(chan []*types.Log)
   241  	)
   242  
   243  	logsSub, err := api.events.SubscribeLogs(aquachain.FilterQuery(crit), matchedLogs)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	go func() {
   249  
   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  //
   272  // TODO(karalabe): Kill this in favor of aquachain.FilterQuery.
   273  type FilterCriteria struct {
   274  	FromBlock *big.Int
   275  	ToBlock   *big.Int
   276  	Addresses []common.Address
   277  	Topics    [][]common.Hash
   278  }
   279  
   280  // NewFilter creates a new filter and returns the filter id. It can be
   281  // used to retrieve logs when the state changes. This method cannot be
   282  // used to fetch logs that are already stored in the state.
   283  //
   284  // Default criteria for the from and to block are "latest".
   285  // Using "latest" as block number will return logs for mined blocks.
   286  // Using "pending" as block number returns logs for not yet mined (pending) blocks.
   287  // In case logs are removed (chain reorg) previously returned logs are returned
   288  // again but with the removed property set to true.
   289  //
   290  // In case "fromBlock" > "toBlock" an error is returned.
   291  //
   292  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_newfilter
   293  func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
   294  	logs := make(chan []*types.Log)
   295  	logsSub, err := api.events.SubscribeLogs(aquachain.FilterQuery(crit), logs)
   296  	if err != nil {
   297  		return rpc.ID(""), err
   298  	}
   299  
   300  	api.filtersMu.Lock()
   301  	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub}
   302  	api.filtersMu.Unlock()
   303  
   304  	go func() {
   305  		for {
   306  			select {
   307  			case l := <-logs:
   308  				api.filtersMu.Lock()
   309  				if f, found := api.filters[logsSub.ID]; found {
   310  					f.logs = append(f.logs, l...)
   311  				}
   312  				api.filtersMu.Unlock()
   313  			case <-logsSub.Err():
   314  				api.filtersMu.Lock()
   315  				delete(api.filters, logsSub.ID)
   316  				api.filtersMu.Unlock()
   317  				return
   318  			}
   319  		}
   320  	}()
   321  
   322  	return logsSub.ID, nil
   323  }
   324  
   325  // GetLogs returns logs matching the given argument that are stored within the state.
   326  //
   327  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_getlogs
   328  func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
   329  	// Convert the RPC block numbers into internal representations
   330  	if crit.FromBlock == nil {
   331  		crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
   332  	}
   333  	if crit.ToBlock == nil {
   334  		crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
   335  	}
   336  	// Create and run the filter to get all the logs
   337  	filter := New(api.backend, crit.FromBlock.Int64(), crit.ToBlock.Int64(), crit.Addresses, crit.Topics)
   338  
   339  	logs, err := filter.Logs(ctx)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	return returnLogs(logs), err
   344  }
   345  
   346  // UninstallFilter removes the filter with the given filter id.
   347  //
   348  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_uninstallfilter
   349  func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
   350  	api.filtersMu.Lock()
   351  	f, found := api.filters[id]
   352  	if found {
   353  		delete(api.filters, id)
   354  	}
   355  	api.filtersMu.Unlock()
   356  	if found {
   357  		f.s.Unsubscribe()
   358  	}
   359  
   360  	return found
   361  }
   362  
   363  // GetFilterLogs returns the logs for the filter with the given id.
   364  // If the filter could not be found an empty array of logs is returned.
   365  //
   366  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_getfilterlogs
   367  func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
   368  	api.filtersMu.Lock()
   369  	f, found := api.filters[id]
   370  	api.filtersMu.Unlock()
   371  
   372  	if !found || f.typ != LogsSubscription {
   373  		return nil, fmt.Errorf("filter not found")
   374  	}
   375  
   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  	// Create and run the filter to get all the logs
   385  	filter := New(api.backend, begin, end, f.crit.Addresses, f.crit.Topics)
   386  
   387  	logs, err := filter.Logs(ctx)
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  	return returnLogs(logs), nil
   392  }
   393  
   394  // GetFilterChanges returns the logs for the filter with the given id since
   395  // last time it was called. This can be used for polling.
   396  //
   397  // For pending transaction and block filters the result is []common.Hash.
   398  // (pending)Log filters return []Log.
   399  //
   400  // https://github.com/aquanetwork/wiki/wiki/JSON-RPC#aqua_getfilterchanges
   401  func (api *PublicFilterAPI) 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(deadline)
   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:
   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  		From      *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.From != nil {
   461  		args.FromBlock = big.NewInt(raw.From.Int64())
   462  	}
   463  
   464  	if raw.ToBlock != nil {
   465  		args.ToBlock = big.NewInt(raw.ToBlock.Int64())
   466  	}
   467  
   468  	args.Addresses = []common.Address{}
   469  
   470  	if raw.Addresses != nil {
   471  		// raw.Address can contain a single address or an array of addresses
   472  		switch rawAddr := raw.Addresses.(type) {
   473  		case []interface{}:
   474  			for i, addr := range rawAddr {
   475  				if strAddr, ok := addr.(string); ok {
   476  					addr, err := decodeAddress(strAddr)
   477  					if err != nil {
   478  						return fmt.Errorf("invalid address at index %d: %v", i, err)
   479  					}
   480  					args.Addresses = append(args.Addresses, addr)
   481  				} else {
   482  					return fmt.Errorf("non-string address at index %d", i)
   483  				}
   484  			}
   485  		case string:
   486  			addr, err := decodeAddress(rawAddr)
   487  			if err != nil {
   488  				return fmt.Errorf("invalid address: %v", err)
   489  			}
   490  			args.Addresses = []common.Address{addr}
   491  		default:
   492  			return errors.New("invalid addresses in query")
   493  		}
   494  	}
   495  
   496  	// topics is an array consisting of strings and/or arrays of strings.
   497  	// JSON null values are converted to common.Hash{} and ignored by the filter manager.
   498  	if len(raw.Topics) > 0 {
   499  		args.Topics = make([][]common.Hash, len(raw.Topics))
   500  		for i, t := range raw.Topics {
   501  			switch topic := t.(type) {
   502  			case nil:
   503  				// ignore topic when matching logs
   504  
   505  			case string:
   506  				// match specific topic
   507  				top, err := decodeTopic(topic)
   508  				if err != nil {
   509  					return err
   510  				}
   511  				args.Topics[i] = []common.Hash{top}
   512  
   513  			case []interface{}:
   514  				// or case e.g. [null, "topic0", "topic1"]
   515  				for _, rawTopic := range topic {
   516  					if rawTopic == nil {
   517  						// null component, match all
   518  						args.Topics[i] = nil
   519  						break
   520  					}
   521  					if topic, ok := rawTopic.(string); ok {
   522  						parsed, err := decodeTopic(topic)
   523  						if err != nil {
   524  							return err
   525  						}
   526  						args.Topics[i] = append(args.Topics[i], parsed)
   527  					} else {
   528  						return fmt.Errorf("invalid topic(s)")
   529  					}
   530  				}
   531  			default:
   532  				return fmt.Errorf("invalid topic(s)")
   533  			}
   534  		}
   535  	}
   536  
   537  	return nil
   538  }
   539  
   540  func decodeAddress(s string) (common.Address, error) {
   541  	b, err := hexutil.Decode(s)
   542  	if err == nil && len(b) != common.AddressLength {
   543  		err = fmt.Errorf("hex has invalid length %d after decoding", len(b))
   544  	}
   545  	return common.BytesToAddress(b), err
   546  }
   547  
   548  func decodeTopic(s string) (common.Hash, error) {
   549  	b, err := hexutil.Decode(s)
   550  	if err == nil && len(b) != common.HashLength {
   551  		err = fmt.Errorf("hex has invalid length %d after decoding", len(b))
   552  	}
   553  	return common.BytesToHash(b), err
   554  }