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