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