github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/eth/filters/api.go (about)

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