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