github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/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/nibiru-cometbft/libs/bytes" 11 cmtjson "github.com/badrootd/nibiru-cometbft/libs/json" 12 "github.com/badrootd/nibiru-cometbft/libs/log" 13 cmtpubsub "github.com/badrootd/nibiru-cometbft/libs/pubsub" 14 "github.com/badrootd/nibiru-cometbft/libs/service" 15 cmtsync "github.com/badrootd/nibiru-cometbft/libs/sync" 16 rpcclient "github.com/badrootd/nibiru-cometbft/rpc/client" 17 ctypes "github.com/badrootd/nibiru-cometbft/rpc/core/types" 18 jsonrpcclient "github.com/badrootd/nibiru-cometbft/rpc/jsonrpc/client" 19 "github.com/badrootd/nibiru-cometbft/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) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) { 422 result := new(ctypes.ResultBlock) 423 params := map[string]interface{}{ 424 "hash": hash, 425 } 426 _, err := c.caller.Call(ctx, "block_by_hash", params, result) 427 if err != nil { 428 return nil, err 429 } 430 return result, nil 431 } 432 433 func (c *baseRPCClient) BlockResults( 434 ctx context.Context, 435 height *int64, 436 ) (*ctypes.ResultBlockResults, error) { 437 result := new(ctypes.ResultBlockResults) 438 params := make(map[string]interface{}) 439 if height != nil { 440 params["height"] = height 441 } 442 _, err := c.caller.Call(ctx, "block_results", params, result) 443 if err != nil { 444 return nil, err 445 } 446 return result, nil 447 } 448 449 func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*ctypes.ResultHeader, error) { 450 result := new(ctypes.ResultHeader) 451 params := make(map[string]interface{}) 452 if height != nil { 453 params["height"] = height 454 } 455 _, err := c.caller.Call(ctx, "header", params, result) 456 if err != nil { 457 return nil, err 458 } 459 return result, nil 460 } 461 462 func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*ctypes.ResultHeader, error) { 463 result := new(ctypes.ResultHeader) 464 params := map[string]interface{}{ 465 "hash": hash, 466 } 467 _, err := c.caller.Call(ctx, "header_by_hash", params, result) 468 if err != nil { 469 return nil, err 470 } 471 return result, nil 472 } 473 474 func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) { 475 result := new(ctypes.ResultCommit) 476 params := make(map[string]interface{}) 477 if height != nil { 478 params["height"] = height 479 } 480 _, err := c.caller.Call(ctx, "commit", params, result) 481 if err != nil { 482 return nil, err 483 } 484 return result, nil 485 } 486 487 func (c *baseRPCClient) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { 488 result := new(ctypes.ResultTx) 489 params := map[string]interface{}{ 490 "hash": hash, 491 "prove": prove, 492 } 493 _, err := c.caller.Call(ctx, "tx", params, result) 494 if err != nil { 495 return nil, err 496 } 497 return result, nil 498 } 499 500 func (c *baseRPCClient) TxSearch( 501 ctx context.Context, 502 query string, 503 prove bool, 504 page, 505 perPage *int, 506 orderBy string, 507 ) (*ctypes.ResultTxSearch, error) { 508 509 result := new(ctypes.ResultTxSearch) 510 params := map[string]interface{}{ 511 "query": query, 512 "prove": prove, 513 "order_by": orderBy, 514 } 515 516 if page != nil { 517 params["page"] = page 518 } 519 if perPage != nil { 520 params["per_page"] = perPage 521 } 522 523 _, err := c.caller.Call(ctx, "tx_search", params, result) 524 if err != nil { 525 return nil, err 526 } 527 528 return result, nil 529 } 530 531 func (c *baseRPCClient) BlockSearch( 532 ctx context.Context, 533 query string, 534 page, perPage *int, 535 orderBy string, 536 ) (*ctypes.ResultBlockSearch, error) { 537 538 result := new(ctypes.ResultBlockSearch) 539 params := map[string]interface{}{ 540 "query": query, 541 "order_by": orderBy, 542 } 543 544 if page != nil { 545 params["page"] = page 546 } 547 if perPage != nil { 548 params["per_page"] = perPage 549 } 550 551 _, err := c.caller.Call(ctx, "block_search", params, result) 552 if err != nil { 553 return nil, err 554 } 555 556 return result, nil 557 } 558 559 func (c *baseRPCClient) Validators( 560 ctx context.Context, 561 height *int64, 562 page, 563 perPage *int, 564 ) (*ctypes.ResultValidators, error) { 565 result := new(ctypes.ResultValidators) 566 params := make(map[string]interface{}) 567 if page != nil { 568 params["page"] = page 569 } 570 if perPage != nil { 571 params["per_page"] = perPage 572 } 573 if height != nil { 574 params["height"] = height 575 } 576 _, err := c.caller.Call(ctx, "validators", params, result) 577 if err != nil { 578 return nil, err 579 } 580 return result, nil 581 } 582 583 func (c *baseRPCClient) BroadcastEvidence( 584 ctx context.Context, 585 ev types.Evidence, 586 ) (*ctypes.ResultBroadcastEvidence, error) { 587 result := new(ctypes.ResultBroadcastEvidence) 588 _, err := c.caller.Call(ctx, "broadcast_evidence", map[string]interface{}{"evidence": ev}, result) 589 if err != nil { 590 return nil, err 591 } 592 return result, nil 593 } 594 595 //----------------------------------------------------------------------------- 596 // WSEvents 597 598 var errNotRunning = errors.New("client is not running. Use .Start() method to start") 599 600 // WSEvents is a wrapper around WSClient, which implements EventsClient. 601 type WSEvents struct { 602 service.BaseService 603 remote string 604 endpoint string 605 ws *jsonrpcclient.WSClient 606 607 mtx cmtsync.RWMutex 608 subscriptions map[string]chan ctypes.ResultEvent // query -> chan 609 } 610 611 func newWSEvents(remote, endpoint string) (*WSEvents, error) { 612 w := &WSEvents{ 613 endpoint: endpoint, 614 remote: remote, 615 subscriptions: make(map[string]chan ctypes.ResultEvent), 616 } 617 w.BaseService = *service.NewBaseService(nil, "WSEvents", w) 618 619 var err error 620 w.ws, err = jsonrpcclient.NewWS(w.remote, w.endpoint, jsonrpcclient.OnReconnect(func() { 621 // resubscribe immediately 622 w.redoSubscriptionsAfter(0 * time.Second) 623 })) 624 if err != nil { 625 return nil, err 626 } 627 w.ws.SetLogger(w.Logger) 628 629 return w, nil 630 } 631 632 // OnStart implements service.Service by starting WSClient and event loop. 633 func (w *WSEvents) OnStart() error { 634 if err := w.ws.Start(); err != nil { 635 return err 636 } 637 638 go w.eventListener() 639 640 return nil 641 } 642 643 // OnStop implements service.Service by stopping WSClient. 644 func (w *WSEvents) OnStop() { 645 if err := w.ws.Stop(); err != nil { 646 w.Logger.Error("Can't stop ws client", "err", err) 647 } 648 } 649 650 // Subscribe implements EventsClient by using WSClient to subscribe given 651 // subscriber to query. By default, returns a channel with cap=1. Error is 652 // returned if it fails to subscribe. 653 // 654 // Channel is never closed to prevent clients from seeing an erroneous event. 655 // 656 // It returns an error if WSEvents is not running. 657 func (w *WSEvents) Subscribe(ctx context.Context, subscriber, query string, 658 outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { 659 660 if !w.IsRunning() { 661 return nil, errNotRunning 662 } 663 664 if err := w.ws.Subscribe(ctx, query); err != nil { 665 return nil, err 666 } 667 668 outCap := 1 669 if len(outCapacity) > 0 { 670 outCap = outCapacity[0] 671 } 672 673 outc := make(chan ctypes.ResultEvent, outCap) 674 w.mtx.Lock() 675 // subscriber param is ignored because CometBFT will override it with 676 // remote IP anyway. 677 w.subscriptions[query] = outc 678 w.mtx.Unlock() 679 680 return outc, nil 681 } 682 683 // Unsubscribe implements EventsClient by using WSClient to unsubscribe given 684 // subscriber from query. 685 // 686 // It returns an error if WSEvents is not running. 687 func (w *WSEvents) Unsubscribe(ctx context.Context, subscriber, query string) error { 688 if !w.IsRunning() { 689 return errNotRunning 690 } 691 692 if err := w.ws.Unsubscribe(ctx, query); err != nil { 693 return err 694 } 695 696 w.mtx.Lock() 697 _, ok := w.subscriptions[query] 698 if ok { 699 delete(w.subscriptions, query) 700 } 701 w.mtx.Unlock() 702 703 return nil 704 } 705 706 // UnsubscribeAll implements EventsClient by using WSClient to unsubscribe 707 // given subscriber from all the queries. 708 // 709 // It returns an error if WSEvents is not running. 710 func (w *WSEvents) UnsubscribeAll(ctx context.Context, subscriber string) error { 711 if !w.IsRunning() { 712 return errNotRunning 713 } 714 715 if err := w.ws.UnsubscribeAll(ctx); err != nil { 716 return err 717 } 718 719 w.mtx.Lock() 720 w.subscriptions = make(map[string]chan ctypes.ResultEvent) 721 w.mtx.Unlock() 722 723 return nil 724 } 725 726 // After being reconnected, it is necessary to redo subscription to server 727 // otherwise no data will be automatically received. 728 func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { 729 time.Sleep(d) 730 731 w.mtx.RLock() 732 defer w.mtx.RUnlock() 733 for q := range w.subscriptions { 734 err := w.ws.Subscribe(context.Background(), q) 735 if err != nil { 736 w.Logger.Error("Failed to resubscribe", "err", err) 737 } 738 } 739 } 740 741 func isErrAlreadySubscribed(err error) bool { 742 return strings.Contains(err.Error(), cmtpubsub.ErrAlreadySubscribed.Error()) 743 } 744 745 func (w *WSEvents) eventListener() { 746 for { 747 select { 748 case resp, ok := <-w.ws.ResponsesCh: 749 if !ok { 750 return 751 } 752 753 if resp.Error != nil { 754 w.Logger.Error("WS error", "err", resp.Error.Error()) 755 // Error can be ErrAlreadySubscribed or max client (subscriptions per 756 // client) reached or CometBFT exited. 757 // We can ignore ErrAlreadySubscribed, but need to retry in other 758 // cases. 759 if !isErrAlreadySubscribed(resp.Error) { 760 // Resubscribe after 1 second to give CometBFT time to restart (if 761 // crashed). 762 w.redoSubscriptionsAfter(1 * time.Second) 763 } 764 continue 765 } 766 767 result := new(ctypes.ResultEvent) 768 err := cmtjson.Unmarshal(resp.Result, result) 769 if err != nil { 770 w.Logger.Error("failed to unmarshal response", "err", err) 771 continue 772 } 773 774 w.mtx.RLock() 775 if out, ok := w.subscriptions[result.Query]; ok { 776 if cap(out) == 0 { 777 out <- *result 778 } else { 779 select { 780 case out <- *result: 781 default: 782 w.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) 783 } 784 } 785 } 786 w.mtx.RUnlock() 787 case <-w.Quit(): 788 return 789 } 790 } 791 }