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