package main import ( "errors" "fmt" "os" "path/filepath" "strconv" "time" "github.com/spf13/viper" "github.com/tendermint/tendermint/abci/server" "github.com/tendermint/tendermint/config" tmflags "github.com/tendermint/tendermint/libs/cli/flags" "github.com/tendermint/tendermint/libs/log" tmnet "github.com/tendermint/tendermint/libs/net" "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" mcs "github.com/tendermint/tendermint/test/maverick/consensus" maverick "github.com/tendermint/tendermint/test/maverick/node" ) var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) // main is the binary entrypoint. func main() { if len(os.Args) != 2 { fmt.Printf("Usage: %v ", os.Args[0]) return } configFile := "" if len(os.Args) == 2 { configFile = os.Args[1] } if err := run(configFile); err != nil { logger.Error(err.Error()) os.Exit(1) } } // run runs the application - basically like main() with error handling. func run(configFile string) error { cfg, err := LoadConfig(configFile) if err != nil { return err } // Start remote signer (must start before node if running builtin). if cfg.PrivValServer != "" { if err = startSigner(cfg); err != nil { return err } if cfg.Protocol == "builtin" { time.Sleep(1 * time.Second) } } // Start app server. switch cfg.Protocol { case "socket", "grpc": err = startApp(cfg) case "builtin": if len(cfg.Misbehaviors) == 0 { err = startNode(cfg) } else { err = startMaverick(cfg) } default: err = fmt.Errorf("invalid protocol %q", cfg.Protocol) } if err != nil { return err } // Apparently there's no way to wait for the server, so we just sleep for { time.Sleep(1 * time.Hour) } } // startApp starts the application server, listening for connections from Tendermint. func startApp(cfg *Config) error { app, err := NewApplication(cfg) if err != nil { return err } server, err := server.NewServer(cfg.Listen, cfg.Protocol, app) if err != nil { return err } err = server.Start() if err != nil { return err } logger.Info(fmt.Sprintf("Server listening on %v (%v protocol)", cfg.Listen, cfg.Protocol)) return nil } // startNode starts a Tendermint node running the application directly. It assumes the Tendermint // configuration is in $TMHOME/config/tendermint.toml. // // FIXME There is no way to simply load the configuration from a file, so we need to pull in Viper. func startNode(cfg *Config) error { app, err := NewApplication(cfg) if err != nil { return err } tmcfg, nodeLogger, nodeKey, err := setupNode() if err != nil { return fmt.Errorf("failed to setup config: %w", err) } n, err := node.NewNode(tmcfg, privval.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()), nodeKey, proxy.NewLocalClientCreator(app), node.DefaultGenesisDocProviderFunc(tmcfg), node.DefaultDBProvider, node.DefaultMetricsProvider(tmcfg.Instrumentation), nodeLogger, ) if err != nil { return err } return n.Start() } // startMaverick starts a Maverick node that runs the application directly. It assumes the Tendermint // configuration is in $TMHOME/config/tendermint.toml. func startMaverick(cfg *Config) error { app, err := NewApplication(cfg) if err != nil { return err } tmcfg, logger, nodeKey, err := setupNode() if err != nil { return fmt.Errorf("failed to setup config: %w", err) } misbehaviors := make(map[int64]mcs.Misbehavior, len(cfg.Misbehaviors)) for heightString, misbehaviorString := range cfg.Misbehaviors { height, _ := strconv.ParseInt(heightString, 10, 64) misbehaviors[height] = mcs.MisbehaviorList[misbehaviorString] } n, err := maverick.NewNode(tmcfg, maverick.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()), nodeKey, proxy.NewLocalClientCreator(app), maverick.DefaultGenesisDocProviderFunc(tmcfg), maverick.DefaultDBProvider, maverick.DefaultMetricsProvider(tmcfg.Instrumentation), logger, misbehaviors, ) if err != nil { return err } return n.Start() } // startSigner starts a signer server connecting to the given endpoint. func startSigner(cfg *Config) error { filePV := privval.LoadFilePV(cfg.PrivValKey, cfg.PrivValState) protocol, address := tmnet.ProtocolAndAddress(cfg.PrivValServer) var dialFn privval.SocketDialer switch protocol { case "tcp": dialFn = privval.DialTCPFn(address, 3*time.Second, filePV.Key.PrivKey) case "unix": dialFn = privval.DialUnixFn(address) default: return fmt.Errorf("invalid privval protocol %q", protocol) } endpoint := privval.NewSignerDialerEndpoint(logger, dialFn, privval.SignerDialerEndpointRetryWaitInterval(1*time.Second), privval.SignerDialerEndpointConnRetries(100)) err := privval.NewSignerServer(endpoint, cfg.ChainID, filePV).Start() if err != nil { return err } logger.Info(fmt.Sprintf("Remote signer connecting to %v", cfg.PrivValServer)) return nil } func setupNode() (*config.Config, log.Logger, *p2p.NodeKey, error) { var tmcfg *config.Config home := os.Getenv("TMHOME") if home == "" { return nil, nil, nil, errors.New("TMHOME not set") } viper.AddConfigPath(filepath.Join(home, "config")) viper.SetConfigName("config") err := viper.ReadInConfig() if err != nil { return nil, nil, nil, err } tmcfg = config.DefaultConfig() err = viper.Unmarshal(tmcfg) if err != nil { return nil, nil, nil, err } tmcfg.SetRoot(home) if err = tmcfg.ValidateBasic(); err != nil { return nil, nil, nil, fmt.Errorf("error in config file: %w", err) } if tmcfg.LogFormat == config.LogFormatJSON { logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout)) } nodeLogger, err := tmflags.ParseLogLevel(tmcfg.LogLevel, logger, config.DefaultLogLevel()) if err != nil { return nil, nil, nil, err } nodeLogger = nodeLogger.With("module", "main") nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile()) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load or gen node key %s: %w", tmcfg.NodeKeyFile(), err) } return tmcfg, nodeLogger, nodeKey, nil }