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