github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/qct/filters/api.go (about)

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