diff --git a/server/util.go b/server/util.go index 521d13d8a508..d06152d19185 100644 --- a/server/util.go +++ b/server/util.go @@ -513,7 +513,12 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { chainID := cast.ToString(appOpts.Get(flags.FlagChainID)) if chainID == "" { // fallback to genesis chain-id - reader, err := os.Open(filepath.Join(homeDir, "config", "genesis.json")) + genesisPathCfg := appOpts.GetString("genesis_file") + if genesisPathCfg == "" { + genesisPathCfg = filepath.Join("config", "genesis.json") + } + + reader, err := os.Open(filepath.Join(homeDir, genesisPathCfg)) if err != nil { panic(err) } diff --git a/server/v2/cometbft/server.go b/server/v2/cometbft/server.go new file mode 100644 index 000000000000..58eb7b442577 --- /dev/null +++ b/server/v2/cometbft/server.go @@ -0,0 +1,335 @@ +package cometbft + +import ( + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "os" + "path/filepath" + + abciserver "github.com/cometbft/cometbft/abci/server" + cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" + cmtcfg "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/node" + "github.com/cometbft/cometbft/p2p" + pvm "github.com/cometbft/cometbft/privval" + "github.com/cometbft/cometbft/proxy" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/server" + "cosmossdk.io/core/transaction" + "cosmossdk.io/log" + "cosmossdk.io/schema/decoding" + "cosmossdk.io/schema/indexer" + serverv2 "cosmossdk.io/server/v2" + "cosmossdk.io/server/v2/appmanager" + cometlog "cosmossdk.io/server/v2/cometbft/log" + "cosmossdk.io/server/v2/cometbft/mempool" + "cosmossdk.io/server/v2/cometbft/types" + "cosmossdk.io/store/v2/snapshots" + + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +const ServerName = "comet" + +var ( + _ serverv2.ServerComponent[transaction.Tx] = (*CometBFTServer[transaction.Tx])(nil) + _ serverv2.HasCLICommands = (*CometBFTServer[transaction.Tx])(nil) + _ serverv2.HasStartFlags = (*CometBFTServer[transaction.Tx])(nil) +) + +type CometBFTServer[T transaction.Tx] struct { + Node *node.Node + Consensus *Consensus[T] + + initTxCodec transaction.Codec[T] + logger log.Logger + serverOptions ServerOptions[T] + config Config + cfgOptions []CfgOption +} + +func New[T transaction.Tx]( + logger log.Logger, + appName string, + store types.Store, + appManager appmanager.AppManager[T], + queryHandlers map[string]appmodulev2.Handler, + decoderResolver decoding.DecoderResolver, + txCodec transaction.Codec[T], + cfg server.ConfigMap, + serverOptions ServerOptions[T], + cfgOptions ...CfgOption, +) (*CometBFTServer[T], error) { + srv := &CometBFTServer[T]{ + initTxCodec: txCodec, + serverOptions: serverOptions, + cfgOptions: cfgOptions, + } + + home, _ := cfg[serverv2.FlagHome].(string) + + // get configs (app.toml + config.toml) from viper + appTomlConfig := srv.Config().(*AppTomlConfig) + configTomlConfig := cmtcfg.DefaultConfig().SetRoot(home) + if len(cfg) > 0 { + if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &appTomlConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) + } + + if err := serverv2.UnmarshalSubConfig(cfg, "", &configTomlConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) + } + } + + srv.config = Config{ + ConfigTomlConfig: configTomlConfig, + AppTomlConfig: appTomlConfig, + } + + chainID, _ := cfg[FlagChainID].(string) + if chainID == "" { + // fallback to genesis chain-id + reader, err := os.Open(srv.config.ConfigTomlConfig.GenesisFile()) + if err != nil { + return nil, fmt.Errorf("failed to open genesis file: %w", err) + } + defer reader.Close() + + chainID, err = genutiltypes.ParseChainIDFromGenesis(reader) + if err != nil { + return nil, fmt.Errorf("failed to parse chain-id from genesis file: %w", err) + } + } + + indexEvents := make(map[string]struct{}, len(srv.config.AppTomlConfig.IndexEvents)) + for _, e := range srv.config.AppTomlConfig.IndexEvents { + indexEvents[e] = struct{}{} + } + + srv.logger = logger.With(log.ModuleKey, srv.Name()) + consensus := NewConsensus( + logger, + appName, + appManager, + srv.serverOptions.Mempool(cfg), + indexEvents, + queryHandlers, + store, + srv.config, + srv.initTxCodec, + chainID, + ) + consensus.prepareProposalHandler = srv.serverOptions.PrepareProposalHandler + consensus.processProposalHandler = srv.serverOptions.ProcessProposalHandler + consensus.checkTxHandler = srv.serverOptions.CheckTxHandler + consensus.verifyVoteExt = srv.serverOptions.VerifyVoteExtensionHandler + consensus.extendVote = srv.serverOptions.ExtendVoteHandler + consensus.addrPeerFilter = srv.serverOptions.AddrPeerFilter + consensus.idPeerFilter = srv.serverOptions.IdPeerFilter + + ss := store.GetStateStorage().(snapshots.StorageSnapshotter) + sc := store.GetStateCommitment().(snapshots.CommitSnapshotter) + + snapshotStore, err := GetSnapshotStore(srv.config.ConfigTomlConfig.RootDir) + if err != nil { + return nil, err + } + consensus.snapshotManager = snapshots.NewManager( + snapshotStore, srv.serverOptions.SnapshotOptions(cfg), sc, ss, nil, logger) + + srv.Consensus = consensus + + // initialize the indexer + if indexerCfg := srv.config.AppTomlConfig.Indexer; len(indexerCfg.Target) > 0 { + listener, err := indexer.StartIndexing(indexer.IndexingOptions{ + Config: indexerCfg, + Resolver: decoderResolver, + Logger: logger.With(log.ModuleKey, "indexer"), + }) + if err != nil { + return nil, fmt.Errorf("failed to start indexing: %w", err) + } + consensus.listener = &listener.Listener + } + + return srv, nil +} + +// NewWithConfigOptions creates a new CometBFT server with the provided config options. +// It is *not* a fully functional server (since it has been created without dependencies) +// The returned server should only be used to get and set configuration. +func NewWithConfigOptions[T transaction.Tx](opts ...CfgOption) *CometBFTServer[T] { + return &CometBFTServer[T]{ + cfgOptions: opts, + } +} + +func (s *CometBFTServer[T]) Name() string { + return ServerName +} + +func (s *CometBFTServer[T]) Start(ctx context.Context) error { + wrappedLogger := cometlog.CometLoggerWrapper{Logger: s.logger} + if s.config.AppTomlConfig.Standalone { + svr, err := abciserver.NewServer(s.config.AppTomlConfig.Address, s.config.AppTomlConfig.Transport, s.Consensus) + if err != nil { + return fmt.Errorf("error creating listener: %w", err) + } + + svr.SetLogger(wrappedLogger) + + return svr.Start() + } + + nodeKey, err := p2p.LoadOrGenNodeKey(s.config.ConfigTomlConfig.NodeKeyFile()) + if err != nil { + return err + } + + pv, err := pvm.LoadOrGenFilePV( + s.config.ConfigTomlConfig.PrivValidatorKeyFile(), + s.config.ConfigTomlConfig.PrivValidatorStateFile(), + s.serverOptions.KeygenF, + ) + if err != nil { + return err + } + + s.Node, err = node.NewNode( + ctx, + s.config.ConfigTomlConfig, + pv, + nodeKey, + proxy.NewConsensusSyncLocalClientCreator(s.Consensus), + getGenDocProvider(s.config.ConfigTomlConfig), + cmtcfg.DefaultDBProvider, + node.DefaultMetricsProvider(s.config.ConfigTomlConfig.Instrumentation), + wrappedLogger, + ) + if err != nil { + return err + } + + s.logger.Info("starting consensus server") + return s.Node.Start() +} + +func (s *CometBFTServer[T]) Stop(context.Context) error { + if s.Node != nil && s.Node.IsRunning() { + s.logger.Info("stopping consensus server") + return s.Node.Stop() + } + + return nil +} + +// returns a function which returns the genesis doc from the genesis file. +func getGenDocProvider(cfg *cmtcfg.Config) func() (node.ChecksummedGenesisDoc, error) { + return func() (node.ChecksummedGenesisDoc, error) { + appGenesis, err := genutiltypes.AppGenesisFromFile(cfg.GenesisFile()) + if err != nil { + return node.ChecksummedGenesisDoc{ + Sha256Checksum: []byte{}, + }, err + } + + gen, err := appGenesis.ToGenesisDoc() + if err != nil { + return node.ChecksummedGenesisDoc{ + Sha256Checksum: []byte{}, + }, err + } + genbz, err := gen.AppState.MarshalJSON() + if err != nil { + return node.ChecksummedGenesisDoc{ + Sha256Checksum: []byte{}, + }, err + } + + bz, err := json.Marshal(genbz) + if err != nil { + return node.ChecksummedGenesisDoc{ + Sha256Checksum: []byte{}, + }, err + } + sum := sha256.Sum256(bz) + + return node.ChecksummedGenesisDoc{ + GenesisDoc: gen, + Sha256Checksum: sum[:], + }, nil + } +} + +func (s *CometBFTServer[T]) StartCmdFlags() *pflag.FlagSet { + flags := pflag.NewFlagSet(s.Name(), pflag.ExitOnError) + + flags.String(FlagAddress, "tcp://127.0.0.1:26658", "Listen address") + flags.String(FlagTransport, "socket", "Transport protocol: socket, grpc") + flags.Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") + flags.Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") + flags.Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log") + flags.Bool(Standalone, false, "Run app without CometBFT") + flags.Int(FlagMempoolMaxTxs, mempool.DefaultMaxTx, "Sets MaxTx value for the app-side mempool") + + // add comet flags, we use an empty command to avoid duplicating CometBFT's AddNodeFlags. + // we can then merge the flag sets. + emptyCmd := &cobra.Command{} + cmtcmd.AddNodeFlags(emptyCmd) + flags.AddFlagSet(emptyCmd.Flags()) + + return flags +} + +func (s *CometBFTServer[T]) CLICommands() serverv2.CLIConfig { + return serverv2.CLIConfig{ + Commands: []*cobra.Command{ + StatusCommand(), + ShowNodeIDCmd(), + ShowValidatorCmd(), + ShowAddressCmd(), + VersionCmd(), + s.BootstrapStateCmd(), + cmtcmd.ResetAllCmd, + cmtcmd.ResetStateCmd, + }, + Queries: []*cobra.Command{ + QueryBlockCmd(), + QueryBlocksCmd(), + QueryBlockResultsCmd(), + }, + } +} + +// CometBFT is a special server, it has config in config.toml and app.toml + +// Config returns the (app.toml) server configuration. +func (s *CometBFTServer[T]) Config() any { + if s.config.AppTomlConfig == nil || s.config.AppTomlConfig.Address == "" { + cfg := &Config{AppTomlConfig: DefaultAppTomlConfig()} + // overwrite the default config with the provided options + for _, opt := range s.cfgOptions { + opt(cfg) + } + + return cfg.AppTomlConfig + } + + return s.config.AppTomlConfig +} + +// WriteCustomConfigAt writes the default cometbft config.toml +func (s *CometBFTServer[T]) WriteCustomConfigAt(configPath string) error { + cfg := &Config{ConfigTomlConfig: cmtcfg.DefaultConfig()} + for _, opt := range s.cfgOptions { + opt(cfg) + } + + cmtcfg.WriteConfigFile(filepath.Join(configPath, "config.toml"), cfg.ConfigTomlConfig) + return nil +}