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