github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/rpc/client/http/http.go (about) 1 package http 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 "strings" 8 "time" 9 10 "github.com/badrootd/celestia-core/libs/bytes" 11 cmtjson "github.com/badrootd/celestia-core/libs/json" 12 "github.com/badrootd/celestia-core/libs/log" 13 cmtpubsub "github.com/badrootd/celestia-core/libs/pubsub" 14 "github.com/badrootd/celestia-core/libs/service" 15 cmtsync "github.com/badrootd/celestia-core/libs/sync" 16 rpcclient "github.com/badrootd/celestia-core/rpc/client" 17 ctypes "github.com/badrootd/celestia-core/rpc/core/types" 18 jsonrpcclient "github.com/badrootd/celestia-core/rpc/jsonrpc/client" 19 "github.com/badrootd/celestia-core/types" 20 ) 21 22 /* 23 HTTP is a Client implementation that communicates with a CometBFT node over 24 JSON RPC and WebSockets. 25 26 This is the main implementation you probably want to use in production code. 27 There are other implementations when calling the CometBFT node in-process 28 (Local), or when you want to mock out the server for test code (mock). 29 30 You can subscribe for any event published by CometBFT using Subscribe method. 31 Note delivery is best-effort. If you don't read events fast enough or network is 32 slow, CometBFT might cancel the subscription. The client will attempt to 33 resubscribe (you don't need to do anything). It will keep trying every second 34 indefinitely until successful. 35 36 Request batching is available for JSON RPC requests over HTTP, which conforms to 37 the JSON RPC specification (https://www.jsonrpc.org/specification#batch). See 38 the example for more details. 39 40 Example: 41 42 c, err := New("http://192.168.1.10:26657", "/websocket") 43 if err != nil { 44 // handle error 45 } 46 47 // call Start/Stop if you're subscribing to events 48 err = c.Start() 49 if err != nil { 50 // handle error 51 } 52 defer c.Stop() 53 54 res, err := c.Status() 55 if err != nil { 56 // handle error 57 } 58 59 // handle result 60 */ 61 type HTTP struct { 62 remote string 63 rpc *jsonrpcclient.Client 64 65 *baseRPCClient 66 *WSEvents 67 } 68 69 // BatchHTTP provides the same interface as `HTTP`, but allows for batching of 70 // requests (as per https://www.jsonrpc.org/specification#batch). Do not 71 // instantiate directly - rather use the HTTP.NewBatch() method to create an 72 // instance of this struct. 73 // 74 // Batching of HTTP requests is thread-safe in the sense that multiple 75 // goroutines can each create their own batches and send them using the same 76 // HTTP client. Multiple goroutines could also enqueue transactions in a single 77 // batch, but ordering of transactions in the batch cannot be guaranteed in such 78 // an example. 79 type BatchHTTP struct { 80 rpcBatch *jsonrpcclient.RequestBatch 81 *baseRPCClient 82 } 83 84 // rpcClient is an internal interface to which our RPC clients (batch and 85 // non-batch) must conform. Acts as an additional code-level sanity check to 86 // make sure the implementations stay coherent. 87 type rpcClient interface { 88 rpcclient.ABCIClient 89 rpcclient.HistoryClient 90 rpcclient.NetworkClient 91 rpcclient.SignClient 92 rpcclient.StatusClient 93 } 94 95 // baseRPCClient implements the basic RPC method logic without the actual 96 // underlying RPC call functionality, which is provided by `caller`. 97 type baseRPCClient struct { 98 caller jsonrpcclient.Caller 99 } 100 101 var ( 102 _ rpcClient = (*HTTP)(nil) 103 _ rpcClient = (*BatchHTTP)(nil) 104 _ rpcClient = (*baseRPCClient)(nil) 105 ) 106 107 //----------------------------------------------------------------------------- 108 // HTTP 109 110 // New takes a remote endpoint in the form <protocol>://<host>:<port> and 111 // the websocket path (which always seems to be "/websocket") 112 // An error is returned on invalid remote. The function panics when remote is nil. 113 func New(remote, wsEndpoint string) (*HTTP, error) { 114 httpClient, err := jsonrpcclient.DefaultHTTPClient(remote) 115 if err != nil { 116 return nil, err 117 } 118 return NewWithClient(remote, wsEndpoint, httpClient) 119 } 120 121 // Create timeout enabled http client 122 func NewWithTimeout(remote, wsEndpoint string, timeout uint) (*HTTP, error) { 123 httpClient, err := jsonrpcclient.DefaultHTTPClient(remote) 124 if err != nil { 125 return nil, err 126 } 127 httpClient.Timeout = time.Duration(timeout) * time.Second 128 return NewWithClient(remote, wsEndpoint, httpClient) 129 } 130 131 // NewWithClient allows for setting a custom http client (See New). 132 // An error is returned on invalid remote. The function panics when remote is nil. 133 func NewWithClient(remote, wsEndpoint string, client *http.Client) (*HTTP, error) { 134 if client == nil { 135 panic("nil http.Client provided") 136 } 137 138 rc, err := jsonrpcclient.NewWithHTTPClient(remote, client) 139 if err != nil { 140 return nil, err 141 } 142 143 wsEvents, err := newWSEvents(remote, wsEndpoint) 144 if err != nil { 145 return nil, err 146 } 147 148 httpClient := &HTTP{ 149 rpc: rc, 150 remote: remote, 151 baseRPCClient: &baseRPCClient{caller: rc}, 152 WSEvents: wsEvents, 153 } 154 155 return httpClient, nil 156 } 157 158 var _ rpcclient.Client = (*HTTP)(nil) 159 160 // SetLogger sets a logger. 161 func (c *HTTP) SetLogger(l log.Logger) { 162 c.WSEvents.SetLogger(l) 163 } 164 165 // Remote returns the remote network address in a string form. 166 func (c *HTTP) Remote() string { 167 return c.remote 168 } 169 170 // NewBatch creates a new batch client for this HTTP client. 171 func (c *HTTP) NewBatch() *BatchHTTP { 172 rpcBatch := c.rpc.NewRequestBatch() 173 return &BatchHTTP{ 174 rpcBatch: rpcBatch, 175 baseRPCClient: &baseRPCClient{ 176 caller: rpcBatch, 177 }, 178 } 179 } 180 181 //----------------------------------------------------------------------------- 182 // BatchHTTP 183 184 // Send is a convenience function for an HTTP batch that will trigger the 185 // compilation of the batched requests and send them off using the client as a 186 // single request. On success, this returns a list of the deserialized results 187 // from each request in the sent batch. 188 func (b *BatchHTTP) Send(ctx context.Context) ([]interface{}, error) { 189 return b.rpcBatch.Send(ctx) 190 } 191 192 // Clear will empty out this batch of requests and return the number of requests 193 // that were cleared out. 194 func (b *BatchHTTP) Clear() int { 195 return b.rpcBatch.Clear() 196 } 197 198 // Count returns the number of enqueued requests waiting to be sent. 199 func (b *BatchHTTP) Count() int { 200 return b.rpcBatch.Count() 201 } 202 203 //----------------------------------------------------------------------------- 204 // baseRPCClient 205 206 func (c *baseRPCClient) Status(ctx context.Context) (*ctypes.ResultStatus, error) { 207 result := new(ctypes.ResultStatus) 208 _, err := c.caller.Call(ctx, "status", map[string]interface{}{}, result) 209 if err != nil { 210 return nil, err 211 } 212 213 return result, nil 214 } 215 216 func (c *baseRPCClient) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) { 217 result := new(ctypes.ResultABCIInfo) 218 _, err := c.caller.Call(ctx, "abci_info", map[string]interface{}{}, result) 219 if err != nil { 220 return nil, err 221 } 222 223 return result, nil 224 } 225 226 func (c *baseRPCClient) ABCIQuery( 227 ctx context.Context, 228 path string, 229 data bytes.HexBytes, 230 ) (*ctypes.ResultABCIQuery, error) { 231 return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) 232 } 233 234 func (c *baseRPCClient) ABCIQueryWithOptions( 235 ctx context.Context, 236 path string, 237 data bytes.HexBytes, 238 opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 239 result := new(ctypes.ResultABCIQuery) 240 _, err := c.caller.Call(ctx, "abci_query", 241 map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}, 242 result) 243 if err != nil { 244 return nil, err 245 } 246 247 return result, nil 248 } 249 250 func (c *baseRPCClient) BroadcastTxCommit( 251 ctx context.Context, 252 tx types.Tx, 253 ) (*ctypes.ResultBroadcastTxCommit, error) { 254 result := new(ctypes.ResultBroadcastTxCommit) 255 _, err := c.caller.Call(ctx, "broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) 256 if err != nil { 257 return nil, err 258 } 259 return result, nil 260 } 261 262 func (c *baseRPCClient) BroadcastTxAsync( 263 ctx context.Context, 264 tx types.Tx, 265 ) (*ctypes.ResultBroadcastTx, error) { 266 return c.broadcastTX(ctx, "broadcast_tx_async", tx) 267 } 268 269 func (c *baseRPCClient) BroadcastTxSync( 270 ctx context.Context, 271 tx types.Tx, 272 ) (*ctypes.ResultBroadcastTx, error) { 273 return c.broadcastTX(ctx, "broadcast_tx_sync", tx) 274 } 275 276 func (c *baseRPCClient) broadcastTX( 277 ctx context.Context, 278 route string, 279 tx types.Tx, 280 ) (*ctypes.ResultBroadcastTx, error) { 281 result := new(ctypes.ResultBroadcastTx) 282 _, err := c.caller.Call(ctx, route, map[string]interface{}{"tx": tx}, result) 283 if err != nil { 284 return nil, err 285 } 286 return result, nil 287 } 288 289 func (c *baseRPCClient) UnconfirmedTxs( 290 ctx context.Context, 291 limit *int, 292 ) (*ctypes.ResultUnconfirmedTxs, error) { 293 result := new(ctypes.ResultUnconfirmedTxs) 294 params := make(map[string]interface{}) 295 if limit != nil { 296 params["limit"] = limit 297 } 298 _, err := c.caller.Call(ctx, "unconfirmed_txs", params, result) 299 if err != nil { 300 return nil, err 301 } 302 return result, nil 303 } 304 305 func (c *baseRPCClient) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) { 306 result := new(ctypes.ResultUnconfirmedTxs) 307 _, err := c.caller.Call(ctx, "num_unconfirmed_txs", map[string]interface{}{}, result) 308 if err != nil { 309 return nil, err 310 } 311 return result, nil 312 } 313 314 func (c *baseRPCClient) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) { 315 result := new(ctypes.ResultCheckTx) 316 _, err := c.caller.Call(ctx, "check_tx", map[string]interface{}{"tx": tx}, result) 317 if err != nil { 318 return nil, err 319 } 320 return result, nil 321 } 322 323 func (c *baseRPCClient) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) { 324 result := new(ctypes.ResultNetInfo) 325 _, err := c.caller.Call(ctx, "net_info", map[string]interface{}{}, result) 326 if err != nil { 327 return nil, err 328 } 329 return result, nil 330 } 331 332 func (c *baseRPCClient) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) { 333 result := new(ctypes.ResultDumpConsensusState) 334 _, err := c.caller.Call(ctx, "dump_consensus_state", map[string]interface{}{}, result) 335 if err != nil { 336 return nil, err 337 } 338 return result, nil 339 } 340 341 func (c *baseRPCClient) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) { 342 result := new(ctypes.ResultConsensusState) 343 _, err := c.caller.Call(ctx, "consensus_state", map[string]interface{}{}, result) 344 if err != nil { 345 return nil, err 346 } 347 return result, nil 348 } 349 350 func (c *baseRPCClient) ConsensusParams( 351 ctx context.Context, 352 height *int64, 353 ) (*ctypes.ResultConsensusParams, error) { 354 result := new(ctypes.ResultConsensusParams) 355 params := make(map[string]interface{}) 356 if height != nil { 357 params["height"] = height 358 } 359 _, err := c.caller.Call(ctx, "consensus_params", params, result) 360 if err != nil { 361 return nil, err 362 } 363 return result, nil 364 } 365 366 func (c *baseRPCClient) Health(ctx context.Context) (*ctypes.ResultHealth, error) { 367 result := new(ctypes.ResultHealth) 368 _, err := c.caller.Call(ctx, "health", map[string]interface{}{}, result) 369 if err != nil { 370 return nil, err 371 } 372 return result, nil 373 } 374 375 func (c *baseRPCClient) BlockchainInfo( 376 ctx context.Context, 377 minHeight, 378 maxHeight int64, 379 ) (*ctypes.ResultBlockchainInfo, error) { 380 result := new(ctypes.ResultBlockchainInfo) 381 _, err := c.caller.Call(ctx, "blockchain", 382 map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}, 383 result) 384 if err != nil { 385 return nil, err 386 } 387 return result, nil 388 } 389 390 func (c *baseRPCClient) Genesis(ctx context.Context) (*ctypes.ResultGenesis, error) { 391 result := new(ctypes.ResultGenesis) 392 _, err := c.caller.Call(ctx, "genesis", map[string]interface{}{}, result) 393 if err != nil { 394 return nil, err 395 } 396 return result, nil 397 } 398 399 func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*ctypes.ResultGenesisChunk, error) { 400 result := new(ctypes.ResultGenesisChunk) 401 _, err := c.caller.Call(ctx, "genesis_chunked", map[string]interface{}{"chunk": id}, result) 402 if err != nil { 403 return nil, err 404 } 405 return result, nil 406 } 407 408 func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) { 409 result := new(ctypes.ResultBlock) 410 params := make(map[string]interface{}) 411 if height != nil { 412 params["height"] = height 413 } 414 _, err := c.caller.Call(ctx, "block", params, result) 415 if err != nil { 416 return nil, err 417 } 418 return result, nil 419 } 420 421 func (c *baseRPCClient) SignedBlock(ctx context.Context, height *int64) (*ctypes.ResultSignedBlock, error) { 422 result := new(ctypes.ResultSignedBlock) 423 params := make(map[string]interface{}) 424 if height != nil { 425 params["height"] = height 426 } 427 _, err := c.caller.Call(ctx, "signed_block", params, result) 428 if err != nil { 429 return nil, err 430 } 431 return result, nil 432 } 433 434 func (c *baseRPCClient) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) { 435 result := new(ctypes.ResultBlock) 436 params := map[string]interface{}{ 437 "hash": hash, 438 } 439 _, err := c.caller.Call(ctx, "block_by_hash", params, result) 440 if err != nil { 441 return nil, err 442 } 443 return result, nil 444 } 445 446 func (c *baseRPCClient) BlockResults( 447 ctx context.Context, 448 height *int64, 449 ) (*ctypes.ResultBlockResults, error) { 450 result := new(ctypes.ResultBlockResults) 451 params := make(map[string]interface{}) 452 if height != nil { 453 params["height"] = height 454 } 455 _, err := c.caller.Call(ctx, "block_results", params, result) 456 if err != nil { 457 return nil, err 458 } 459 return result, nil 460 } 461 462 func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*ctypes.ResultHeader, error) { 463 result := new(ctypes.ResultHeader) 464 params := make(map[string]interface{}) 465 if height != nil { 466 params["height"] = height 467 } 468 _, err := c.caller.Call(ctx, "header", params, result) 469 if err != nil { 470 return nil, err 471 } 472 return result, nil 473 } 474 475 func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*ctypes.ResultHeader, error) { 476 result := new(ctypes.ResultHeader) 477 params := map[string]interface{}{ 478 "hash": hash, 479 } 480 _, err := c.caller.Call(ctx, "header_by_hash", params, result) 481 if err != nil { 482 return nil, err 483 } 484 return result, nil 485 } 486 487 func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) { 488 result := new(ctypes.ResultCommit) 489 params := make(map[string]interface{}) 490 if height != nil { 491 params["height"] = height 492 } 493 _, err := c.caller.Call(ctx, "commit", params, result) 494 if err != nil { 495 return nil, err 496 } 497 return result, nil 498 } 499 500 func (c *baseRPCClient) DataCommitment( 501 ctx context.Context, 502 start uint64, 503 end uint64, 504 ) (*ctypes.ResultDataCommitment, error) { 505 result := new(ctypes.ResultDataCommitment) 506 params := map[string]interface{}{ 507 "start": start, 508 "end": end, 509 } 510 511 _, err := c.caller.Call(ctx, "data_commitment", params, result) 512 if err != nil { 513 return nil, err 514 } 515 516 return result, nil 517 } 518 519 func (c *baseRPCClient) DataRootInclusionProof( 520 ctx context.Context, 521 height uint64, 522 start uint64, 523 end uint64, 524 ) (*ctypes.ResultDataRootInclusionProof, error) { 525 result := new(ctypes.ResultDataRootInclusionProof) 526 params := map[string]interface{}{ 527 "height": height, 528 "start": start, 529 "end": end, 530 } 531 532 _, err := c.caller.Call(ctx, "data_root_inclusion_proof", params, result) 533 if err != nil { 534 return nil, err 535 } 536 537 return result, nil 538 } 539 540 func (c *baseRPCClient) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { 541 result := new(ctypes.ResultTx) 542 params := map[string]interface{}{ 543 "hash": hash, 544 "prove": prove, 545 } 546 _, err := c.caller.Call(ctx, "tx", params, result) 547 if err != nil { 548 return nil, err 549 } 550 return result, nil 551 } 552 553 func (c *baseRPCClient) ProveShares( 554 ctx context.Context, 555 height uint64, 556 startShare uint64, 557 endShare uint64, 558 ) (types.ShareProof, error) { 559 result := new(types.ShareProof) 560 params := map[string]interface{}{ 561 "height": height, 562 "startShare": startShare, 563 "endShare": endShare, 564 } 565 _, err := c.caller.Call(ctx, "prove_shares", params, result) 566 if err != nil { 567 return types.ShareProof{}, err 568 } 569 return *result, nil 570 } 571 572 func (c *baseRPCClient) TxSearch( 573 ctx context.Context, 574 query string, 575 prove bool, 576 page, 577 perPage *int, 578 orderBy string, 579 ) (*ctypes.ResultTxSearch, error) { 580 581 result := new(ctypes.ResultTxSearch) 582 params := map[string]interface{}{ 583 "query": query, 584 "prove": prove, 585 "order_by": orderBy, 586 } 587 588 if page != nil { 589 params["page"] = page 590 } 591 if perPage != nil { 592 params["per_page"] = perPage 593 } 594 595 _, err := c.caller.Call(ctx, "tx_search", params, result) 596 if err != nil { 597 return nil, err 598 } 599 600 return result, nil 601 } 602 603 func (c *baseRPCClient) BlockSearch( 604 ctx context.Context, 605 query string, 606 page, perPage *int, 607 orderBy string, 608 ) (*ctypes.ResultBlockSearch, error) { 609 610 result := new(ctypes.ResultBlockSearch) 611 params := map[string]interface{}{ 612 "query": query, 613 "order_by": orderBy, 614 } 615 616 if page != nil { 617 params["page"] = page 618 } 619 if perPage != nil { 620 params["per_page"] = perPage 621 } 622 623 _, err := c.caller.Call(ctx, "block_search", params, result) 624 if err != nil { 625 return nil, err 626 } 627 628 return result, nil 629 } 630 631 func (c *baseRPCClient) Validators( 632 ctx context.Context, 633 height *int64, 634 page, 635 perPage *int, 636 ) (*ctypes.ResultValidators, error) { 637 result := new(ctypes.ResultValidators) 638 params := make(map[string]interface{}) 639 if page != nil { 640 params["page"] = page 641 } 642 if perPage != nil { 643 params["per_page"] = perPage 644 } 645 if height != nil { 646 params["height"] = height 647 } 648 _, err := c.caller.Call(ctx, "validators", params, result) 649 if err != nil { 650 return nil, err 651 } 652 return result, nil 653 } 654 655 func (c *baseRPCClient) BroadcastEvidence( 656 ctx context.Context, 657 ev types.Evidence, 658 ) (*ctypes.ResultBroadcastEvidence, error) { 659 result := new(ctypes.ResultBroadcastEvidence) 660 _, err := c.caller.Call(ctx, "broadcast_evidence", map[string]interface{}{"evidence": ev}, result) 661 if err != nil { 662 return nil, err 663 } 664 return result, nil 665 } 666 667 //----------------------------------------------------------------------------- 668 // WSEvents 669 670 var errNotRunning = errors.New("client is not running. Use .Start() method to start") 671 672 // WSEvents is a wrapper around WSClient, which implements EventsClient. 673 type WSEvents struct { 674 service.BaseService 675 remote string 676 endpoint string 677 ws *jsonrpcclient.WSClient 678 679 mtx cmtsync.RWMutex 680 subscriptions map[string]chan ctypes.ResultEvent // query -> chan 681 } 682 683 func newWSEvents(remote, endpoint string) (*WSEvents, error) { 684 w := &WSEvents{ 685 endpoint: endpoint, 686 remote: remote, 687 subscriptions: make(map[string]chan ctypes.ResultEvent), 688 } 689 w.BaseService = *service.NewBaseService(nil, "WSEvents", w) 690 691 var err error 692 w.ws, err = jsonrpcclient.NewWS(w.remote, w.endpoint, jsonrpcclient.OnReconnect(func() { 693 // resubscribe immediately 694 w.redoSubscriptionsAfter(0 * time.Second) 695 })) 696 if err != nil { 697 return nil, err 698 } 699 w.ws.SetLogger(w.Logger) 700 701 return w, nil 702 } 703 704 // OnStart implements service.Service by starting WSClient and event loop. 705 func (w *WSEvents) OnStart() error { 706 if err := w.ws.Start(); err != nil { 707 return err 708 } 709 710 go w.eventListener() 711 712 return nil 713 } 714 715 // OnStop implements service.Service by stopping WSClient. 716 func (w *WSEvents) OnStop() { 717 if err := w.ws.Stop(); err != nil { 718 w.Logger.Error("Can't stop ws client", "err", err) 719 } 720 } 721 722 // Subscribe implements EventsClient by using WSClient to subscribe given 723 // subscriber to query. By default, returns a channel with cap=1. Error is 724 // returned if it fails to subscribe. 725 // 726 // Channel is never closed to prevent clients from seeing an erroneous event. 727 // 728 // It returns an error if WSEvents is not running. 729 func (w *WSEvents) Subscribe(ctx context.Context, subscriber, query string, 730 outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { 731 732 if !w.IsRunning() { 733 return nil, errNotRunning 734 } 735 736 if err := w.ws.Subscribe(ctx, query); err != nil { 737 return nil, err 738 } 739 740 outCap := 1 741 if len(outCapacity) > 0 { 742 outCap = outCapacity[0] 743 } 744 745 outc := make(chan ctypes.ResultEvent, outCap) 746 w.mtx.Lock() 747 // subscriber param is ignored because CometBFT will override it with 748 // remote IP anyway. 749 w.subscriptions[query] = outc 750 w.mtx.Unlock() 751 752 return outc, nil 753 } 754 755 // Unsubscribe implements EventsClient by using WSClient to unsubscribe given 756 // subscriber from query. 757 // 758 // It returns an error if WSEvents is not running. 759 func (w *WSEvents) Unsubscribe(ctx context.Context, subscriber, query string) error { 760 if !w.IsRunning() { 761 return errNotRunning 762 } 763 764 if err := w.ws.Unsubscribe(ctx, query); err != nil { 765 return err 766 } 767 768 w.mtx.Lock() 769 _, ok := w.subscriptions[query] 770 if ok { 771 delete(w.subscriptions, query) 772 } 773 w.mtx.Unlock() 774 775 return nil 776 } 777 778 // UnsubscribeAll implements EventsClient by using WSClient to unsubscribe 779 // given subscriber from all the queries. 780 // 781 // It returns an error if WSEvents is not running. 782 func (w *WSEvents) UnsubscribeAll(ctx context.Context, subscriber string) error { 783 if !w.IsRunning() { 784 return errNotRunning 785 } 786 787 if err := w.ws.UnsubscribeAll(ctx); err != nil { 788 return err 789 } 790 791 w.mtx.Lock() 792 w.subscriptions = make(map[string]chan ctypes.ResultEvent) 793 w.mtx.Unlock() 794 795 return nil 796 } 797 798 // After being reconnected, it is necessary to redo subscription to server 799 // otherwise no data will be automatically received. 800 func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { 801 time.Sleep(d) 802 803 w.mtx.RLock() 804 defer w.mtx.RUnlock() 805 for q := range w.subscriptions { 806 err := w.ws.Subscribe(context.Background(), q) 807 if err != nil { 808 w.Logger.Error("Failed to resubscribe", "err", err) 809 } 810 } 811 } 812 813 func isErrAlreadySubscribed(err error) bool { 814 return strings.Contains(err.Error(), cmtpubsub.ErrAlreadySubscribed.Error()) 815 } 816 817 func (w *WSEvents) eventListener() { 818 for { 819 select { 820 case resp, ok := <-w.ws.ResponsesCh: 821 if !ok { 822 return 823 } 824 825 if resp.Error != nil { 826 w.Logger.Error("WS error", "err", resp.Error.Error()) 827 // Error can be ErrAlreadySubscribed or max client (subscriptions per 828 // client) reached or CometBFT exited. 829 // We can ignore ErrAlreadySubscribed, but need to retry in other 830 // cases. 831 if !isErrAlreadySubscribed(resp.Error) { 832 // Resubscribe after 1 second to give CometBFT time to restart (if 833 // crashed). 834 w.redoSubscriptionsAfter(1 * time.Second) 835 } 836 continue 837 } 838 839 result := new(ctypes.ResultEvent) 840 err := cmtjson.Unmarshal(resp.Result, result) 841 if err != nil { 842 w.Logger.Error("failed to unmarshal response", "err", err) 843 continue 844 } 845 846 w.mtx.RLock() 847 if out, ok := w.subscriptions[result.Query]; ok { 848 if cap(out) == 0 { 849 out <- *result 850 } else { 851 select { 852 case out <- *result: 853 default: 854 w.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) 855 } 856 } 857 } 858 w.mtx.RUnlock() 859 case <-w.Quit(): 860 return 861 } 862 } 863 }