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