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