github.com/fiagdao/tendermint@v0.32.11-0.20220824195748-2087fcc480c1/rpc/core/events.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/pkg/errors"
     8  
     9  	tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
    10  	tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
    11  	ctypes "github.com/tendermint/tendermint/rpc/core/types"
    12  	rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
    13  )
    14  
    15  const (
    16  	// Buffer on the Tendermint (server) side to allow some slowness in clients.
    17  	subBufferSize = 100
    18  )
    19  
    20  // Subscribe for events via WebSocket.
    21  // More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe
    22  func Subscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) {
    23  	addr := ctx.RemoteAddr()
    24  
    25  	if env.EventBus.NumClients() >= env.Config.MaxSubscriptionClients {
    26  		return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients)
    27  	} else if env.EventBus.NumClientSubscriptions(addr) >= env.Config.MaxSubscriptionsPerClient {
    28  		return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient)
    29  	}
    30  
    31  	env.Logger.Info("Subscribe to query", "remote", addr, "query", query)
    32  
    33  	q, err := tmquery.New(query)
    34  	if err != nil {
    35  		return nil, errors.Wrap(err, "failed to parse query")
    36  	}
    37  
    38  	subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout)
    39  	defer cancel()
    40  
    41  	sub, err := env.EventBus.Subscribe(subCtx, addr, q, subBufferSize)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	// Capture the current ID, since it can change in the future.
    47  	subscriptionID := ctx.JSONReq.ID
    48  	go func() {
    49  		for {
    50  			select {
    51  			case msg := <-sub.Out():
    52  				resultEvent := &ctypes.ResultEvent{Query: query, Data: msg.Data(), Events: msg.Events()}
    53  				ctx.WSConn.TryWriteRPCResponse(
    54  					rpctypes.NewRPCSuccessResponse(
    55  						ctx.WSConn.Codec(),
    56  						subscriptionID,
    57  						resultEvent,
    58  					))
    59  			case <-sub.Cancelled():
    60  				if sub.Err() != tmpubsub.ErrUnsubscribed {
    61  					var reason string
    62  					if sub.Err() == nil {
    63  						reason = "Tendermint exited"
    64  					} else {
    65  						reason = sub.Err().Error()
    66  					}
    67  					ctx.WSConn.TryWriteRPCResponse(
    68  						rpctypes.RPCServerError(
    69  							subscriptionID,
    70  							fmt.Errorf("subscription was cancelled (reason: %s)", reason),
    71  						))
    72  				}
    73  				return
    74  			}
    75  		}
    76  	}()
    77  
    78  	return &ctypes.ResultSubscribe{}, nil
    79  }
    80  
    81  // Unsubscribe from events via WebSocket.
    82  // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe
    83  func Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
    84  	addr := ctx.RemoteAddr()
    85  	env.Logger.Info("Unsubscribe from query", "remote", addr, "query", query)
    86  	q, err := tmquery.New(query)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "failed to parse query")
    89  	}
    90  	err = env.EventBus.Unsubscribe(context.Background(), addr, q)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	return &ctypes.ResultUnsubscribe{}, nil
    95  }
    96  
    97  // UnsubscribeAll from all events via WebSocket.
    98  // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe_all
    99  func UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
   100  	addr := ctx.RemoteAddr()
   101  	env.Logger.Info("Unsubscribe from all", "remote", addr)
   102  	err := env.EventBus.UnsubscribeAll(context.Background(), addr)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	return &ctypes.ResultUnsubscribe{}, nil
   107  }