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