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