github.com/ava-labs/subnet-evm@v0.6.4/eth/filters/api.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2015 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package filters
    28  
    29  import (
    30  	"context"
    31  	"encoding/json"
    32  	"errors"
    33  	"fmt"
    34  	"math/big"
    35  	"sync"
    36  	"time"
    37  
    38  	"github.com/ava-labs/subnet-evm/core/types"
    39  	"github.com/ava-labs/subnet-evm/interfaces"
    40  	"github.com/ava-labs/subnet-evm/internal/ethapi"
    41  	"github.com/ava-labs/subnet-evm/rpc"
    42  	"github.com/ethereum/go-ethereum/common"
    43  	"github.com/ethereum/go-ethereum/common/hexutil"
    44  	"github.com/ethereum/go-ethereum/event"
    45  )
    46  
    47  var (
    48  	errInvalidTopic   = errors.New("invalid topic(s)")
    49  	errFilterNotFound = errors.New("filter not found")
    50  )
    51  
    52  // filter is a helper struct that holds meta information over the filter type
    53  // and associated subscription in the event system.
    54  type filter struct {
    55  	typ      Type
    56  	deadline *time.Timer // filter is inactive when deadline triggers
    57  	hashes   []common.Hash
    58  	fullTx   bool
    59  	txs      []*types.Transaction
    60  	crit     FilterCriteria
    61  	logs     []*types.Log
    62  	s        *Subscription // associated subscription in event system
    63  }
    64  
    65  // FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
    66  // information related to the Ethereum protocol such as blocks, transactions and logs.
    67  type FilterAPI struct {
    68  	sys       *FilterSystem
    69  	events    *EventSystem
    70  	filtersMu sync.Mutex
    71  	filters   map[rpc.ID]*filter
    72  	timeout   time.Duration
    73  }
    74  
    75  // NewFilterAPI returns a new FilterAPI instance.
    76  func NewFilterAPI(system *FilterSystem) *FilterAPI {
    77  	api := &FilterAPI{
    78  		sys:     system,
    79  		events:  NewEventSystem(system),
    80  		filters: make(map[rpc.ID]*filter),
    81  		timeout: system.cfg.Timeout,
    82  	}
    83  	go api.timeoutLoop(system.cfg.Timeout)
    84  
    85  	return api
    86  }
    87  
    88  // timeoutLoop runs at the interval set by 'timeout' and deletes filters
    89  // that have not been recently used. It is started when the API is created.
    90  func (api *FilterAPI) timeoutLoop(timeout time.Duration) {
    91  	var toUninstall []*Subscription
    92  	ticker := time.NewTicker(timeout)
    93  	defer ticker.Stop()
    94  	for {
    95  		<-ticker.C
    96  		api.filtersMu.Lock()
    97  		for id, f := range api.filters {
    98  			select {
    99  			case <-f.deadline.C:
   100  				toUninstall = append(toUninstall, f.s)
   101  				delete(api.filters, id)
   102  			default:
   103  				continue
   104  			}
   105  		}
   106  		api.filtersMu.Unlock()
   107  
   108  		// Unsubscribes are processed outside the lock to avoid the following scenario:
   109  		// event loop attempts broadcasting events to still active filters while
   110  		// Unsubscribe is waiting for it to process the uninstall request.
   111  		for _, s := range toUninstall {
   112  			s.Unsubscribe()
   113  		}
   114  		toUninstall = nil
   115  	}
   116  }
   117  
   118  // NewPendingTransactionFilter creates a filter that fetches pending transactions
   119  // as transactions enter the pending state.
   120  //
   121  // It is part of the filter package because this filter can be used through the
   122  // `eth_getFilterChanges` polling method that is also used for log filters.
   123  func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
   124  	var (
   125  		pendingTxs   = make(chan []*types.Transaction)
   126  		pendingTxSub = api.events.SubscribePendingTxs(pendingTxs)
   127  	)
   128  
   129  	api.filtersMu.Lock()
   130  	api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, fullTx: fullTx != nil && *fullTx, deadline: time.NewTimer(api.timeout), txs: make([]*types.Transaction, 0), s: pendingTxSub}
   131  	api.filtersMu.Unlock()
   132  
   133  	go func() {
   134  		for {
   135  			select {
   136  			case pTx := <-pendingTxs:
   137  				api.filtersMu.Lock()
   138  				if f, found := api.filters[pendingTxSub.ID]; found {
   139  					f.txs = append(f.txs, pTx...)
   140  				}
   141  				api.filtersMu.Unlock()
   142  			case <-pendingTxSub.Err():
   143  				api.filtersMu.Lock()
   144  				delete(api.filters, pendingTxSub.ID)
   145  				api.filtersMu.Unlock()
   146  				return
   147  			}
   148  		}
   149  	}()
   150  
   151  	return pendingTxSub.ID
   152  }
   153  
   154  // NewPendingTransactions creates a subscription that is triggered each time a
   155  // transaction enters the transaction pool. If fullTx is true the full tx is
   156  // sent to the client, otherwise the hash is sent.
   157  func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) {
   158  	notifier, supported := rpc.NotifierFromContext(ctx)
   159  	if !supported {
   160  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   161  	}
   162  
   163  	rpcSub := notifier.CreateSubscription()
   164  
   165  	go func() {
   166  		txs := make(chan []*types.Transaction, 128)
   167  		pendingTxSub := api.events.SubscribePendingTxs(txs)
   168  		chainConfig := api.sys.backend.ChainConfig()
   169  
   170  		for {
   171  			select {
   172  			case txs := <-txs:
   173  				// To keep the original behaviour, send a single tx hash in one notification.
   174  				// TODO(rjl493456442) Send a batch of tx hashes in one notification
   175  				latest := api.sys.backend.CurrentHeader()
   176  				for _, tx := range txs {
   177  					if fullTx != nil && *fullTx {
   178  						rpcTx := ethapi.NewRPCTransaction(tx, latest, latest.BaseFee, chainConfig)
   179  						notifier.Notify(rpcSub.ID, rpcTx)
   180  					} else {
   181  						notifier.Notify(rpcSub.ID, tx.Hash())
   182  					}
   183  				}
   184  			case <-rpcSub.Err():
   185  				pendingTxSub.Unsubscribe()
   186  				return
   187  			case <-notifier.Closed():
   188  				pendingTxSub.Unsubscribe()
   189  				return
   190  			}
   191  		}
   192  	}()
   193  
   194  	return rpcSub, nil
   195  }
   196  
   197  // NewAcceptedTransactions creates a subscription that is triggered each time a
   198  // transaction is accepted. If fullTx is true the full tx is
   199  // sent to the client, otherwise the hash is sent.
   200  func (api *FilterAPI) NewAcceptedTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) {
   201  	notifier, supported := rpc.NotifierFromContext(ctx)
   202  	if !supported {
   203  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   204  	}
   205  
   206  	rpcSub := notifier.CreateSubscription()
   207  
   208  	go func() {
   209  		txs := make(chan []*types.Transaction, 128)
   210  		acceptedTxSub := api.events.SubscribeAcceptedTxs(txs)
   211  		chainConfig := api.sys.backend.ChainConfig()
   212  
   213  		for {
   214  			select {
   215  			case txs := <-txs:
   216  				// To keep the original behaviour, send a single tx hash in one notification.
   217  				// TODO(rjl493456442) Send a batch of tx hashes in one notification
   218  				latest := api.sys.backend.LastAcceptedBlock().Header()
   219  				for _, tx := range txs {
   220  					if fullTx != nil && *fullTx {
   221  						rpcTx := ethapi.NewRPCTransaction(tx, latest, latest.BaseFee, chainConfig)
   222  						notifier.Notify(rpcSub.ID, rpcTx)
   223  					} else {
   224  						notifier.Notify(rpcSub.ID, tx.Hash())
   225  					}
   226  				}
   227  			case <-rpcSub.Err():
   228  				acceptedTxSub.Unsubscribe()
   229  				return
   230  			case <-notifier.Closed():
   231  				acceptedTxSub.Unsubscribe()
   232  				return
   233  			}
   234  		}
   235  	}()
   236  
   237  	return rpcSub, nil
   238  }
   239  
   240  // NewBlockFilter creates a filter that fetches blocks that are imported into the chain.
   241  // It is part of the filter package since polling goes with eth_getFilterChanges.
   242  func (api *FilterAPI) NewBlockFilter() rpc.ID {
   243  	var (
   244  		headers   = make(chan *types.Header)
   245  		headerSub *Subscription
   246  	)
   247  
   248  	if api.sys.backend.IsAllowUnfinalizedQueries() {
   249  		headerSub = api.events.SubscribeNewHeads(headers)
   250  	} else {
   251  		headerSub = api.events.SubscribeAcceptedHeads(headers)
   252  	}
   253  
   254  	api.filtersMu.Lock()
   255  	api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub}
   256  	api.filtersMu.Unlock()
   257  
   258  	go func() {
   259  		for {
   260  			select {
   261  			case h := <-headers:
   262  				api.filtersMu.Lock()
   263  				if f, found := api.filters[headerSub.ID]; found {
   264  					f.hashes = append(f.hashes, h.Hash())
   265  				}
   266  				api.filtersMu.Unlock()
   267  			case <-headerSub.Err():
   268  				api.filtersMu.Lock()
   269  				delete(api.filters, headerSub.ID)
   270  				api.filtersMu.Unlock()
   271  				return
   272  			}
   273  		}
   274  	}()
   275  
   276  	return headerSub.ID
   277  }
   278  
   279  // NewHeads send a notification each time a new (header) block is appended to the chain.
   280  func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
   281  	notifier, supported := rpc.NotifierFromContext(ctx)
   282  	if !supported {
   283  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   284  	}
   285  
   286  	rpcSub := notifier.CreateSubscription()
   287  
   288  	go func() {
   289  		var (
   290  			headers    = make(chan *types.Header)
   291  			headersSub event.Subscription
   292  		)
   293  
   294  		if api.sys.backend.IsAllowUnfinalizedQueries() {
   295  			headersSub = api.events.SubscribeNewHeads(headers)
   296  		} else {
   297  			headersSub = api.events.SubscribeAcceptedHeads(headers)
   298  		}
   299  
   300  		for {
   301  			select {
   302  			case h := <-headers:
   303  				notifier.Notify(rpcSub.ID, h)
   304  			case <-rpcSub.Err():
   305  				headersSub.Unsubscribe()
   306  				return
   307  			case <-notifier.Closed():
   308  				headersSub.Unsubscribe()
   309  				return
   310  			}
   311  		}
   312  	}()
   313  
   314  	return rpcSub, nil
   315  }
   316  
   317  // Logs creates a subscription that fires for all new log that match the given filter criteria.
   318  func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) {
   319  	notifier, supported := rpc.NotifierFromContext(ctx)
   320  	if !supported {
   321  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   322  	}
   323  
   324  	var (
   325  		rpcSub      = notifier.CreateSubscription()
   326  		matchedLogs = make(chan []*types.Log)
   327  		logsSub     event.Subscription
   328  		err         error
   329  	)
   330  
   331  	if api.sys.backend.IsAllowUnfinalizedQueries() {
   332  		logsSub, err = api.events.SubscribeLogs(interfaces.FilterQuery(crit), matchedLogs)
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  	} else {
   337  		logsSub, err = api.events.SubscribeAcceptedLogs(interfaces.FilterQuery(crit), matchedLogs)
   338  		if err != nil {
   339  			return nil, err
   340  		}
   341  	}
   342  
   343  	go func() {
   344  		for {
   345  			select {
   346  			case logs := <-matchedLogs:
   347  				for _, log := range logs {
   348  					log := log
   349  					notifier.Notify(rpcSub.ID, &log)
   350  				}
   351  			case <-rpcSub.Err(): // client send an unsubscribe request
   352  				logsSub.Unsubscribe()
   353  				return
   354  			case <-notifier.Closed(): // connection dropped
   355  				logsSub.Unsubscribe()
   356  				return
   357  			}
   358  		}
   359  	}()
   360  
   361  	return rpcSub, nil
   362  }
   363  
   364  // FilterCriteria represents a request to create a new filter.
   365  // Same as interfaces.FilterQuery but with UnmarshalJSON() method.
   366  type FilterCriteria interfaces.FilterQuery
   367  
   368  // NewFilter creates a new filter and returns the filter id. It can be
   369  // used to retrieve logs when the state changes. This method cannot be
   370  // used to fetch logs that are already stored in the state.
   371  //
   372  // Default criteria for the from and to block are "latest".
   373  // Using "latest" as block number will return logs for mined blocks.
   374  // Using "pending" as block number returns logs for not yet mined (pending) blocks.
   375  // In case logs are removed (chain reorg) previously returned logs are returned
   376  // again but with the removed property set to true.
   377  //
   378  // In case "fromBlock" > "toBlock" an error is returned.
   379  func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
   380  	var (
   381  		logs    = make(chan []*types.Log)
   382  		logsSub *Subscription
   383  		err     error
   384  	)
   385  
   386  	if api.sys.backend.IsAllowUnfinalizedQueries() {
   387  		logsSub, err = api.events.SubscribeLogs(interfaces.FilterQuery(crit), logs)
   388  		if err != nil {
   389  			return "", err
   390  		}
   391  	} else {
   392  		logsSub, err = api.events.SubscribeAcceptedLogs(interfaces.FilterQuery(crit), logs)
   393  		if err != nil {
   394  			return "", err
   395  		}
   396  	}
   397  
   398  	api.filtersMu.Lock()
   399  	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub}
   400  	api.filtersMu.Unlock()
   401  
   402  	go func() {
   403  		for {
   404  			select {
   405  			case l := <-logs:
   406  				api.filtersMu.Lock()
   407  				if f, found := api.filters[logsSub.ID]; found {
   408  					f.logs = append(f.logs, l...)
   409  				}
   410  				api.filtersMu.Unlock()
   411  			case <-logsSub.Err():
   412  				api.filtersMu.Lock()
   413  				delete(api.filters, logsSub.ID)
   414  				api.filtersMu.Unlock()
   415  				return
   416  			}
   417  		}
   418  	}()
   419  
   420  	return logsSub.ID, nil
   421  }
   422  
   423  // GetLogs returns logs matching the given argument that are stored within the state.
   424  func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
   425  	var filter *Filter
   426  	if crit.BlockHash != nil {
   427  		// Block filter requested, construct a single-shot filter
   428  		filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics)
   429  	} else {
   430  		// Convert the RPC block numbers into internal representations
   431  		// LatestBlockNumber is left in place here to be handled
   432  		// correctly within NewRangeFilter
   433  		begin := rpc.LatestBlockNumber.Int64()
   434  		if crit.FromBlock != nil {
   435  			begin = crit.FromBlock.Int64()
   436  		}
   437  		end := rpc.LatestBlockNumber.Int64()
   438  		if crit.ToBlock != nil {
   439  			end = crit.ToBlock.Int64()
   440  		}
   441  		// Construct the range filter
   442  		filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
   443  	}
   444  	// Run the filter and return all the logs
   445  	logs, err := filter.Logs(ctx)
   446  	if err != nil {
   447  		return nil, err
   448  	}
   449  	return returnLogs(logs), err
   450  }
   451  
   452  // UninstallFilter removes the filter with the given filter id.
   453  func (api *FilterAPI) UninstallFilter(id rpc.ID) bool {
   454  	api.filtersMu.Lock()
   455  	f, found := api.filters[id]
   456  	if found {
   457  		delete(api.filters, id)
   458  	}
   459  	api.filtersMu.Unlock()
   460  	if found {
   461  		f.s.Unsubscribe()
   462  	}
   463  
   464  	return found
   465  }
   466  
   467  // GetFilterLogs returns the logs for the filter with the given id.
   468  // If the filter could not be found an empty array of logs is returned.
   469  func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
   470  	api.filtersMu.Lock()
   471  	f, found := api.filters[id]
   472  	api.filtersMu.Unlock()
   473  
   474  	if !found || f.typ != LogsSubscription {
   475  		return nil, errFilterNotFound
   476  	}
   477  
   478  	var filter *Filter
   479  	if f.crit.BlockHash != nil {
   480  		// Block filter requested, construct a single-shot filter
   481  		filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics)
   482  	} else {
   483  		// Convert the RPC block numbers into internal representations
   484  		// Leave LatestBlockNumber in place here as the defaults
   485  		// Should be handled correctly as request for the last
   486  		// accepted block instead throughout all APIs.
   487  		begin := rpc.LatestBlockNumber.Int64()
   488  		if f.crit.FromBlock != nil {
   489  			begin = f.crit.FromBlock.Int64()
   490  		}
   491  		end := rpc.LatestBlockNumber.Int64()
   492  		if f.crit.ToBlock != nil {
   493  			end = f.crit.ToBlock.Int64()
   494  		}
   495  		// Construct the range filter
   496  		filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics)
   497  	}
   498  	// Run the filter and return all the logs
   499  	logs, err := filter.Logs(ctx)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	return returnLogs(logs), nil
   504  }
   505  
   506  // GetFilterChanges returns the logs for the filter with the given id since
   507  // last time it was called. This can be used for polling.
   508  //
   509  // For pending transaction and block filters the result is []common.Hash.
   510  // (pending)Log filters return []Log.
   511  func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
   512  	api.filtersMu.Lock()
   513  	defer api.filtersMu.Unlock()
   514  
   515  	chainConfig := api.sys.backend.ChainConfig()
   516  	latest := api.sys.backend.CurrentHeader()
   517  
   518  	var baseFee *big.Int
   519  	if latest != nil {
   520  		baseFee = latest.BaseFee
   521  	}
   522  
   523  	if f, found := api.filters[id]; found {
   524  		if !f.deadline.Stop() {
   525  			// timer expired but filter is not yet removed in timeout loop
   526  			// receive timer value and reset timer
   527  			<-f.deadline.C
   528  		}
   529  		f.deadline.Reset(api.timeout)
   530  
   531  		switch f.typ {
   532  		case BlocksSubscription, AcceptedBlocksSubscription:
   533  			hashes := f.hashes
   534  			f.hashes = nil
   535  			return returnHashes(hashes), nil
   536  		case PendingTransactionsSubscription, AcceptedTransactionsSubscription:
   537  			if f.fullTx {
   538  				txs := make([]*ethapi.RPCTransaction, 0, len(f.txs))
   539  				for _, tx := range f.txs {
   540  					txs = append(txs, ethapi.NewRPCTransaction(tx, latest, baseFee, chainConfig))
   541  				}
   542  				f.txs = nil
   543  				return txs, nil
   544  			} else {
   545  				hashes := make([]common.Hash, 0, len(f.txs))
   546  				for _, tx := range f.txs {
   547  					hashes = append(hashes, tx.Hash())
   548  				}
   549  				f.txs = nil
   550  				return hashes, nil
   551  			}
   552  		case LogsSubscription, AcceptedLogsSubscription, MinedAndPendingLogsSubscription:
   553  			logs := f.logs
   554  			f.logs = nil
   555  			return returnLogs(logs), nil
   556  		}
   557  	}
   558  
   559  	return []interface{}{}, errFilterNotFound
   560  }
   561  
   562  // returnHashes is a helper that will return an empty hash array case the given hash array is nil,
   563  // otherwise the given hashes array is returned.
   564  func returnHashes(hashes []common.Hash) []common.Hash {
   565  	if hashes == nil {
   566  		return []common.Hash{}
   567  	}
   568  	return hashes
   569  }
   570  
   571  // returnLogs is a helper that will return an empty log array in case the given logs array is nil,
   572  // otherwise the given logs array is returned.
   573  func returnLogs(logs []*types.Log) []*types.Log {
   574  	if logs == nil {
   575  		return []*types.Log{}
   576  	}
   577  	return logs
   578  }
   579  
   580  // UnmarshalJSON sets *args fields with given data.
   581  func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
   582  	type input struct {
   583  		BlockHash *common.Hash     `json:"blockHash"`
   584  		FromBlock *rpc.BlockNumber `json:"fromBlock"`
   585  		ToBlock   *rpc.BlockNumber `json:"toBlock"`
   586  		Addresses interface{}      `json:"address"`
   587  		Topics    []interface{}    `json:"topics"`
   588  	}
   589  
   590  	var raw input
   591  	if err := json.Unmarshal(data, &raw); err != nil {
   592  		return err
   593  	}
   594  
   595  	if raw.BlockHash != nil {
   596  		if raw.FromBlock != nil || raw.ToBlock != nil {
   597  			// BlockHash is mutually exclusive with FromBlock/ToBlock criteria
   598  			return errors.New("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other")
   599  		}
   600  		args.BlockHash = raw.BlockHash
   601  	} else {
   602  		if raw.FromBlock != nil {
   603  			args.FromBlock = big.NewInt(raw.FromBlock.Int64())
   604  		}
   605  
   606  		if raw.ToBlock != nil {
   607  			args.ToBlock = big.NewInt(raw.ToBlock.Int64())
   608  		}
   609  	}
   610  
   611  	args.Addresses = []common.Address{}
   612  
   613  	if raw.Addresses != nil {
   614  		// raw.Address can contain a single address or an array of addresses
   615  		switch rawAddr := raw.Addresses.(type) {
   616  		case []interface{}:
   617  			for i, addr := range rawAddr {
   618  				if strAddr, ok := addr.(string); ok {
   619  					addr, err := decodeAddress(strAddr)
   620  					if err != nil {
   621  						return fmt.Errorf("invalid address at index %d: %v", i, err)
   622  					}
   623  					args.Addresses = append(args.Addresses, addr)
   624  				} else {
   625  					return fmt.Errorf("non-string address at index %d", i)
   626  				}
   627  			}
   628  		case string:
   629  			addr, err := decodeAddress(rawAddr)
   630  			if err != nil {
   631  				return fmt.Errorf("invalid address: %v", err)
   632  			}
   633  			args.Addresses = []common.Address{addr}
   634  		default:
   635  			return errors.New("invalid addresses in query")
   636  		}
   637  	}
   638  
   639  	// topics is an array consisting of strings and/or arrays of strings.
   640  	// JSON null values are converted to common.Hash{} and ignored by the filter manager.
   641  	if len(raw.Topics) > 0 {
   642  		args.Topics = make([][]common.Hash, len(raw.Topics))
   643  		for i, t := range raw.Topics {
   644  			switch topic := t.(type) {
   645  			case nil:
   646  				// ignore topic when matching logs
   647  
   648  			case string:
   649  				// match specific topic
   650  				top, err := decodeTopic(topic)
   651  				if err != nil {
   652  					return err
   653  				}
   654  				args.Topics[i] = []common.Hash{top}
   655  
   656  			case []interface{}:
   657  				// or case e.g. [null, "topic0", "topic1"]
   658  				for _, rawTopic := range topic {
   659  					if rawTopic == nil {
   660  						// null component, match all
   661  						args.Topics[i] = nil
   662  						break
   663  					}
   664  					if topic, ok := rawTopic.(string); ok {
   665  						parsed, err := decodeTopic(topic)
   666  						if err != nil {
   667  							return err
   668  						}
   669  						args.Topics[i] = append(args.Topics[i], parsed)
   670  					} else {
   671  						return errInvalidTopic
   672  					}
   673  				}
   674  			default:
   675  				return errInvalidTopic
   676  			}
   677  		}
   678  	}
   679  
   680  	return nil
   681  }
   682  
   683  func decodeAddress(s string) (common.Address, error) {
   684  	b, err := hexutil.Decode(s)
   685  	if err == nil && len(b) != common.AddressLength {
   686  		err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength)
   687  	}
   688  	return common.BytesToAddress(b), err
   689  }
   690  
   691  func decodeTopic(s string) (common.Hash, error) {
   692  	b, err := hexutil.Decode(s)
   693  	if err == nil && len(b) != common.HashLength {
   694  		err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength)
   695  	}
   696  	return common.BytesToHash(b), err
   697  }