github.com/klaytn/klaytn@v1.12.1/node/cn/filters/api.go (about)

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