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