github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/light/rpc/client.go (about)

     1  package rpc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"regexp"
     9  	"time"
    10  
    11  	"github.com/cosmos/gogoproto/proto"
    12  
    13  	abci "github.com/badrootd/nibiru-cometbft/abci/types"
    14  	"github.com/badrootd/nibiru-cometbft/crypto/merkle"
    15  	cmtbytes "github.com/badrootd/nibiru-cometbft/libs/bytes"
    16  	cmtmath "github.com/badrootd/nibiru-cometbft/libs/math"
    17  	service "github.com/badrootd/nibiru-cometbft/libs/service"
    18  	rpcclient "github.com/badrootd/nibiru-cometbft/rpc/client"
    19  	ctypes "github.com/badrootd/nibiru-cometbft/rpc/core/types"
    20  	rpctypes "github.com/badrootd/nibiru-cometbft/rpc/jsonrpc/types"
    21  	"github.com/badrootd/nibiru-cometbft/types"
    22  )
    23  
    24  var errNegOrZeroHeight = errors.New("negative or zero height")
    25  
    26  // KeyPathFunc builds a merkle path out of the given path and key.
    27  type KeyPathFunc func(path string, key []byte) (merkle.KeyPath, error)
    28  
    29  // LightClient is an interface that contains functionality needed by Client from the light client.
    30  //
    31  //go:generate ../../scripts/mockery_generate.sh LightClient
    32  type LightClient interface {
    33  	ChainID() string
    34  	Update(ctx context.Context, now time.Time) (*types.LightBlock, error)
    35  	VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error)
    36  	TrustedLightBlock(height int64) (*types.LightBlock, error)
    37  }
    38  
    39  var _ rpcclient.Client = (*Client)(nil)
    40  
    41  // Client is an RPC client, which uses light#Client to verify data (if it can
    42  // be proved). Note, merkle.DefaultProofRuntime is used to verify values
    43  // returned by ABCI#Query.
    44  type Client struct {
    45  	service.BaseService
    46  
    47  	next rpcclient.Client
    48  	lc   LightClient
    49  
    50  	// proof runtime used to verify values returned by ABCIQuery
    51  	prt       *merkle.ProofRuntime
    52  	keyPathFn KeyPathFunc
    53  }
    54  
    55  var _ rpcclient.Client = (*Client)(nil)
    56  
    57  // Option allow you to tweak Client.
    58  type Option func(*Client)
    59  
    60  // KeyPathFn option can be used to set a function, which parses a given path
    61  // and builds the merkle path for the prover. It must be provided if you want
    62  // to call ABCIQuery or ABCIQueryWithOptions.
    63  func KeyPathFn(fn KeyPathFunc) Option {
    64  	return func(c *Client) {
    65  		c.keyPathFn = fn
    66  	}
    67  }
    68  
    69  // DefaultMerkleKeyPathFn creates a function used to generate merkle key paths
    70  // from a path string and a key. This is the default used by the cosmos SDK.
    71  // This merkle key paths are required when verifying /abci_query calls
    72  func DefaultMerkleKeyPathFn() KeyPathFunc {
    73  	// regexp for extracting store name from /abci_query path
    74  	storeNameRegexp := regexp.MustCompile(`\/store\/(.+)\/key`)
    75  
    76  	return func(path string, key []byte) (merkle.KeyPath, error) {
    77  		matches := storeNameRegexp.FindStringSubmatch(path)
    78  		if len(matches) != 2 {
    79  			return nil, fmt.Errorf("can't find store name in %s using %s", path, storeNameRegexp)
    80  		}
    81  		storeName := matches[1]
    82  
    83  		kp := merkle.KeyPath{}
    84  		kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
    85  		kp = kp.AppendKey(key, merkle.KeyEncodingURL)
    86  		return kp, nil
    87  	}
    88  }
    89  
    90  // NewClient returns a new client.
    91  func NewClient(next rpcclient.Client, lc LightClient, opts ...Option) *Client {
    92  	c := &Client{
    93  		next: next,
    94  		lc:   lc,
    95  		prt:  merkle.DefaultProofRuntime(),
    96  	}
    97  	c.BaseService = *service.NewBaseService(nil, "Client", c)
    98  	for _, o := range opts {
    99  		o(c)
   100  	}
   101  	return c
   102  }
   103  
   104  func (c *Client) OnStart() error {
   105  	if !c.next.IsRunning() {
   106  		return c.next.Start()
   107  	}
   108  	return nil
   109  }
   110  
   111  func (c *Client) OnStop() {
   112  	if c.next.IsRunning() {
   113  		if err := c.next.Stop(); err != nil {
   114  			c.Logger.Error("Error stopping on next", "err", err)
   115  		}
   116  	}
   117  }
   118  
   119  func (c *Client) Status(ctx context.Context) (*ctypes.ResultStatus, error) {
   120  	return c.next.Status(ctx)
   121  }
   122  
   123  func (c *Client) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) {
   124  	return c.next.ABCIInfo(ctx)
   125  }
   126  
   127  // ABCIQuery requests proof by default.
   128  func (c *Client) ABCIQuery(ctx context.Context, path string, data cmtbytes.HexBytes) (*ctypes.ResultABCIQuery, error) {
   129  	return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions)
   130  }
   131  
   132  // ABCIQueryWithOptions returns an error if opts.Prove is false.
   133  func (c *Client) ABCIQueryWithOptions(ctx context.Context, path string, data cmtbytes.HexBytes,
   134  	opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
   135  
   136  	// always request the proof
   137  	opts.Prove = true
   138  
   139  	res, err := c.next.ABCIQueryWithOptions(ctx, path, data, opts)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	resp := res.Response
   144  
   145  	// Validate the response.
   146  	if resp.IsErr() {
   147  		return nil, fmt.Errorf("err response code: %v", resp.Code)
   148  	}
   149  	if len(resp.Key) == 0 {
   150  		return nil, errors.New("empty key")
   151  	}
   152  	if resp.ProofOps == nil || len(resp.ProofOps.Ops) == 0 {
   153  		return nil, errors.New("no proof ops")
   154  	}
   155  	if resp.Height <= 0 {
   156  		return nil, errNegOrZeroHeight
   157  	}
   158  
   159  	// Update the light client if we're behind.
   160  	// NOTE: AppHash for height H is in header H+1.
   161  	nextHeight := resp.Height + 1
   162  	l, err := c.updateLightClientIfNeededTo(ctx, &nextHeight)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	// Validate the value proof against the trusted header.
   168  	if resp.Value != nil {
   169  		// 1) build a Merkle key path from path and resp.Key
   170  		if c.keyPathFn == nil {
   171  			return nil, errors.New("please configure Client with KeyPathFn option")
   172  		}
   173  
   174  		kp, err := c.keyPathFn(path, resp.Key)
   175  		if err != nil {
   176  			return nil, fmt.Errorf("can't build merkle key path: %w", err)
   177  		}
   178  
   179  		// 2) verify value
   180  		err = c.prt.VerifyValue(resp.ProofOps, l.AppHash, kp.String(), resp.Value)
   181  		if err != nil {
   182  			return nil, fmt.Errorf("verify value proof: %w", err)
   183  		}
   184  	} else { // OR validate the absence proof against the trusted header.
   185  		err = c.prt.VerifyAbsence(resp.ProofOps, l.AppHash, string(resp.Key))
   186  		if err != nil {
   187  			return nil, fmt.Errorf("verify absence proof: %w", err)
   188  		}
   189  	}
   190  
   191  	return &ctypes.ResultABCIQuery{Response: resp}, nil
   192  }
   193  
   194  func (c *Client) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
   195  	return c.next.BroadcastTxCommit(ctx, tx)
   196  }
   197  
   198  func (c *Client) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
   199  	return c.next.BroadcastTxAsync(ctx, tx)
   200  }
   201  
   202  func (c *Client) BroadcastTxSync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
   203  	return c.next.BroadcastTxSync(ctx, tx)
   204  }
   205  
   206  func (c *Client) UnconfirmedTxs(ctx context.Context, limit *int) (*ctypes.ResultUnconfirmedTxs, error) {
   207  	return c.next.UnconfirmedTxs(ctx, limit)
   208  }
   209  
   210  func (c *Client) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) {
   211  	return c.next.NumUnconfirmedTxs(ctx)
   212  }
   213  
   214  func (c *Client) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) {
   215  	return c.next.CheckTx(ctx, tx)
   216  }
   217  
   218  func (c *Client) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) {
   219  	return c.next.NetInfo(ctx)
   220  }
   221  
   222  func (c *Client) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) {
   223  	return c.next.DumpConsensusState(ctx)
   224  }
   225  
   226  func (c *Client) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) {
   227  	return c.next.ConsensusState(ctx)
   228  }
   229  
   230  func (c *Client) ConsensusParams(ctx context.Context, height *int64) (*ctypes.ResultConsensusParams, error) {
   231  	res, err := c.next.ConsensusParams(ctx, height)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	// Validate res.
   237  	if err := res.ConsensusParams.ValidateBasic(); err != nil {
   238  		return nil, err
   239  	}
   240  	if res.BlockHeight <= 0 {
   241  		return nil, errNegOrZeroHeight
   242  	}
   243  
   244  	// Update the light client if we're behind.
   245  	l, err := c.updateLightClientIfNeededTo(ctx, &res.BlockHeight)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	// Verify hash.
   251  	if cH, tH := res.ConsensusParams.Hash(), l.ConsensusHash; !bytes.Equal(cH, tH) {
   252  		return nil, fmt.Errorf("params hash %X does not match trusted hash %X",
   253  			cH, tH)
   254  	}
   255  
   256  	return res, nil
   257  }
   258  
   259  func (c *Client) Health(ctx context.Context) (*ctypes.ResultHealth, error) {
   260  	return c.next.Health(ctx)
   261  }
   262  
   263  // BlockchainInfo calls rpcclient#BlockchainInfo and then verifies every header
   264  // returned.
   265  func (c *Client) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
   266  	res, err := c.next.BlockchainInfo(ctx, minHeight, maxHeight)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	// Validate res.
   272  	for i, meta := range res.BlockMetas {
   273  		if meta == nil {
   274  			return nil, fmt.Errorf("nil block meta %d", i)
   275  		}
   276  		if err := meta.ValidateBasic(); err != nil {
   277  			return nil, fmt.Errorf("invalid block meta %d: %w", i, err)
   278  		}
   279  	}
   280  
   281  	// Update the light client if we're behind.
   282  	if len(res.BlockMetas) > 0 {
   283  		lastHeight := res.BlockMetas[len(res.BlockMetas)-1].Header.Height
   284  		if _, err := c.updateLightClientIfNeededTo(ctx, &lastHeight); err != nil {
   285  			return nil, err
   286  		}
   287  	}
   288  
   289  	// Verify each of the BlockMetas.
   290  	for _, meta := range res.BlockMetas {
   291  		h, err := c.lc.TrustedLightBlock(meta.Header.Height)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("trusted header %d: %w", meta.Header.Height, err)
   294  		}
   295  		if bmH, tH := meta.Header.Hash(), h.Hash(); !bytes.Equal(bmH, tH) {
   296  			return nil, fmt.Errorf("block meta header %X does not match with trusted header %X",
   297  				bmH, tH)
   298  		}
   299  	}
   300  
   301  	return res, nil
   302  }
   303  
   304  func (c *Client) Genesis(ctx context.Context) (*ctypes.ResultGenesis, error) {
   305  	return c.next.Genesis(ctx)
   306  }
   307  
   308  func (c *Client) GenesisChunked(ctx context.Context, id uint) (*ctypes.ResultGenesisChunk, error) {
   309  	return c.next.GenesisChunked(ctx, id)
   310  }
   311  
   312  // Block calls rpcclient#Block and then verifies the result.
   313  func (c *Client) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) {
   314  	res, err := c.next.Block(ctx, height)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	// Validate res.
   320  	if err := res.BlockID.ValidateBasic(); err != nil {
   321  		return nil, err
   322  	}
   323  	if err := res.Block.ValidateBasic(); err != nil {
   324  		return nil, err
   325  	}
   326  	if bmH, bH := res.BlockID.Hash, res.Block.Hash(); !bytes.Equal(bmH, bH) {
   327  		return nil, fmt.Errorf("blockID %X does not match with block %X",
   328  			bmH, bH)
   329  	}
   330  
   331  	// Update the light client if we're behind.
   332  	l, err := c.updateLightClientIfNeededTo(ctx, &res.Block.Height)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	// Verify block.
   338  	if bH, tH := res.Block.Hash(), l.Hash(); !bytes.Equal(bH, tH) {
   339  		return nil, fmt.Errorf("block header %X does not match with trusted header %X",
   340  			bH, tH)
   341  	}
   342  
   343  	return res, nil
   344  }
   345  
   346  // BlockByHash calls rpcclient#BlockByHash and then verifies the result.
   347  func (c *Client) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
   348  	res, err := c.next.BlockByHash(ctx, hash)
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  
   353  	// Validate res.
   354  	if err := res.BlockID.ValidateBasic(); err != nil {
   355  		return nil, err
   356  	}
   357  	if err := res.Block.ValidateBasic(); err != nil {
   358  		return nil, err
   359  	}
   360  	if bmH, bH := res.BlockID.Hash, res.Block.Hash(); !bytes.Equal(bmH, bH) {
   361  		return nil, fmt.Errorf("blockID %X does not match with block %X",
   362  			bmH, bH)
   363  	}
   364  
   365  	// Update the light client if we're behind.
   366  	l, err := c.updateLightClientIfNeededTo(ctx, &res.Block.Height)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	// Verify block.
   372  	if bH, tH := res.Block.Hash(), l.Hash(); !bytes.Equal(bH, tH) {
   373  		return nil, fmt.Errorf("block header %X does not match with trusted header %X",
   374  			bH, tH)
   375  	}
   376  
   377  	return res, nil
   378  }
   379  
   380  // BlockResults returns the block results for the given height. If no height is
   381  // provided, the results of the block preceding the latest are returned.
   382  func (c *Client) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) {
   383  	var h int64
   384  	if height == nil {
   385  		res, err := c.next.Status(ctx)
   386  		if err != nil {
   387  			return nil, fmt.Errorf("can't get latest height: %w", err)
   388  		}
   389  		// Can't return the latest block results here because we won't be able to
   390  		// prove them. Return the results for the previous block instead.
   391  		h = res.SyncInfo.LatestBlockHeight - 1
   392  	} else {
   393  		h = *height
   394  	}
   395  
   396  	res, err := c.next.BlockResults(ctx, &h)
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  
   401  	// Validate res.
   402  	if res.Height <= 0 {
   403  		return nil, errNegOrZeroHeight
   404  	}
   405  
   406  	// Update the light client if we're behind.
   407  	nextHeight := h + 1
   408  	trustedBlock, err := c.updateLightClientIfNeededTo(ctx, &nextHeight)
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  
   413  	// proto-encode BeginBlock events
   414  	bbeBytes, err := proto.Marshal(&abci.ResponseBeginBlock{
   415  		Events: res.BeginBlockEvents,
   416  	})
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  
   421  	// Build a Merkle tree of proto-encoded DeliverTx results and get a hash.
   422  	results := types.NewResults(res.TxsResults)
   423  
   424  	// proto-encode EndBlock events.
   425  	ebeBytes, err := proto.Marshal(&abci.ResponseEndBlock{
   426  		Events: res.EndBlockEvents,
   427  	})
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	// Build a Merkle tree out of the above 3 binary slices.
   433  	rH := merkle.HashFromByteSlices([][]byte{bbeBytes, results.Hash(), ebeBytes})
   434  
   435  	// Verify block results.
   436  	if !bytes.Equal(rH, trustedBlock.LastResultsHash) {
   437  		return nil, fmt.Errorf("last results %X does not match with trusted last results %X",
   438  			rH, trustedBlock.LastResultsHash)
   439  	}
   440  
   441  	return res, nil
   442  }
   443  
   444  // Header fetches and verifies the header directly via the light client
   445  func (c *Client) Header(ctx context.Context, height *int64) (*ctypes.ResultHeader, error) {
   446  	lb, err := c.updateLightClientIfNeededTo(ctx, height)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  
   451  	return &ctypes.ResultHeader{Header: lb.Header}, nil
   452  }
   453  
   454  // HeaderByHash calls rpcclient#HeaderByHash and updates the client if it's falling behind.
   455  func (c *Client) HeaderByHash(ctx context.Context, hash cmtbytes.HexBytes) (*ctypes.ResultHeader, error) {
   456  	res, err := c.next.HeaderByHash(ctx, hash)
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	if err := res.Header.ValidateBasic(); err != nil {
   462  		return nil, err
   463  	}
   464  
   465  	lb, err := c.updateLightClientIfNeededTo(ctx, &res.Header.Height)
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  
   470  	if !bytes.Equal(lb.Header.Hash(), res.Header.Hash()) {
   471  		return nil, fmt.Errorf("primary header hash does not match trusted header hash. (%X != %X)",
   472  			lb.Header.Hash(), res.Header.Hash())
   473  	}
   474  
   475  	return res, nil
   476  }
   477  
   478  func (c *Client) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) {
   479  	// Update the light client if we're behind and retrieve the light block at the requested height
   480  	// or at the latest height if no height is provided.
   481  	l, err := c.updateLightClientIfNeededTo(ctx, height)
   482  	if err != nil {
   483  		return nil, err
   484  	}
   485  
   486  	return &ctypes.ResultCommit{
   487  		SignedHeader:    *l.SignedHeader,
   488  		CanonicalCommit: true,
   489  	}, nil
   490  }
   491  
   492  // Tx calls rpcclient#Tx method and then verifies the proof if such was
   493  // requested.
   494  func (c *Client) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
   495  	res, err := c.next.Tx(ctx, hash, prove)
   496  	if err != nil || !prove {
   497  		return res, err
   498  	}
   499  
   500  	// Validate res.
   501  	if res.Height <= 0 {
   502  		return nil, errNegOrZeroHeight
   503  	}
   504  
   505  	// Update the light client if we're behind.
   506  	l, err := c.updateLightClientIfNeededTo(ctx, &res.Height)
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	// Validate the proof.
   512  	return res, res.Proof.Validate(l.DataHash)
   513  }
   514  
   515  func (c *Client) TxSearch(
   516  	ctx context.Context,
   517  	query string,
   518  	prove bool,
   519  	page, perPage *int,
   520  	orderBy string,
   521  ) (*ctypes.ResultTxSearch, error) {
   522  	return c.next.TxSearch(ctx, query, prove, page, perPage, orderBy)
   523  }
   524  
   525  func (c *Client) BlockSearch(
   526  	ctx context.Context,
   527  	query string,
   528  	page, perPage *int,
   529  	orderBy string,
   530  ) (*ctypes.ResultBlockSearch, error) {
   531  	return c.next.BlockSearch(ctx, query, page, perPage, orderBy)
   532  }
   533  
   534  // Validators fetches and verifies validators.
   535  func (c *Client) Validators(
   536  	ctx context.Context,
   537  	height *int64,
   538  	pagePtr, perPagePtr *int,
   539  ) (*ctypes.ResultValidators, error) {
   540  
   541  	// Update the light client if we're behind and retrieve the light block at the
   542  	// requested height or at the latest height if no height is provided.
   543  	l, err := c.updateLightClientIfNeededTo(ctx, height)
   544  	if err != nil {
   545  		return nil, err
   546  	}
   547  
   548  	totalCount := len(l.ValidatorSet.Validators)
   549  	perPage := validatePerPage(perPagePtr)
   550  	page, err := validatePage(pagePtr, perPage, totalCount)
   551  	if err != nil {
   552  		return nil, err
   553  	}
   554  
   555  	skipCount := validateSkipCount(page, perPage)
   556  	v := l.ValidatorSet.Validators[skipCount : skipCount+cmtmath.MinInt(perPage, totalCount-skipCount)]
   557  
   558  	return &ctypes.ResultValidators{
   559  		BlockHeight: l.Height,
   560  		Validators:  v,
   561  		Count:       len(v),
   562  		Total:       totalCount}, nil
   563  }
   564  
   565  func (c *Client) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   566  	return c.next.BroadcastEvidence(ctx, ev)
   567  }
   568  
   569  func (c *Client) Subscribe(ctx context.Context, subscriber, query string,
   570  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   571  	return c.next.Subscribe(ctx, subscriber, query, outCapacity...)
   572  }
   573  
   574  func (c *Client) Unsubscribe(ctx context.Context, subscriber, query string) error {
   575  	return c.next.Unsubscribe(ctx, subscriber, query)
   576  }
   577  
   578  func (c *Client) UnsubscribeAll(ctx context.Context, subscriber string) error {
   579  	return c.next.UnsubscribeAll(ctx, subscriber)
   580  }
   581  
   582  func (c *Client) updateLightClientIfNeededTo(ctx context.Context, height *int64) (*types.LightBlock, error) {
   583  	var (
   584  		l   *types.LightBlock
   585  		err error
   586  	)
   587  	if height == nil {
   588  		l, err = c.lc.Update(ctx, time.Now())
   589  	} else {
   590  		l, err = c.lc.VerifyLightBlockAtHeight(ctx, *height, time.Now())
   591  	}
   592  	if err != nil {
   593  		return nil, fmt.Errorf("failed to update light client to %d: %w", *height, err)
   594  	}
   595  	return l, nil
   596  }
   597  
   598  func (c *Client) RegisterOpDecoder(typ string, dec merkle.OpDecoder) {
   599  	c.prt.RegisterOpDecoder(typ, dec)
   600  }
   601  
   602  // SubscribeWS subscribes for events using the given query and remote address as
   603  // a subscriber, but does not verify responses (UNSAFE)!
   604  // TODO: verify data
   605  func (c *Client) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) {
   606  	out, err := c.next.Subscribe(context.Background(), ctx.RemoteAddr(), query)
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  
   611  	go func() {
   612  		for {
   613  			select {
   614  			case resultEvent := <-out:
   615  				// We should have a switch here that performs a validation
   616  				// depending on the event's type.
   617  				ctx.WSConn.TryWriteRPCResponse(
   618  					rpctypes.NewRPCSuccessResponse(
   619  						rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", ctx.JSONReq.ID)),
   620  						resultEvent,
   621  					))
   622  			case <-c.Quit():
   623  				return
   624  			}
   625  		}
   626  	}()
   627  
   628  	return &ctypes.ResultSubscribe{}, nil
   629  }
   630  
   631  // UnsubscribeWS calls original client's Unsubscribe using remote address as a
   632  // subscriber.
   633  func (c *Client) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
   634  	err := c.next.Unsubscribe(context.Background(), ctx.RemoteAddr(), query)
   635  	if err != nil {
   636  		return nil, err
   637  	}
   638  	return &ctypes.ResultUnsubscribe{}, nil
   639  }
   640  
   641  // UnsubscribeAllWS calls original client's UnsubscribeAll using remote address
   642  // as a subscriber.
   643  func (c *Client) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
   644  	err := c.next.UnsubscribeAll(context.Background(), ctx.RemoteAddr())
   645  	if err != nil {
   646  		return nil, err
   647  	}
   648  	return &ctypes.ResultUnsubscribe{}, nil
   649  }
   650  
   651  // XXX: Copied from rpc/core/env.go
   652  const (
   653  	// see README
   654  	defaultPerPage = 30
   655  	maxPerPage     = 100
   656  )
   657  
   658  func validatePage(pagePtr *int, perPage, totalCount int) (int, error) {
   659  	if perPage < 1 {
   660  		panic(fmt.Sprintf("zero or negative perPage: %d", perPage))
   661  	}
   662  
   663  	if pagePtr == nil { // no page parameter
   664  		return 1, nil
   665  	}
   666  
   667  	pages := ((totalCount - 1) / perPage) + 1
   668  	if pages == 0 {
   669  		pages = 1 // one page (even if it's empty)
   670  	}
   671  	page := *pagePtr
   672  	if page <= 0 || page > pages {
   673  		return 1, fmt.Errorf("page should be within [1, %d] range, given %d", pages, page)
   674  	}
   675  
   676  	return page, nil
   677  }
   678  
   679  func validatePerPage(perPagePtr *int) int {
   680  	if perPagePtr == nil { // no per_page parameter
   681  		return defaultPerPage
   682  	}
   683  
   684  	perPage := *perPagePtr
   685  	if perPage < 1 {
   686  		return defaultPerPage
   687  	} else if perPage > maxPerPage {
   688  		return maxPerPage
   689  	}
   690  	return perPage
   691  }
   692  
   693  func validateSkipCount(page, perPage int) int {
   694  	skipCount := (page - 1) * perPage
   695  	if skipCount < 0 {
   696  		return 0
   697  	}
   698  
   699  	return skipCount
   700  }