github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/eth/filters/api.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  package filters
    13  
    14  import (
    15  	"context"
    16  	"encoding/json"
    17  	"errors"
    18  	"fmt"
    19  	"math/big"
    20  	"sync"
    21  	"time"
    22  
    23  	sberex "github.com/Sberex/go-sberex"
    24  	"github.com/Sberex/go-sberex/common"
    25  	"github.com/Sberex/go-sberex/common/hexutil"
    26  	"github.com/Sberex/go-sberex/core/types"
    27  	"github.com/Sberex/go-sberex/ethdb"
    28  	"github.com/Sberex/go-sberex/event"
    29  	"github.com/Sberex/go-sberex/rpc"
    30  )
    31  
    32  var (
    33  	deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline
    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 Sberex protocol such als blocks, transactions and logs.
    49  type PublicFilterAPI struct {
    50  	backend   Backend
    51  	mux       *event.TypeMux
    52  	quit      chan struct{}
    53  	chainDb   ethdb.Database
    54  	events    *EventSystem
    55  	filtersMu sync.Mutex
    56  	filters   map[rpc.ID]*filter
    57  }
    58  
    59  // NewPublicFilterAPI returns a new PublicFilterAPI instance.
    60  func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI {
    61  	api := &PublicFilterAPI{
    62  		backend: backend,
    63  		mux:     backend.EventMux(),
    64  		chainDb: backend.ChainDb(),
    65  		events:  NewEventSystem(backend.EventMux(), backend, lightMode),
    66  		filters: make(map[rpc.ID]*filter),
    67  	}
    68  	go api.timeoutLoop()
    69  
    70  	return api
    71  }
    72  
    73  // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used.
    74  // Tt is started when the api is created.
    75  func (api *PublicFilterAPI) timeoutLoop() {
    76  	ticker := time.NewTicker(5 * time.Minute)
    77  	for {
    78  		<-ticker.C
    79  		api.filtersMu.Lock()
    80  		for id, f := range api.filters {
    81  			select {
    82  			case <-f.deadline.C:
    83  				f.s.Unsubscribe()
    84  				delete(api.filters, id)
    85  			default:
    86  				continue
    87  			}
    88  		}
    89  		api.filtersMu.Unlock()
    90  	}
    91  }
    92  
    93  // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes
    94  // as transactions enter the pending state.
    95  //
    96  // It is part of the filter package because this filter can be used throug the
    97  // `eth_getFilterChanges` polling method that is also used for log filters.
    98  //
    99  // https://github.com/Sberex/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter
   100  func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
   101  	var (
   102  		pendingTxs   = make(chan common.Hash)
   103  		pendingTxSub = api.events.SubscribePendingTxEvents(pendingTxs)
   104  	)
   105  
   106  	api.filtersMu.Lock()
   107  	api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
   108  	api.filtersMu.Unlock()
   109  
   110  	go func() {
   111  		for {
   112  			select {
   113  			case ph := <-pendingTxs:
   114  				api.filtersMu.Lock()
   115  				if f, found := api.filters[pendingTxSub.ID]; found {
   116  					f.hashes = append(f.hashes, ph)
   117  				}
   118  				api.filtersMu.Unlock()
   119  			case <-pendingTxSub.Err():
   120  				api.filtersMu.Lock()
   121  				delete(api.filters, pendingTxSub.ID)
   122  				api.filtersMu.Unlock()
   123  				return
   124  			}
   125  		}
   126  	}()
   127  
   128  	return pendingTxSub.ID
   129  }
   130  
   131  // NewPendingTransactions creates a subscription that is triggered each time a transaction
   132  // enters the transaction pool and was signed from one of the transactions this nodes manages.
   133  func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) {
   134  	notifier, supported := rpc.NotifierFromContext(ctx)
   135  	if !supported {
   136  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   137  	}
   138  
   139  	rpcSub := notifier.CreateSubscription()
   140  
   141  	go func() {
   142  		txHashes := make(chan common.Hash)
   143  		pendingTxSub := api.events.SubscribePendingTxEvents(txHashes)
   144  
   145  		for {
   146  			select {
   147  			case h := <-txHashes:
   148  				notifier.Notify(rpcSub.ID, h)
   149  			case <-rpcSub.Err():
   150  				pendingTxSub.Unsubscribe()
   151  				return
   152  			case <-notifier.Closed():
   153  				pendingTxSub.Unsubscribe()
   154  				return
   155  			}
   156  		}
   157  	}()
   158  
   159  	return rpcSub, nil
   160  }
   161  
   162  // NewBlockFilter creates a filter that fetches blocks that are imported into the chain.
   163  // It is part of the filter package since polling goes with eth_getFilterChanges.
   164  //
   165  // https://github.com/Sberex/wiki/wiki/JSON-RPC#eth_newblockfilter
   166  func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
   167  	var (
   168  		headers   = make(chan *types.Header)
   169  		headerSub = api.events.SubscribeNewHeads(headers)
   170  	)
   171  
   172  	api.filtersMu.Lock()
   173  	api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub}
   174  	api.filtersMu.Unlock()
   175  
   176  	go func() {
   177  		for {
   178  			select {
   179  			case h := <-headers:
   180  				api.filtersMu.Lock()
   181  				if f, found := api.filters[headerSub.ID]; found {
   182  					f.hashes = append(f.hashes, h.Hash())
   183  				}
   184  				api.filtersMu.Unlock()
   185  			case <-headerSub.Err():
   186  				api.filtersMu.Lock()
   187  				delete(api.filters, headerSub.ID)
   188  				api.filtersMu.Unlock()
   189  				return
   190  			}
   191  		}
   192  	}()
   193  
   194  	return headerSub.ID
   195  }
   196  
   197  // NewHeads send a notification each time a new (header) block is appended to the chain.
   198  func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
   199  	notifier, supported := rpc.NotifierFromContext(ctx)
   200  	if !supported {
   201  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   202  	}
   203  
   204  	rpcSub := notifier.CreateSubscription()
   205  
   206  	go func() {
   207  		headers := make(chan *types.Header)
   208  		headersSub := api.events.SubscribeNewHeads(headers)
   209  
   210  		for {
   211  			select {
   212  			case h := <-headers:
   213  				notifier.Notify(rpcSub.ID, h)
   214  			case <-rpcSub.Err():
   215  				headersSub.Unsubscribe()
   216  				return
   217  			case <-notifier.Closed():
   218  				headersSub.Unsubscribe()
   219  				return
   220  			}
   221  		}
   222  	}()
   223  
   224  	return rpcSub, nil
   225  }
   226  
   227  // Logs creates a subscription that fires for all new log that match the given filter criteria.
   228  func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) {
   229  	notifier, supported := rpc.NotifierFromContext(ctx)
   230  	if !supported {
   231  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   232  	}
   233  
   234  	var (
   235  		rpcSub      = notifier.CreateSubscription()
   236  		matchedLogs = make(chan []*types.Log)
   237  	)
   238  
   239  	logsSub, err := api.events.SubscribeLogs(sberex.FilterQuery(crit), matchedLogs)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	go func() {
   245  
   246  		for {
   247  			select {
   248  			case logs := <-matchedLogs:
   249  				for _, log := range logs {
   250  					notifier.Notify(rpcSub.ID, &log)
   251  				}
   252  			case <-rpcSub.Err(): // client send an unsubscribe request
   253  				logsSub.Unsubscribe()
   254  				return
   255  			case <-notifier.Closed(): // connection dropped
   256  				logsSub.Unsubscribe()
   257  				return
   258  			}
   259  		}
   260  	}()
   261  
   262  	return rpcSub, nil
   263  }
   264  
   265  // FilterCriteria represents a request to create a new filter.
   266  //
   267  // TODO(karalabe): Kill this in favor of sberex.FilterQuery.
   268  type FilterCriteria struct {
   269  	FromBlock *big.Int
   270  	ToBlock   *big.Int
   271  	Addresses []common.Address
   272  	Topics    [][]common.Hash
   273  }
   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://github.com/Sberex/wiki/wiki/JSON-RPC#eth_newfilter
   288  func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
   289  	logs := make(chan []*types.Log)
   290  	logsSub, err := api.events.SubscribeLogs(sberex.FilterQuery(crit), logs)
   291  	if err != nil {
   292  		return rpc.ID(""), err
   293  	}
   294  
   295  	api.filtersMu.Lock()
   296  	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub}
   297  	api.filtersMu.Unlock()
   298  
   299  	go func() {
   300  		for {
   301  			select {
   302  			case l := <-logs:
   303  				api.filtersMu.Lock()
   304  				if f, found := api.filters[logsSub.ID]; found {
   305  					f.logs = append(f.logs, l...)
   306  				}
   307  				api.filtersMu.Unlock()
   308  			case <-logsSub.Err():
   309  				api.filtersMu.Lock()
   310  				delete(api.filters, logsSub.ID)
   311  				api.filtersMu.Unlock()
   312  				return
   313  			}
   314  		}
   315  	}()
   316  
   317  	return logsSub.ID, nil
   318  }
   319  
   320  // GetLogs returns logs matching the given argument that are stored within the state.
   321  //
   322  // https://github.com/Sberex/wiki/wiki/JSON-RPC#eth_getlogs
   323  func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
   324  	// Convert the RPC block numbers into internal representations
   325  	if crit.FromBlock == nil {
   326  		crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
   327  	}
   328  	if crit.ToBlock == nil {
   329  		crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
   330  	}
   331  	// Create and run the filter to get all the logs
   332  	filter := New(api.backend, crit.FromBlock.Int64(), crit.ToBlock.Int64(), crit.Addresses, crit.Topics)
   333  
   334  	logs, err := filter.Logs(ctx)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	return returnLogs(logs), err
   339  }
   340  
   341  // UninstallFilter removes the filter with the given filter id.
   342  //
   343  // https://github.com/Sberex/wiki/wiki/JSON-RPC#eth_uninstallfilter
   344  func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
   345  	api.filtersMu.Lock()
   346  	f, found := api.filters[id]
   347  	if found {
   348  		delete(api.filters, id)
   349  	}
   350  	api.filtersMu.Unlock()
   351  	if found {
   352  		f.s.Unsubscribe()
   353  	}
   354  
   355  	return found
   356  }
   357  
   358  // GetFilterLogs returns the logs for the filter with the given id.
   359  // If the filter could not be found an empty array of logs is returned.
   360  //
   361  // https://github.com/Sberex/wiki/wiki/JSON-RPC#eth_getfilterlogs
   362  func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
   363  	api.filtersMu.Lock()
   364  	f, found := api.filters[id]
   365  	api.filtersMu.Unlock()
   366  
   367  	if !found || f.typ != LogsSubscription {
   368  		return nil, fmt.Errorf("filter not found")
   369  	}
   370  
   371  	begin := rpc.LatestBlockNumber.Int64()
   372  	if f.crit.FromBlock != nil {
   373  		begin = f.crit.FromBlock.Int64()
   374  	}
   375  	end := rpc.LatestBlockNumber.Int64()
   376  	if f.crit.ToBlock != nil {
   377  		end = f.crit.ToBlock.Int64()
   378  	}
   379  	// Create and run the filter to get all the logs
   380  	filter := New(api.backend, begin, end, f.crit.Addresses, f.crit.Topics)
   381  
   382  	logs, err := filter.Logs(ctx)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	return returnLogs(logs), nil
   387  }
   388  
   389  // GetFilterChanges returns the logs for the filter with the given id since
   390  // last time it was called. This can be used for polling.
   391  //
   392  // For pending transaction and block filters the result is []common.Hash.
   393  // (pending)Log filters return []Log.
   394  //
   395  // https://github.com/Sberex/wiki/wiki/JSON-RPC#eth_getfilterchanges
   396  func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
   397  	api.filtersMu.Lock()
   398  	defer api.filtersMu.Unlock()
   399  
   400  	if f, found := api.filters[id]; found {
   401  		if !f.deadline.Stop() {
   402  			// timer expired but filter is not yet removed in timeout loop
   403  			// receive timer value and reset timer
   404  			<-f.deadline.C
   405  		}
   406  		f.deadline.Reset(deadline)
   407  
   408  		switch f.typ {
   409  		case PendingTransactionsSubscription, BlocksSubscription:
   410  			hashes := f.hashes
   411  			f.hashes = nil
   412  			return returnHashes(hashes), nil
   413  		case LogsSubscription:
   414  			logs := f.logs
   415  			f.logs = nil
   416  			return returnLogs(logs), nil
   417  		}
   418  	}
   419  
   420  	return []interface{}{}, fmt.Errorf("filter not found")
   421  }
   422  
   423  // returnHashes is a helper that will return an empty hash array case the given hash array is nil,
   424  // otherwise the given hashes array is returned.
   425  func returnHashes(hashes []common.Hash) []common.Hash {
   426  	if hashes == nil {
   427  		return []common.Hash{}
   428  	}
   429  	return hashes
   430  }
   431  
   432  // returnLogs is a helper that will return an empty log array in case the given logs array is nil,
   433  // otherwise the given logs array is returned.
   434  func returnLogs(logs []*types.Log) []*types.Log {
   435  	if logs == nil {
   436  		return []*types.Log{}
   437  	}
   438  	return logs
   439  }
   440  
   441  // UnmarshalJSON sets *args fields with given data.
   442  func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
   443  	type input struct {
   444  		From      *rpc.BlockNumber `json:"fromBlock"`
   445  		ToBlock   *rpc.BlockNumber `json:"toBlock"`
   446  		Addresses interface{}      `json:"address"`
   447  		Topics    []interface{}    `json:"topics"`
   448  	}
   449  
   450  	var raw input
   451  	if err := json.Unmarshal(data, &raw); err != nil {
   452  		return err
   453  	}
   454  
   455  	if raw.From != nil {
   456  		args.FromBlock = big.NewInt(raw.From.Int64())
   457  	}
   458  
   459  	if raw.ToBlock != nil {
   460  		args.ToBlock = big.NewInt(raw.ToBlock.Int64())
   461  	}
   462  
   463  	args.Addresses = []common.Address{}
   464  
   465  	if raw.Addresses != nil {
   466  		// raw.Address can contain a single address or an array of addresses
   467  		switch rawAddr := raw.Addresses.(type) {
   468  		case []interface{}:
   469  			for i, addr := range rawAddr {
   470  				if strAddr, ok := addr.(string); ok {
   471  					addr, err := decodeAddress(strAddr)
   472  					if err != nil {
   473  						return fmt.Errorf("invalid address at index %d: %v", i, err)
   474  					}
   475  					args.Addresses = append(args.Addresses, addr)
   476  				} else {
   477  					return fmt.Errorf("non-string address at index %d", i)
   478  				}
   479  			}
   480  		case string:
   481  			addr, err := decodeAddress(rawAddr)
   482  			if err != nil {
   483  				return fmt.Errorf("invalid address: %v", err)
   484  			}
   485  			args.Addresses = []common.Address{addr}
   486  		default:
   487  			return errors.New("invalid addresses in query")
   488  		}
   489  	}
   490  
   491  	// topics is an array consisting of strings and/or arrays of strings.
   492  	// JSON null values are converted to common.Hash{} and ignored by the filter manager.
   493  	if len(raw.Topics) > 0 {
   494  		args.Topics = make([][]common.Hash, len(raw.Topics))
   495  		for i, t := range raw.Topics {
   496  			switch topic := t.(type) {
   497  			case nil:
   498  				// ignore topic when matching logs
   499  
   500  			case string:
   501  				// match specific topic
   502  				top, err := decodeTopic(topic)
   503  				if err != nil {
   504  					return err
   505  				}
   506  				args.Topics[i] = []common.Hash{top}
   507  
   508  			case []interface{}:
   509  				// or case e.g. [null, "topic0", "topic1"]
   510  				for _, rawTopic := range topic {
   511  					if rawTopic == nil {
   512  						// null component, match all
   513  						args.Topics[i] = nil
   514  						break
   515  					}
   516  					if topic, ok := rawTopic.(string); ok {
   517  						parsed, err := decodeTopic(topic)
   518  						if err != nil {
   519  							return err
   520  						}
   521  						args.Topics[i] = append(args.Topics[i], parsed)
   522  					} else {
   523  						return fmt.Errorf("invalid topic(s)")
   524  					}
   525  				}
   526  			default:
   527  				return fmt.Errorf("invalid topic(s)")
   528  			}
   529  		}
   530  	}
   531  
   532  	return nil
   533  }
   534  
   535  func decodeAddress(s string) (common.Address, error) {
   536  	b, err := hexutil.Decode(s)
   537  	if err == nil && len(b) != common.AddressLength {
   538  		err = fmt.Errorf("hex has invalid length %d after decoding", len(b))
   539  	}
   540  	return common.BytesToAddress(b), err
   541  }
   542  
   543  func decodeTopic(s string) (common.Hash, error) {
   544  	b, err := hexutil.Decode(s)
   545  	if err == nil && len(b) != common.HashLength {
   546  		err = fmt.Errorf("hex has invalid length %d after decoding", len(b))
   547  	}
   548  	return common.BytesToHash(b), err
   549  }