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