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