package rpc import ( "context" "net/http" "time" "github.com/rs/cors" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/internal/consensus" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/pubsub" "github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/rpc/jsonrpc/server" "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/indexer" "github.com/tendermint/tendermint/types" ) // Server defines parameters for running an Inspector rpc server. type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler http.Handler Logger log.Logger Config *config.RPCConfig } // Routes returns the set of routes used by the Inspector server. // //nolint: lll func Routes(cfg config.RPCConfig, s state.Store, bs state.BlockStore, es []indexer.EventSink, logger log.Logger) core.RoutesMap { env := &core.Environment{ Config: cfg, EventSinks: es, StateStore: s, BlockStore: bs, ConsensusReactor: waitSyncCheckerImpl{}, Logger: logger, } return core.RoutesMap{ "blockchain": server.NewRPCFunc(env.BlockchainInfo, "minHeight,maxHeight", true), "consensus_params": server.NewRPCFunc(env.ConsensusParams, "height", true), "block": server.NewRPCFunc(env.Block, "height", true), "block_by_hash": server.NewRPCFunc(env.BlockByHash, "hash", true), "block_results": server.NewRPCFunc(env.BlockResults, "height", true), "commit": server.NewRPCFunc(env.Commit, "height", true), "validators": server.NewRPCFunc(env.Validators, "height,page,per_page", true), "tx": server.NewRPCFunc(env.Tx, "hash,prove", true), "tx_search": server.NewRPCFunc(env.TxSearch, "query,prove,page,per_page,order_by", false), "block_search": server.NewRPCFunc(env.BlockSearch, "query,page,per_page,order_by", false), } } // Handler returns the http.Handler configured for use with an Inspector server. Handler // registers the routes on the http.Handler and also registers the websocket handler // and the CORS handler if specified by the configuration options. func Handler(rpcConfig *config.RPCConfig, routes core.RoutesMap, logger log.Logger) http.Handler { mux := http.NewServeMux() wmLogger := logger.With("protocol", "websocket") var eventBus types.EventBusSubscriber websocketDisconnectFn := func(remoteAddr string) { err := eventBus.UnsubscribeAll(context.Background(), remoteAddr) if err != nil && err != pubsub.ErrSubscriptionNotFound { wmLogger.Error("Failed to unsubscribe addr from events", "addr", remoteAddr, "err", err) } } wm := server.NewWebsocketManager(routes, server.OnDisconnect(websocketDisconnectFn), server.ReadLimit(rpcConfig.MaxBodyBytes)) wm.SetLogger(wmLogger) mux.HandleFunc("/websocket", wm.WebsocketHandler) server.RegisterRPCFuncs(mux, routes, logger) var rootHandler http.Handler = mux if rpcConfig.IsCorsEnabled() { rootHandler = addCORSHandler(rpcConfig, mux) } return rootHandler } func addCORSHandler(rpcConfig *config.RPCConfig, h http.Handler) http.Handler { corsMiddleware := cors.New(cors.Options{ AllowedOrigins: rpcConfig.CORSAllowedOrigins, AllowedMethods: rpcConfig.CORSAllowedMethods, AllowedHeaders: rpcConfig.CORSAllowedHeaders, }) h = corsMiddleware.Handler(h) return h } type waitSyncCheckerImpl struct{} func (waitSyncCheckerImpl) WaitSync() bool { return false } func (waitSyncCheckerImpl) GetPeerState(peerID types.NodeID) (*consensus.PeerState, bool) { return nil, false } // ListenAndServe listens on the address specified in srv.Addr and handles any // incoming requests over HTTP using the Inspector rpc handler specified on the server. func (srv *Server) ListenAndServe(ctx context.Context) error { listener, err := server.Listen(srv.Addr, srv.Config.MaxOpenConnections) if err != nil { return err } go func() { <-ctx.Done() listener.Close() }() return server.Serve(listener, srv.Handler, srv.Logger, serverRPCConfig(srv.Config)) } // ListenAndServeTLS listens on the address specified in srv.Addr. ListenAndServeTLS handles // incoming requests over HTTPS using the Inspector rpc handler specified on the server. func (srv *Server) ListenAndServeTLS(ctx context.Context, certFile, keyFile string) error { listener, err := server.Listen(srv.Addr, srv.Config.MaxOpenConnections) if err != nil { return err } go func() { <-ctx.Done() listener.Close() }() return server.ServeTLS(listener, srv.Handler, certFile, keyFile, srv.Logger, serverRPCConfig(srv.Config)) } func serverRPCConfig(r *config.RPCConfig) *server.Config { cfg := server.DefaultConfig() cfg.MaxBodyBytes = r.MaxBodyBytes cfg.MaxHeaderBytes = r.MaxHeaderBytes // If necessary adjust global WriteTimeout to ensure it's greater than // TimeoutBroadcastTxCommit. // See https://github.com/tendermint/tendermint/issues/3435 if cfg.WriteTimeout <= r.TimeoutBroadcastTxCommit { cfg.WriteTimeout = r.TimeoutBroadcastTxCommit + 1*time.Second } return cfg }