github.com/klaytn/klaytn@v1.10.2/node/cn/filters/api.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of go-ethereum.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from eth/filters/api.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package filters
    22  
    23  import (
    24  	"context"
    25  	"encoding/json"
    26  	"errors"
    27  	"fmt"
    28  	"math/big"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/klaytn/klaytn/params"
    33  
    34  	"github.com/klaytn/klaytn"
    35  	"github.com/klaytn/klaytn/blockchain/types"
    36  	"github.com/klaytn/klaytn/common"
    37  	"github.com/klaytn/klaytn/common/hexutil"
    38  	"github.com/klaytn/klaytn/event"
    39  	"github.com/klaytn/klaytn/networks/rpc"
    40  	"github.com/klaytn/klaytn/storage/database"
    41  )
    42  
    43  var (
    44  	deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline
    45  
    46  	getLogsCxtKeyMaxItems = "maxItems"       // the value of the context key should have the type of GetLogsMaxItems
    47  	GetLogsDeadline       = 10 * time.Second // execution deadlines for getLogs and getFilterLogs APIs
    48  	GetLogsMaxItems       = int(10000)       // maximum allowed number of return items for getLogs and getFilterLogs APIs
    49  )
    50  
    51  // filter is a helper struct that holds meta information over the filter type
    52  // and associated subscription in the event system.
    53  type filter struct {
    54  	typ      Type
    55  	deadline *time.Timer // filter is inactiv when deadline triggers
    56  	hashes   []common.Hash
    57  	crit     FilterCriteria
    58  	logs     []*types.Log
    59  	s        *Subscription // associated subscription in event system
    60  }
    61  
    62  // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
    63  // information related to the Klaytn protocol such als blocks, transactions and logs.
    64  type PublicFilterAPI struct {
    65  	backend   Backend
    66  	mux       *event.TypeMux
    67  	quit      chan struct{}
    68  	chainDB   database.DBManager
    69  	events    *EventSystem
    70  	filtersMu sync.Mutex
    71  	filters   map[rpc.ID]*filter
    72  }
    73  
    74  // NewPublicFilterAPI returns a new PublicFilterAPI instance.
    75  func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI {
    76  	api := &PublicFilterAPI{
    77  		backend: backend,
    78  		mux:     backend.EventMux(),
    79  		chainDB: backend.ChainDB(),
    80  		events:  NewEventSystem(backend.EventMux(), backend, lightMode),
    81  		filters: make(map[rpc.ID]*filter),
    82  	}
    83  	go api.timeoutLoop()
    84  
    85  	return api
    86  }
    87  
    88  // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used.
    89  // Tt is started when the api is created.
    90  func (api *PublicFilterAPI) timeoutLoop() {
    91  	ticker := time.NewTicker(5 * time.Minute)
    92  	for {
    93  		<-ticker.C
    94  		api.filtersMu.Lock()
    95  		for id, f := range api.filters {
    96  			select {
    97  			case <-f.deadline.C:
    98  				f.s.Unsubscribe()
    99  				delete(api.filters, id)
   100  			default:
   101  				continue
   102  			}
   103  		}
   104  		api.filtersMu.Unlock()
   105  	}
   106  }
   107  
   108  // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes
   109  // as transactions enter the pending state.
   110  //
   111  // It is part of the filter package because this filter can be used through the
   112  // `klay_getFilterChanges` polling method that is also used for log filters.
   113  func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
   114  	var (
   115  		pendingTxs   = make(chan []common.Hash)
   116  		pendingTxSub = api.events.SubscribePendingTxs(pendingTxs)
   117  	)
   118  
   119  	api.filtersMu.Lock()
   120  	api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
   121  	api.filtersMu.Unlock()
   122  
   123  	go func() {
   124  		for {
   125  			select {
   126  			case ph := <-pendingTxs:
   127  				api.filtersMu.Lock()
   128  				if f, found := api.filters[pendingTxSub.ID]; found {
   129  					f.hashes = append(f.hashes, ph...)
   130  				}
   131  				api.filtersMu.Unlock()
   132  			case <-pendingTxSub.Err():
   133  				api.filtersMu.Lock()
   134  				delete(api.filters, pendingTxSub.ID)
   135  				api.filtersMu.Unlock()
   136  				return
   137  			}
   138  		}
   139  	}()
   140  
   141  	return pendingTxSub.ID
   142  }
   143  
   144  // NewPendingTransactions creates a subscription that is triggered each time a transaction
   145  // enters the transaction pool and was signed from one of the transactions this nodes manages.
   146  func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*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  		txHashes := make(chan []common.Hash, 128)
   156  		pendingTxSub := api.events.SubscribePendingTxs(txHashes)
   157  
   158  		for {
   159  			select {
   160  			case hashes := <-txHashes:
   161  				// To keep the original behaviour, send a single tx hash in one notification.
   162  				// TODO(rjl493456442) Send a batch of tx hashes in one notification
   163  				for _, h := range hashes {
   164  					notifier.Notify(rpcSub.ID, h)
   165  				}
   166  			case <-rpcSub.Err():
   167  				pendingTxSub.Unsubscribe()
   168  				return
   169  			case <-notifier.Closed():
   170  				pendingTxSub.Unsubscribe()
   171  				return
   172  			}
   173  		}
   174  	}()
   175  
   176  	return rpcSub, nil
   177  }
   178  
   179  // NewBlockFilter creates a filter that fetches blocks that are imported into the chain.
   180  // It is part of the filter package since polling goes with eth_getFilterChanges.
   181  func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
   182  	var (
   183  		headers   = make(chan *types.Header)
   184  		headerSub = api.events.SubscribeNewHeads(headers)
   185  	)
   186  
   187  	api.filtersMu.Lock()
   188  	api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub}
   189  	api.filtersMu.Unlock()
   190  
   191  	go func() {
   192  		for {
   193  			select {
   194  			case h := <-headers:
   195  				api.filtersMu.Lock()
   196  				if f, found := api.filters[headerSub.ID]; found {
   197  					f.hashes = append(f.hashes, h.Hash())
   198  				}
   199  				api.filtersMu.Unlock()
   200  			case <-headerSub.Err():
   201  				api.filtersMu.Lock()
   202  				delete(api.filters, headerSub.ID)
   203  				api.filtersMu.Unlock()
   204  				return
   205  			}
   206  		}
   207  	}()
   208  
   209  	return headerSub.ID
   210  }
   211  
   212  // RPCMarshalHeader converts the given header to the RPC output that includes the baseFeePerGas field.
   213  func RPCMarshalHeader(head *types.Header, isEnabledEthTxTypeFork bool) map[string]interface{} {
   214  	result := map[string]interface{}{
   215  		"parentHash":       head.ParentHash,
   216  		"reward":           head.Rewardbase,
   217  		"stateRoot":        head.Root,
   218  		"transactionsRoot": head.TxHash,
   219  		"receiptsRoot":     head.ReceiptHash,
   220  		"logsBloom":        head.Bloom,
   221  		"blockScore":       (*hexutil.Big)(head.BlockScore),
   222  		"number":           (*hexutil.Big)(head.Number),
   223  		"gasUsed":          hexutil.Uint64(head.GasUsed),
   224  		"timestamp":        (*hexutil.Big)(head.Time),
   225  		"timestampFoS":     hexutil.Uint(head.TimeFoS),
   226  		"extraData":        hexutil.Bytes(head.Extra),
   227  		"governanceData":   hexutil.Bytes(head.Governance),
   228  		"voteData":         hexutil.Bytes(head.Vote),
   229  		"hash":             head.Hash(),
   230  	}
   231  
   232  	if isEnabledEthTxTypeFork {
   233  		if head.BaseFee == nil {
   234  			result["baseFeePerGas"] = (*hexutil.Big)(new(big.Int).SetUint64(params.ZeroBaseFee))
   235  		} else {
   236  			result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
   237  		}
   238  	}
   239  
   240  	return result
   241  }
   242  
   243  // NewHeads send a notification each time a new (header) block is appended to the chain.
   244  func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
   245  	notifier, supported := rpc.NotifierFromContext(ctx)
   246  	if !supported {
   247  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   248  	}
   249  
   250  	rpcSub := notifier.CreateSubscription()
   251  
   252  	go func() {
   253  		headers := make(chan *types.Header)
   254  		headersSub := api.events.SubscribeNewHeads(headers)
   255  
   256  		for {
   257  			select {
   258  			case h := <-headers:
   259  				header := RPCMarshalHeader(h, api.backend.ChainConfig().IsEthTxTypeForkEnabled(h.Number))
   260  				notifier.Notify(rpcSub.ID, header)
   261  			case <-rpcSub.Err():
   262  				headersSub.Unsubscribe()
   263  				return
   264  			case <-notifier.Closed():
   265  				headersSub.Unsubscribe()
   266  				return
   267  			}
   268  		}
   269  	}()
   270  
   271  	return rpcSub, nil
   272  }
   273  
   274  // Logs creates a subscription that fires for all new log that match the given filter criteria.
   275  func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) {
   276  	notifier, supported := rpc.NotifierFromContext(ctx)
   277  	if !supported {
   278  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   279  	}
   280  
   281  	var (
   282  		rpcSub      = notifier.CreateSubscription()
   283  		matchedLogs = make(chan []*types.Log)
   284  	)
   285  
   286  	logsSub, err := api.events.SubscribeLogs(klaytn.FilterQuery(crit), matchedLogs)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	go func() {
   292  		for {
   293  			select {
   294  			case logs := <-matchedLogs:
   295  				for _, log := range logs {
   296  					notifier.Notify(rpcSub.ID, &log)
   297  				}
   298  			case <-rpcSub.Err(): // client send an unsubscribe request
   299  				logsSub.Unsubscribe()
   300  				return
   301  			case <-notifier.Closed(): // connection dropped
   302  				logsSub.Unsubscribe()
   303  				return
   304  			}
   305  		}
   306  	}()
   307  
   308  	return rpcSub, nil
   309  }
   310  
   311  // FilterCriteria represents a request to create a new filter.
   312  // Same as klaytn.FilterQuery but with UnmarshalJSON() method.
   313  type FilterCriteria klaytn.FilterQuery
   314  
   315  // NewFilter creates a new filter and returns the filter id. It can be
   316  // used to retrieve logs when the state changes. This method cannot be
   317  // used to fetch logs that are already stored in the state.
   318  //
   319  // Default criteria for the from and to block are "latest".
   320  // Using "latest" as block number will return logs for mined blocks.
   321  // Using "pending" as block number returns logs for not yet mined (pending) blocks.
   322  // In case logs are removed (chain reorg) previously returned logs are returned
   323  // again but with the removed property set to true.
   324  //
   325  // In case "fromBlock" > "toBlock" an error is returned.
   326  func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
   327  	logs := make(chan []*types.Log)
   328  	logsSub, err := api.events.SubscribeLogs(klaytn.FilterQuery(crit), logs)
   329  	if err != nil {
   330  		return rpc.ID(""), err
   331  	}
   332  
   333  	api.filtersMu.Lock()
   334  	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub}
   335  	api.filtersMu.Unlock()
   336  
   337  	go func() {
   338  		for {
   339  			select {
   340  			case l := <-logs:
   341  				api.filtersMu.Lock()
   342  				if f, found := api.filters[logsSub.ID]; found {
   343  					f.logs = append(f.logs, l...)
   344  				}
   345  				api.filtersMu.Unlock()
   346  			case <-logsSub.Err():
   347  				api.filtersMu.Lock()
   348  				delete(api.filters, logsSub.ID)
   349  				api.filtersMu.Unlock()
   350  				return
   351  			}
   352  		}
   353  	}()
   354  
   355  	return logsSub.ID, nil
   356  }
   357  
   358  // GetLogs returns logs matching the given argument that are stored within the state.
   359  func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
   360  	ctx = context.WithValue(ctx, getLogsCxtKeyMaxItems, GetLogsMaxItems)
   361  	ctx, cancelFnc := context.WithTimeout(ctx, GetLogsDeadline)
   362  	defer cancelFnc()
   363  
   364  	var filter *Filter
   365  	if crit.BlockHash != nil {
   366  		// Block filter requested, construct a single-shot filter
   367  		filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics)
   368  	} else {
   369  		// Convert the RPC block numbers into internal representations
   370  		begin := rpc.LatestBlockNumber.Int64()
   371  		if crit.FromBlock != nil {
   372  			begin = crit.FromBlock.Int64()
   373  		}
   374  		end := rpc.LatestBlockNumber.Int64()
   375  		if crit.ToBlock != nil {
   376  			end = crit.ToBlock.Int64()
   377  		}
   378  		// Construct the range filter
   379  		filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics)
   380  	}
   381  
   382  	// Run the filter and return all the logs
   383  	logs, err := filter.Logs(ctx)
   384  	if err != nil {
   385  		return nil, err
   386  	}
   387  	return returnLogs(logs), err
   388  }
   389  
   390  // UninstallFilter removes the filter with the given filter id.
   391  func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
   392  	api.filtersMu.Lock()
   393  	f, found := api.filters[id]
   394  	if found {
   395  		delete(api.filters, id)
   396  	}
   397  	api.filtersMu.Unlock()
   398  	if found {
   399  		f.s.Unsubscribe()
   400  	}
   401  
   402  	return found
   403  }
   404  
   405  // GetFilterLogs returns the logs for the filter with the given id.
   406  // If the filter could not be found an empty array of logs is returned.
   407  func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
   408  	ctx = context.WithValue(ctx, getLogsCxtKeyMaxItems, GetLogsMaxItems)
   409  	ctx, cancelFnc := context.WithTimeout(ctx, GetLogsDeadline)
   410  	defer cancelFnc()
   411  
   412  	api.filtersMu.Lock()
   413  	f, found := api.filters[id]
   414  	api.filtersMu.Unlock()
   415  
   416  	if !found || f.typ != LogsSubscription {
   417  		return nil, fmt.Errorf("filter not found")
   418  	}
   419  
   420  	begin := rpc.LatestBlockNumber.Int64()
   421  	if f.crit.FromBlock != nil {
   422  		begin = f.crit.FromBlock.Int64()
   423  	}
   424  	end := rpc.LatestBlockNumber.Int64()
   425  	if f.crit.ToBlock != nil {
   426  		end = f.crit.ToBlock.Int64()
   427  	}
   428  	// Create and run the filter to get all the logs
   429  	filter := NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics)
   430  
   431  	logs, err := filter.Logs(ctx)
   432  	if err != nil {
   433  		return nil, err
   434  	}
   435  	return returnLogs(logs), nil
   436  }
   437  
   438  // GetFilterChanges returns the logs for the filter with the given id since
   439  // last time it was called. This can be used for polling.
   440  //
   441  // For pending transaction and block filters the result is []common.Hash.
   442  // (pending)Log filters return []Log.
   443  func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
   444  	api.filtersMu.Lock()
   445  	defer api.filtersMu.Unlock()
   446  
   447  	if f, found := api.filters[id]; found {
   448  		if !f.deadline.Stop() {
   449  			// timer expired but filter is not yet removed in timeout loop
   450  			// receive timer value and reset timer
   451  			<-f.deadline.C
   452  		}
   453  		f.deadline.Reset(deadline)
   454  
   455  		switch f.typ {
   456  		case PendingTransactionsSubscription, BlocksSubscription:
   457  			hashes := f.hashes
   458  			f.hashes = nil
   459  			return returnHashes(hashes), nil
   460  		case LogsSubscription:
   461  			logs := f.logs
   462  			f.logs = nil
   463  			return returnLogs(logs), nil
   464  		}
   465  	}
   466  
   467  	return []interface{}{}, fmt.Errorf("filter not found")
   468  }
   469  
   470  // Events return private field events of PublicFilterAPI.
   471  func (api *PublicFilterAPI) Events() *EventSystem {
   472  	return api.events
   473  }
   474  
   475  // returnHashes is a helper that will return an empty hash array case the given hash array is nil,
   476  // otherwise the given hashes array is returned.
   477  func returnHashes(hashes []common.Hash) []common.Hash {
   478  	if hashes == nil {
   479  		return []common.Hash{}
   480  	}
   481  	return hashes
   482  }
   483  
   484  // returnLogs is a helper that will return an empty log array in case the given logs array is nil,
   485  // otherwise the given logs array is returned.
   486  func returnLogs(logs []*types.Log) []*types.Log {
   487  	if logs == nil {
   488  		return []*types.Log{}
   489  	}
   490  	return logs
   491  }
   492  
   493  // UnmarshalJSON sets *args fields with given data.
   494  func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
   495  	type input struct {
   496  		BlockHash *common.Hash     `json:"blockHash"`
   497  		FromBlock *rpc.BlockNumber `json:"fromBlock"`
   498  		ToBlock   *rpc.BlockNumber `json:"toBlock"`
   499  		Addresses interface{}      `json:"address"`
   500  		Topics    []interface{}    `json:"topics"`
   501  	}
   502  
   503  	var raw input
   504  	if err := json.Unmarshal(data, &raw); err != nil {
   505  		return err
   506  	}
   507  
   508  	if raw.BlockHash != nil {
   509  		if raw.FromBlock != nil || raw.ToBlock != nil {
   510  			// BlockHash is mutually exclusive with FromBlock/ToBlock criteria
   511  			return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other")
   512  		}
   513  		args.BlockHash = raw.BlockHash
   514  	} else {
   515  		if raw.FromBlock != nil {
   516  			args.FromBlock = big.NewInt(raw.FromBlock.Int64())
   517  		}
   518  		if raw.ToBlock != nil {
   519  			args.ToBlock = big.NewInt(raw.ToBlock.Int64())
   520  		}
   521  	}
   522  
   523  	args.Addresses = []common.Address{}
   524  
   525  	if raw.Addresses != nil {
   526  		// raw.Address can contain a single address or an array of addresses
   527  		switch rawAddr := raw.Addresses.(type) {
   528  		case []interface{}:
   529  			for i, addr := range rawAddr {
   530  				if strAddr, ok := addr.(string); ok {
   531  					addr, err := decodeAddress(strAddr)
   532  					if err != nil {
   533  						return fmt.Errorf("invalid address at index %d: %v", i, err)
   534  					}
   535  					args.Addresses = append(args.Addresses, addr)
   536  				} else {
   537  					return fmt.Errorf("non-string address at index %d", i)
   538  				}
   539  			}
   540  		case string:
   541  			addr, err := decodeAddress(rawAddr)
   542  			if err != nil {
   543  				return fmt.Errorf("invalid address: %v", err)
   544  			}
   545  			args.Addresses = []common.Address{addr}
   546  		default:
   547  			return errors.New("invalid addresses in query")
   548  		}
   549  	}
   550  
   551  	// topics is an array consisting of strings and/or arrays of strings.
   552  	// JSON null values are converted to common.Hash{} and ignored by the filter manager.
   553  	if len(raw.Topics) > 0 {
   554  		args.Topics = make([][]common.Hash, len(raw.Topics))
   555  		for i, t := range raw.Topics {
   556  			switch topic := t.(type) {
   557  			case nil:
   558  				// ignore topic when matching logs
   559  
   560  			case string:
   561  				// match specific topic
   562  				top, err := decodeTopic(topic)
   563  				if err != nil {
   564  					return err
   565  				}
   566  				args.Topics[i] = []common.Hash{top}
   567  
   568  			case []interface{}:
   569  				// or case e.g. [null, "topic0", "topic1"]
   570  				for _, rawTopic := range topic {
   571  					if rawTopic == nil {
   572  						// null component, match all
   573  						args.Topics[i] = nil
   574  						break
   575  					}
   576  					if topic, ok := rawTopic.(string); ok {
   577  						parsed, err := decodeTopic(topic)
   578  						if err != nil {
   579  							return err
   580  						}
   581  						args.Topics[i] = append(args.Topics[i], parsed)
   582  					} else {
   583  						return fmt.Errorf("invalid topic(s)")
   584  					}
   585  				}
   586  			default:
   587  				return fmt.Errorf("invalid topic(s)")
   588  			}
   589  		}
   590  	}
   591  
   592  	return nil
   593  }
   594  
   595  func decodeAddress(s string) (common.Address, error) {
   596  	b, err := hexutil.Decode(s)
   597  	if err == nil && len(b) != common.AddressLength {
   598  		err = fmt.Errorf("hex has invalid length %d after decoding", len(b))
   599  	}
   600  	return common.BytesToAddress(b), err
   601  }
   602  
   603  func decodeTopic(s string) (common.Hash, error) {
   604  	b, err := hexutil.Decode(s)
   605  	if err == nil && len(b) != common.HashLength {
   606  		err = fmt.Errorf("hex has invalid length %d after decoding", len(b))
   607  	}
   608  	return common.BytesToHash(b), err
   609  }