package inspect import ( "context" "errors" "fmt" "net" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/internal/inspect/rpc" rpccore "github.com/tendermint/tendermint/internal/rpc/core" "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/libs/log" tmstrings "github.com/tendermint/tendermint/libs/strings" "github.com/tendermint/tendermint/types" "golang.org/x/sync/errgroup" ) // Inspector manages an RPC service that exports methods to debug a failed node. // After a node shuts down due to a consensus failure, it will no longer start // up its state cannot easily be inspected. An Inspector value provides a similar interface // to the node, using the underlying Tendermint data stores, without bringing up // any other components. A caller can query the Inspector service to inspect the // persisted state and debug the failure. type Inspector struct { routes rpccore.RoutesMap config *config.RPCConfig indexerService *indexer.Service eventBus *types.EventBus logger log.Logger } // New returns an Inspector that serves RPC on the specified BlockStore and StateStore. // The Inspector type does not modify the state or block stores. // The sinks are used to enable block and transaction querying via the RPC server. // The caller is responsible for starting and stopping the Inspector service. /// //nolint:lll func New(cfg *config.RPCConfig, bs state.BlockStore, ss state.Store, es []indexer.EventSink, logger log.Logger) *Inspector { routes := rpc.Routes(*cfg, ss, bs, es, logger) eb := types.NewEventBus() eb.SetLogger(logger.With("module", "events")) is := indexer.NewIndexerService(es, eb) is.SetLogger(logger.With("module", "txindex")) return &Inspector{ routes: routes, config: cfg, logger: logger, eventBus: eb, indexerService: is, } } // NewFromConfig constructs an Inspector using the values defined in the passed in config. func NewFromConfig(logger log.Logger, cfg *config.Config) (*Inspector, error) { bsDB, err := config.DefaultDBProvider(&config.DBContext{ID: "blockstore", Config: cfg}) if err != nil { return nil, err } bs := store.NewBlockStore(bsDB) sDB, err := config.DefaultDBProvider(&config.DBContext{ID: "state", Config: cfg}) if err != nil { return nil, err } genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile()) if err != nil { return nil, err } sinks, err := sink.EventSinksFromConfig(cfg, config.DefaultDBProvider, genDoc.ChainID) if err != nil { return nil, err } ss := state.NewStore(sDB) return New(cfg.RPC, bs, ss, sinks, logger), nil } // Run starts the Inspector servers and blocks until the servers shut down. The passed // in context is used to control the lifecycle of the servers. func (ins *Inspector) Run(ctx context.Context) error { err := ins.eventBus.Start() if err != nil { return fmt.Errorf("error starting event bus: %s", err) } defer func() { err := ins.eventBus.Stop() if err != nil { ins.logger.Error("event bus stopped with error", "err", err) } }() err = ins.indexerService.Start() if err != nil { return fmt.Errorf("error starting indexer service: %s", err) } defer func() { err := ins.indexerService.Stop() if err != nil { ins.logger.Error("indexer service stopped with error", "err", err) } }() return startRPCServers(ctx, ins.config, ins.logger, ins.routes) } func startRPCServers(ctx context.Context, cfg *config.RPCConfig, logger log.Logger, routes rpccore.RoutesMap) error { g, tctx := errgroup.WithContext(ctx) listenAddrs := tmstrings.SplitAndTrimEmpty(cfg.ListenAddress, ",", " ") rh := rpc.Handler(cfg, routes, logger) for _, listenerAddr := range listenAddrs { server := rpc.Server{ Logger: logger, Config: cfg, Handler: rh, Addr: listenerAddr, } if cfg.IsTLSEnabled() { keyFile := cfg.KeyFile() certFile := cfg.CertFile() listenerAddr := listenerAddr g.Go(func() error { logger.Info("RPC HTTPS server starting", "address", listenerAddr, "certfile", certFile, "keyfile", keyFile) err := server.ListenAndServeTLS(tctx, certFile, keyFile) if !errors.Is(err, net.ErrClosed) { return err } logger.Info("RPC HTTPS server stopped", "address", listenerAddr) return nil }) } else { listenerAddr := listenerAddr g.Go(func() error { logger.Info("RPC HTTP server starting", "address", listenerAddr) err := server.ListenAndServe(tctx) if !errors.Is(err, net.ErrClosed) { return err } logger.Info("RPC HTTP server stopped", "address", listenerAddr) return nil }) } } return g.Wait() }