Browse Source

cmd: avoid package state in cli constructors (#7719)

pull/7738/head
Sam Kleinman 2 years ago
committed by GitHub
parent
commit
a4e2f05d7a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1008 additions and 910 deletions
  1. +103
    -100
      abci/cmd/abci-cli/abci-cli.go
  2. +7
    -3
      cmd/priv_val_server/main.go
  3. +23
    -23
      cmd/tendermint/commands/gen_validator.go
  4. +32
    -33
      cmd/tendermint/commands/init.go
  5. +29
    -32
      cmd/tendermint/commands/inspect.go
  6. +4
    -3
      cmd/tendermint/commands/key_migrate.go
  7. +166
    -169
      cmd/tendermint/commands/light.go
  8. +83
    -61
      cmd/tendermint/commands/reindex_event.go
  9. +20
    -12
      cmd/tendermint/commands/reindex_event_test.go
  10. +20
    -15
      cmd/tendermint/commands/replay.go
  11. +34
    -27
      cmd/tendermint/commands/reset_priv_validator.go
  12. +18
    -15
      cmd/tendermint/commands/rollback.go
  13. +37
    -40
      cmd/tendermint/commands/root.go
  14. +60
    -57
      cmd/tendermint/commands/root_test.go
  15. +29
    -28
      cmd/tendermint/commands/run_node.go
  16. +14
    -13
      cmd/tendermint/commands/show_node_id.go
  17. +56
    -53
      cmd/tendermint/commands/show_validator.go
  18. +226
    -192
      cmd/tendermint/commands/testnet.go
  19. +37
    -25
      cmd/tendermint/main.go
  20. +5
    -1
      docs/tutorials/go.md
  21. +5
    -5
      libs/cli/setup.go
  22. +0
    -3
      libs/log/default_test.go

+ 103
- 100
abci/cmd/abci-cli/abci-cli.go View File

@ -28,7 +28,6 @@ import (
// client is a global variable so it can be reused by the console
var (
client abciclient.Client
logger log.Logger
)
// flags
@ -48,34 +47,32 @@ var (
flagPersist string
)
var RootCmd = &cobra.Command{
Use: "abci-cli",
Short: "the ABCI CLI tool wraps an ABCI client",
Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
func RootCmmand(logger log.Logger) *cobra.Command {
return &cobra.Command{
Use: "abci-cli",
Short: "the ABCI CLI tool wraps an ABCI client",
Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers",
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
switch cmd.Use {
case "kvstore", "version":
return nil
}
if logger == nil {
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
}
if client == nil {
var err error
client, err = abciclient.NewClient(logger.With("module", "abci-client"), flagAddress, flagAbci, false)
if err != nil {
return err
switch cmd.Use {
case "kvstore", "version":
return nil
}
if err := client.Start(cmd.Context()); err != nil {
return err
if client == nil {
var err error
client, err = abciclient.NewClient(logger.With("module", "abci-client"), flagAddress, flagAbci, false)
if err != nil {
return err
}
if err := client.Start(cmd.Context()); err != nil {
return err
}
}
}
return nil
},
return nil
},
}
}
// Structure for data passed to print response.
@ -97,56 +94,46 @@ type queryResponse struct {
}
func Execute() error {
addGlobalFlags()
addCommands()
return RootCmd.Execute()
logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
if err != nil {
return err
}
cmd := RootCmmand(logger)
addGlobalFlags(cmd)
addCommands(cmd, logger)
return cmd.Execute()
}
func addGlobalFlags() {
RootCmd.PersistentFlags().StringVarP(&flagAddress,
func addGlobalFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&flagAddress,
"address",
"",
"tcp://0.0.0.0:26658",
"address of application socket")
RootCmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc")
RootCmd.PersistentFlags().BoolVarP(&flagVerbose,
cmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc")
cmd.PersistentFlags().BoolVarP(&flagVerbose,
"verbose",
"v",
false,
"print the command and results as if it were a console session")
RootCmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level")
cmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level")
}
func addQueryFlags() {
queryCmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with")
queryCmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at")
queryCmd.PersistentFlags().BoolVarP(&flagProve,
"prove",
"",
false,
"whether or not to return a merkle proof of the query result")
}
func addKVStoreFlags() {
kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
}
func addCommands() {
RootCmd.AddCommand(batchCmd)
RootCmd.AddCommand(consoleCmd)
RootCmd.AddCommand(echoCmd)
RootCmd.AddCommand(infoCmd)
RootCmd.AddCommand(deliverTxCmd)
RootCmd.AddCommand(checkTxCmd)
RootCmd.AddCommand(commitCmd)
RootCmd.AddCommand(versionCmd)
RootCmd.AddCommand(testCmd)
addQueryFlags()
RootCmd.AddCommand(queryCmd)
func addCommands(cmd *cobra.Command, logger log.Logger) {
cmd.AddCommand(batchCmd)
cmd.AddCommand(consoleCmd)
cmd.AddCommand(echoCmd)
cmd.AddCommand(infoCmd)
cmd.AddCommand(deliverTxCmd)
cmd.AddCommand(checkTxCmd)
cmd.AddCommand(commitCmd)
cmd.AddCommand(versionCmd)
cmd.AddCommand(testCmd)
cmd.AddCommand(getQueryCmd())
// examples
addKVStoreFlags()
RootCmd.AddCommand(kvstoreCmd)
cmd.AddCommand(getKVStoreCmd(logger))
}
var batchCmd = &cobra.Command{
@ -236,20 +223,38 @@ var versionCmd = &cobra.Command{
},
}
var queryCmd = &cobra.Command{
Use: "query",
Short: "query the application state",
Long: "query the application state",
Args: cobra.ExactArgs(1),
RunE: cmdQuery,
func getQueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Short: "query the application state",
Long: "query the application state",
Args: cobra.ExactArgs(1),
RunE: cmdQuery,
}
cmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with")
cmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at")
cmd.PersistentFlags().BoolVarP(&flagProve,
"prove",
"",
false,
"whether or not to return a merkle proof of the query result")
return cmd
}
var kvstoreCmd = &cobra.Command{
Use: "kvstore",
Short: "ABCI demo example",
Long: "ABCI demo example",
Args: cobra.ExactArgs(0),
RunE: cmdKVStore,
func getKVStoreCmd(logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "kvstore",
Short: "ABCI demo example",
Long: "ABCI demo example",
Args: cobra.ExactArgs(0),
RunE: makeKVStoreCmd(logger),
}
cmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
return cmd
}
var testCmd = &cobra.Command{
@ -425,12 +430,9 @@ func cmdUnimplemented(cmd *cobra.Command, args []string) error {
})
fmt.Println("Available commands:")
fmt.Printf("%s: %s\n", echoCmd.Use, echoCmd.Short)
fmt.Printf("%s: %s\n", infoCmd.Use, infoCmd.Short)
fmt.Printf("%s: %s\n", checkTxCmd.Use, checkTxCmd.Short)
fmt.Printf("%s: %s\n", deliverTxCmd.Use, deliverTxCmd.Short)
fmt.Printf("%s: %s\n", queryCmd.Use, queryCmd.Short)
fmt.Printf("%s: %s\n", commitCmd.Use, commitCmd.Short)
for _, cmd := range cmd.Commands() {
fmt.Printf("%s: %s\n", cmd.Use, cmd.Short)
}
fmt.Println("Use \"[command] --help\" for more information about a command.")
return nil
@ -574,33 +576,34 @@ func cmdQuery(cmd *cobra.Command, args []string) error {
return nil
}
func cmdKVStore(cmd *cobra.Command, args []string) error {
logger := log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
func makeKVStoreCmd(logger log.Logger) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
app = kvstore.NewApplication()
} else {
app = kvstore.NewPersistentKVStoreApplication(logger, flagPersist)
}
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
app = kvstore.NewApplication()
} else {
app = kvstore.NewPersistentKVStoreApplication(logger, flagPersist)
}
// Start the listener
srv, err := server.NewServer(logger.With("module", "abci-server"), flagAddress, flagAbci, app)
if err != nil {
return err
}
// Start the listener
srv, err := server.NewServer(logger.With("module", "abci-server"), flagAddress, flagAbci, app)
if err != nil {
return err
}
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM)
defer cancel()
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM)
defer cancel()
if err := srv.Start(ctx); err != nil {
return err
}
if err := srv.Start(ctx); err != nil {
return err
// Run forever.
<-ctx.Done()
return nil
}
// Run forever.
<-ctx.Done()
return nil
}
//--------------------------------------------------------------------------------


+ 7
- 3
cmd/priv_val_server/main.go View File

@ -45,12 +45,16 @@ func main() {
keyFile = flag.String("keyfile", "", "absolute path to server key")
rootCA = flag.String("rootcafile", "", "absolute path to root CA")
prometheusAddr = flag.String("prometheus-addr", "", "address for prometheus endpoint (host:port)")
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo).
With("module", "priv_val")
)
flag.Parse()
logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to construct logger: %v", err)
os.Exit(1)
}
logger = logger.With("module", "priv_val")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()


+ 23
- 23
cmd/tendermint/commands/gen_validator.go View File

@ -12,30 +12,30 @@ import (
// GenValidatorCmd allows the generation of a keypair for a
// validator.
var GenValidatorCmd = &cobra.Command{
Use: "gen-validator",
Short: "Generate new validator keypair",
RunE: genValidator,
}
func init() {
GenValidatorCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
"Key type to generate privval file with. Options: ed25519, secp256k1")
}
func genValidator(cmd *cobra.Command, args []string) error {
pv, err := privval.GenFilePV("", "", keyType)
if err != nil {
return err
}
jsbz, err := json.Marshal(pv)
if err != nil {
return fmt.Errorf("validator -> json: %w", err)
func MakeGenValidatorCommand() *cobra.Command {
var keyType string
cmd := &cobra.Command{
Use: "gen-validator",
Short: "Generate new validator keypair",
RunE: func(cmd *cobra.Command, args []string) error {
pv, err := privval.GenFilePV("", "", keyType)
if err != nil {
return err
}
jsbz, err := json.Marshal(pv)
if err != nil {
return fmt.Errorf("validator -> json: %w", err)
}
fmt.Printf("%v\n", string(jsbz))
return nil
},
}
fmt.Printf(`%v
`, string(jsbz))
cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
"Key type to generate privval file with. Options: ed25519, secp256k1")
return nil
return cmd
}

+ 32
- 33
cmd/tendermint/commands/init.go View File

@ -7,7 +7,8 @@ import (
"github.com/spf13/cobra"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtime "github.com/tendermint/tendermint/libs/time"
@ -15,43 +16,41 @@ import (
"github.com/tendermint/tendermint/types"
)
// InitFilesCmd initializes a fresh Tendermint Core instance.
var InitFilesCmd = &cobra.Command{
Use: "init [full|validator|seed]",
Short: "Initializes a Tendermint node",
ValidArgs: []string{"full", "validator", "seed"},
// We allow for zero args so we can throw a more informative error
Args: cobra.MaximumNArgs(1),
RunE: initFiles,
}
var (
keyType string
)
// MakeInitFilesCommand returns the command to initialize a fresh Tendermint Core instance.
func MakeInitFilesCommand(conf *config.Config, logger log.Logger) *cobra.Command {
var keyType string
cmd := &cobra.Command{
Use: "init [full|validator|seed]",
Short: "Initializes a Tendermint node",
ValidArgs: []string{"full", "validator", "seed"},
// We allow for zero args so we can throw a more informative error
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("must specify a node type: tendermint init [validator|full|seed]")
}
conf.Mode = args[0]
return initFilesWithConfig(cmd.Context(), conf, logger)
},
}
func init() {
InitFilesCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
"Key type to generate privval file with. Options: ed25519, secp256k1")
}
func initFiles(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("must specify a node type: tendermint init [validator|full|seed]")
}
config.Mode = args[0]
return initFilesWithConfig(cmd.Context(), config)
return cmd
}
func initFilesWithConfig(ctx context.Context, config *cfg.Config) error {
func initFilesWithConfig(ctx context.Context, conf *config.Config, logger log.Logger) error {
var (
pv *privval.FilePV
err error
pv *privval.FilePV
err error
keyType string
)
if config.Mode == cfg.ModeValidator {
if conf.Mode == config.ModeValidator {
// private validator
privValKeyFile := config.PrivValidator.KeyFile()
privValStateFile := config.PrivValidator.StateFile()
privValKeyFile := conf.PrivValidator.KeyFile()
privValStateFile := conf.PrivValidator.StateFile()
if tmos.FileExists(privValKeyFile) {
pv, err = privval.LoadFilePV(privValKeyFile, privValStateFile)
if err != nil {
@ -73,7 +72,7 @@ func initFilesWithConfig(ctx context.Context, config *cfg.Config) error {
}
}
nodeKeyFile := config.NodeKeyFile()
nodeKeyFile := conf.NodeKeyFile()
if tmos.FileExists(nodeKeyFile) {
logger.Info("Found node key", "path", nodeKeyFile)
} else {
@ -84,7 +83,7 @@ func initFilesWithConfig(ctx context.Context, config *cfg.Config) error {
}
// genesis file
genFile := config.GenesisFile()
genFile := conf.GenesisFile()
if tmos.FileExists(genFile) {
logger.Info("Found genesis file", "path", genFile)
} else {
@ -123,10 +122,10 @@ func initFilesWithConfig(ctx context.Context, config *cfg.Config) error {
}
// write config file
if err := cfg.WriteConfigFile(config.RootDir, config); err != nil {
if err := config.WriteConfigFile(conf.RootDir, conf); err != nil {
return err
}
logger.Info("Generated config", "mode", config.Mode)
logger.Info("Generated config", "mode", conf.Mode)
return nil
}

+ 29
- 32
cmd/tendermint/commands/inspect.go View File

@ -6,14 +6,17 @@ import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/internal/inspect"
"github.com/tendermint/tendermint/libs/log"
)
// InspectCmd is the command for starting an inspect server.
var InspectCmd = &cobra.Command{
Use: "inspect",
Short: "Run an inspect server for investigating Tendermint state",
Long: `
// InspectCmd constructs the command to start an inspect server.
func MakeInspectCommand(conf *config.Config, logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "inspect",
Short: "Run an inspect server for investigating Tendermint state",
Long: `
inspect runs a subset of Tendermint's RPC endpoints that are useful for debugging
issues with Tendermint.
@ -22,33 +25,27 @@ var InspectCmd = &cobra.Command{
The inspect command can be used to query the block and state store using Tendermint
RPC calls to debug issues of inconsistent state.
`,
RunE: runInspect,
}
func init() {
InspectCmd.Flags().
String("rpc.laddr",
config.RPC.ListenAddress, "RPC listenener address. Port required")
InspectCmd.Flags().
String("db-backend",
config.DBBackend, "database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb")
InspectCmd.Flags().
String("db-dir", config.DBPath, "database directory")
}
func runInspect(cmd *cobra.Command, args []string) error {
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()
ins, err := inspect.NewFromConfig(logger, config)
if err != nil {
return err
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()
ins, err := inspect.NewFromConfig(logger, conf)
if err != nil {
return err
}
logger.Info("starting inspect server")
if err := ins.Run(ctx); err != nil {
return err
}
return nil
},
}
cmd.Flags().String("rpc.laddr",
conf.RPC.ListenAddress, "RPC listenener address. Port required")
cmd.Flags().String("db-backend",
conf.DBBackend, "database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb")
cmd.Flags().String("db-dir", conf.DBPath, "database directory")
logger.Info("starting inspect server")
if err := ins.Run(ctx); err != nil {
return err
}
return nil
return cmd
}

+ 4
- 3
cmd/tendermint/commands/key_migrate.go View File

@ -6,10 +6,11 @@ import (
"github.com/spf13/cobra"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/scripts/keymigrate"
)
func MakeKeyMigrateCommand() *cobra.Command {
func MakeKeyMigrateCommand(conf *cfg.Config, logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "key-migrate",
Short: "Run Database key migration",
@ -38,7 +39,7 @@ func MakeKeyMigrateCommand() *cobra.Command {
db, err := cfg.DefaultDBProvider(&cfg.DBContext{
ID: dbctx,
Config: config,
Config: conf,
})
if err != nil {
@ -58,7 +59,7 @@ func MakeKeyMigrateCommand() *cobra.Command {
}
// allow database info to be overridden via cli
addDBFlags(cmd)
addDBFlags(cmd, conf)
return cmd
}

+ 166
- 169
cmd/tendermint/commands/light.go View File

@ -15,6 +15,7 @@ import (
"github.com/spf13/cobra"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
tmmath "github.com/tendermint/tendermint/libs/math"
"github.com/tendermint/tendermint/light"
@ -24,11 +25,58 @@ import (
rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
)
// LightCmd represents the base command when called without any subcommands
var LightCmd = &cobra.Command{
Use: "light [chainID]",
Short: "Run a light client proxy server, verifying Tendermint rpc",
Long: `Run a light client proxy server, verifying Tendermint rpc.
// LightCmd constructs the base command called when invoked without any subcommands.
func MakeLightCommand(conf *config.Config, logger log.Logger) *cobra.Command {
var (
listenAddr string
primaryAddr string
witnessAddrsJoined string
chainID string
dir string
maxOpenConnections int
sequential bool
trustingPeriod time.Duration
trustedHeight int64
trustedHash []byte
trustLevelStr string
logLevel string
logFormat string
primaryKey = []byte("primary")
witnessesKey = []byte("witnesses")
)
checkForExistingProviders := func(db dbm.DB) (string, []string, error) {
primaryBytes, err := db.Get(primaryKey)
if err != nil {
return "", []string{""}, err
}
witnessesBytes, err := db.Get(witnessesKey)
if err != nil {
return "", []string{""}, err
}
witnessesAddrs := strings.Split(string(witnessesBytes), ",")
return string(primaryBytes), witnessesAddrs, nil
}
saveProviders := func(db dbm.DB, primaryAddr, witnessesAddrs string) error {
err := db.Set(primaryKey, []byte(primaryAddr))
if err != nil {
return fmt.Errorf("failed to save primary provider: %w", err)
}
err = db.Set(witnessesKey, []byte(witnessesAddrs))
if err != nil {
return fmt.Errorf("failed to save witness providers: %w", err)
}
return nil
}
cmd := &cobra.Command{
Use: "light [chainID]",
Short: "Run a light client proxy server, verifying Tendermint rpc",
Long: `Run a light client proxy server, verifying Tendermint rpc.
All calls that can be tracked back to a block header by a proof
will be verified before passing them back to the caller. Other than
@ -46,185 +94,134 @@ When /abci_query is called, the Merkle key path format is:
Please verify with your application that this Merkle key format is used (true
for applications built w/ Cosmos SDK).
`,
RunE: runProxy,
Args: cobra.ExactArgs(1),
Example: `light cosmoshub-3 -p http://52.57.29.196:26657 -w http://public-seed-node.cosmoshub.certus.one:26657
RunE: func(cmd *cobra.Command, args []string) error {
chainID = args[0]
logger.Info("Creating client...", "chainID", chainID)
var witnessesAddrs []string
if witnessAddrsJoined != "" {
witnessesAddrs = strings.Split(witnessAddrsJoined, ",")
}
lightDB, err := dbm.NewGoLevelDB("light-client-db", dir)
if err != nil {
return fmt.Errorf("can't create a db: %w", err)
}
// create a prefixed db on the chainID
db := dbm.NewPrefixDB(lightDB, []byte(chainID))
if primaryAddr == "" { // check to see if we can start from an existing state
var err error
primaryAddr, witnessesAddrs, err = checkForExistingProviders(db)
if err != nil {
return fmt.Errorf("failed to retrieve primary or witness from db: %w", err)
}
if primaryAddr == "" {
return errors.New("no primary address was provided nor found. Please provide a primary (using -p)." +
" Run the command: tendermint light --help for more information")
}
} else {
err := saveProviders(db, primaryAddr, witnessAddrsJoined)
if err != nil {
logger.Error("Unable to save primary and or witness addresses", "err", err)
}
}
trustLevel, err := tmmath.ParseFraction(trustLevelStr)
if err != nil {
return fmt.Errorf("can't parse trust level: %w", err)
}
options := []light.Option{light.Logger(logger)}
vo := light.SkippingVerification(trustLevel)
if sequential {
vo = light.SequentialVerification()
}
options = append(options, vo)
// Initiate the light client. If the trusted store already has blocks in it, this
// will be used else we use the trusted options.
c, err := light.NewHTTPClient(
context.Background(),
chainID,
light.TrustOptions{
Period: trustingPeriod,
Height: trustedHeight,
Hash: trustedHash,
},
primaryAddr,
witnessesAddrs,
dbs.New(db),
options...,
)
if err != nil {
return err
}
cfg := rpcserver.DefaultConfig()
cfg.MaxBodyBytes = conf.RPC.MaxBodyBytes
cfg.MaxHeaderBytes = conf.RPC.MaxHeaderBytes
cfg.MaxOpenConnections = maxOpenConnections
// If necessary adjust global WriteTimeout to ensure it's greater than
// TimeoutBroadcastTxCommit.
// See https://github.com/tendermint/tendermint/issues/3435
if cfg.WriteTimeout <= conf.RPC.TimeoutBroadcastTxCommit {
cfg.WriteTimeout = conf.RPC.TimeoutBroadcastTxCommit + 1*time.Second
}
p, err := lproxy.NewProxy(c, listenAddr, primaryAddr, cfg, logger, lrpc.KeyPathFn(lrpc.DefaultMerkleKeyPathFn()))
if err != nil {
return err
}
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM)
defer cancel()
go func() {
<-ctx.Done()
p.Listener.Close()
}()
logger.Info("Starting proxy...", "laddr", listenAddr)
if err := p.ListenAndServe(ctx); err != http.ErrServerClosed {
// Error starting or closing listener:
logger.Error("proxy ListenAndServe", "err", err)
}
return nil
},
Args: cobra.ExactArgs(1),
Example: `light cosmoshub-3 -p http://52.57.29.196:26657 -w http://public-seed-node.cosmoshub.certus.one:26657
--height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948020CD`,
}
var (
listenAddr string
primaryAddr string
witnessAddrsJoined string
chainID string
dir string
maxOpenConnections int
sequential bool
trustingPeriod time.Duration
trustedHeight int64
trustedHash []byte
trustLevelStr string
logLevel string
logFormat string
primaryKey = []byte("primary")
witnessesKey = []byte("witnesses")
)
}
func init() {
LightCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888",
cmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888",
"serve the proxy on the given address")
LightCmd.Flags().StringVarP(&primaryAddr, "primary", "p", "",
cmd.Flags().StringVarP(&primaryAddr, "primary", "p", "",
"connect to a Tendermint node at this address")
LightCmd.Flags().StringVarP(&witnessAddrsJoined, "witnesses", "w", "",
cmd.Flags().StringVarP(&witnessAddrsJoined, "witnesses", "w", "",
"tendermint nodes to cross-check the primary node, comma-separated")
LightCmd.Flags().StringVarP(&dir, "dir", "d", os.ExpandEnv(filepath.Join("$HOME", ".tendermint-light")),
cmd.Flags().StringVarP(&dir, "dir", "d", os.ExpandEnv(filepath.Join("$HOME", ".tendermint-light")),
"specify the directory")
LightCmd.Flags().IntVar(
cmd.Flags().IntVar(
&maxOpenConnections,
"max-open-connections",
900,
"maximum number of simultaneous connections (including WebSocket).")
LightCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour,
cmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour,
"trusting period that headers can be verified within. Should be significantly less than the unbonding period")
LightCmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height")
LightCmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash")
LightCmd.Flags().StringVar(&logLevel, "log-level", log.LogLevelInfo, "The logging level (debug|info|warn|error|fatal)")
LightCmd.Flags().StringVar(&logFormat, "log-format", log.LogFormatPlain, "The logging format (text|json)")
LightCmd.Flags().StringVar(&trustLevelStr, "trust-level", "1/3",
cmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height")
cmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash")
cmd.Flags().StringVar(&logLevel, "log-level", log.LogLevelInfo, "The logging level (debug|info|warn|error|fatal)")
cmd.Flags().StringVar(&logFormat, "log-format", log.LogFormatPlain, "The logging format (text|json)")
cmd.Flags().StringVar(&trustLevelStr, "trust-level", "1/3",
"trust level. Must be between 1/3 and 3/3",
)
LightCmd.Flags().BoolVar(&sequential, "sequential", false,
cmd.Flags().BoolVar(&sequential, "sequential", false,
"sequential verification. Verify all headers sequentially as opposed to using skipping verification",
)
}
func runProxy(cmd *cobra.Command, args []string) error {
logger, err := log.NewDefaultLogger(logFormat, logLevel)
if err != nil {
return err
}
chainID = args[0]
logger.Info("Creating client...", "chainID", chainID)
witnessesAddrs := []string{}
if witnessAddrsJoined != "" {
witnessesAddrs = strings.Split(witnessAddrsJoined, ",")
}
lightDB, err := dbm.NewGoLevelDB("light-client-db", dir)
if err != nil {
return fmt.Errorf("can't create a db: %w", err)
}
// create a prefixed db on the chainID
db := dbm.NewPrefixDB(lightDB, []byte(chainID))
if primaryAddr == "" { // check to see if we can start from an existing state
var err error
primaryAddr, witnessesAddrs, err = checkForExistingProviders(db)
if err != nil {
return fmt.Errorf("failed to retrieve primary or witness from db: %w", err)
}
if primaryAddr == "" {
return errors.New("no primary address was provided nor found. Please provide a primary (using -p)." +
" Run the command: tendermint light --help for more information")
}
} else {
err := saveProviders(db, primaryAddr, witnessAddrsJoined)
if err != nil {
logger.Error("Unable to save primary and or witness addresses", "err", err)
}
}
trustLevel, err := tmmath.ParseFraction(trustLevelStr)
if err != nil {
return fmt.Errorf("can't parse trust level: %w", err)
}
options := []light.Option{light.Logger(logger)}
if sequential {
options = append(options, light.SequentialVerification())
} else {
options = append(options, light.SkippingVerification(trustLevel))
}
// Initiate the light client. If the trusted store already has blocks in it, this
// will be used else we use the trusted options.
c, err := light.NewHTTPClient(
context.Background(),
chainID,
light.TrustOptions{
Period: trustingPeriod,
Height: trustedHeight,
Hash: trustedHash,
},
primaryAddr,
witnessesAddrs,
dbs.New(db),
options...,
)
if err != nil {
return err
}
cfg := rpcserver.DefaultConfig()
cfg.MaxBodyBytes = config.RPC.MaxBodyBytes
cfg.MaxHeaderBytes = config.RPC.MaxHeaderBytes
cfg.MaxOpenConnections = maxOpenConnections
// If necessary adjust global WriteTimeout to ensure it's greater than
// TimeoutBroadcastTxCommit.
// See https://github.com/tendermint/tendermint/issues/3435
if cfg.WriteTimeout <= config.RPC.TimeoutBroadcastTxCommit {
cfg.WriteTimeout = config.RPC.TimeoutBroadcastTxCommit + 1*time.Second
}
return cmd
p, err := lproxy.NewProxy(c, listenAddr, primaryAddr, cfg, logger, lrpc.KeyPathFn(lrpc.DefaultMerkleKeyPathFn()))
if err != nil {
return err
}
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM)
defer cancel()
go func() {
<-ctx.Done()
p.Listener.Close()
}()
logger.Info("Starting proxy...", "laddr", listenAddr)
if err := p.ListenAndServe(ctx); err != http.ErrServerClosed {
// Error starting or closing listener:
logger.Error("proxy ListenAndServe", "err", err)
}
return nil
}
func checkForExistingProviders(db dbm.DB) (string, []string, error) {
primaryBytes, err := db.Get(primaryKey)
if err != nil {
return "", []string{""}, err
}
witnessesBytes, err := db.Get(witnessesKey)
if err != nil {
return "", []string{""}, err
}
witnessesAddrs := strings.Split(string(witnessesBytes), ",")
return string(primaryBytes), witnessesAddrs, nil
}
func saveProviders(db dbm.DB, primaryAddr, witnessesAddrs string) error {
err := db.Set(primaryKey, []byte(primaryAddr))
if err != nil {
return fmt.Errorf("failed to save primary provider: %w", err)
}
err = db.Set(witnessesKey, []byte(witnessesAddrs))
if err != nil {
return fmt.Errorf("failed to save witness providers: %w", err)
}
return nil
}

+ 83
- 61
cmd/tendermint/commands/reindex_event.go View File

@ -17,6 +17,7 @@ import (
"github.com/tendermint/tendermint/internal/state/indexer/sink/kv"
"github.com/tendermint/tendermint/internal/state/indexer/sink/psql"
"github.com/tendermint/tendermint/internal/store"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/rpc/coretypes"
"github.com/tendermint/tendermint/types"
@ -26,59 +27,68 @@ const (
reindexFailed = "event re-index failed: "
)
// ReIndexEventCmd allows re-index the event by given block height interval
var ReIndexEventCmd = &cobra.Command{
Use: "reindex-event",
Short: "reindex events to the event store backends",
Long: `
// MakeReindexEventCommand constructs a command to re-index events in a block height interval.
func MakeReindexEventCommand(conf *tmcfg.Config, logger log.Logger) *cobra.Command {
var (
startHeight int64
endHeight int64
)
cmd := &cobra.Command{
Use: "reindex-event",
Short: "reindex events to the event store backends",
Long: `
reindex-event is an offline tooling to re-index block and tx events to the eventsinks,
you can run this command when the event store backend dropped/disconnected or you want to
replace the backend. The default start-height is 0, meaning the tooling will start
reindex from the base block height(inclusive); and the default end-height is 0, meaning
you can run this command when the event store backend dropped/disconnected or you want to
replace the backend. The default start-height is 0, meaning the tooling will start
reindex from the base block height(inclusive); and the default end-height is 0, meaning
the tooling will reindex until the latest block height(inclusive). User can omit
either or both arguments.
`,
Example: `
Example: `
tendermint reindex-event
tendermint reindex-event --start-height 2
tendermint reindex-event --end-height 10
tendermint reindex-event --start-height 2 --end-height 10
`,
Run: func(cmd *cobra.Command, args []string) {
bs, ss, err := loadStateAndBlockStore(config)
if err != nil {
fmt.Println(reindexFailed, err)
return
}
if err := checkValidHeight(bs); err != nil {
fmt.Println(reindexFailed, err)
return
}
RunE: func(cmd *cobra.Command, args []string) error {
bs, ss, err := loadStateAndBlockStore(conf)
if err != nil {
return fmt.Errorf("%s: %w", reindexFailed, err)
}
es, err := loadEventSinks(config)
if err != nil {
fmt.Println(reindexFailed, err)
return
}
cvhArgs := checkValidHeightArgs{
startHeight: startHeight,
endHeight: endHeight,
}
if err := checkValidHeight(bs, cvhArgs); err != nil {
return fmt.Errorf("%s: %w", reindexFailed, err)
}
if err = eventReIndex(cmd, es, bs, ss); err != nil {
fmt.Println(reindexFailed, err)
return
}
es, err := loadEventSinks(conf)
if err != nil {
return fmt.Errorf("%s: %w", reindexFailed, err)
}
fmt.Println("event re-index finished")
},
}
riArgs := eventReIndexArgs{
startHeight: startHeight,
endHeight: endHeight,
sinks: es,
blockStore: bs,
stateStore: ss,
}
if err := eventReIndex(cmd, riArgs); err != nil {
return fmt.Errorf("%s: %w", reindexFailed, err)
}
var (
startHeight int64
endHeight int64
)
logger.Info("event re-index finished")
return nil
},
}
func init() {
ReIndexEventCmd.Flags().Int64Var(&startHeight, "start-height", 0, "the block height would like to start for re-index")
ReIndexEventCmd.Flags().Int64Var(&endHeight, "end-height", 0, "the block height would like to finish for re-index")
cmd.Flags().Int64Var(&startHeight, "start-height", 0, "the block height would like to start for re-index")
cmd.Flags().Int64Var(&endHeight, "end-height", 0, "the block height would like to finish for re-index")
return cmd
}
func loadEventSinks(cfg *tmcfg.Config) ([]indexer.EventSink, error) {
@ -109,7 +119,7 @@ func loadEventSinks(cfg *tmcfg.Config) ([]indexer.EventSink, error) {
if conn == "" {
return nil, errors.New("the psql connection settings cannot be empty")
}
es, err := psql.NewEventSink(conn, chainID)
es, err := psql.NewEventSink(conn, cfg.ChainID())
if err != nil {
return nil, err
}
@ -159,24 +169,31 @@ func loadStateAndBlockStore(cfg *tmcfg.Config) (*store.BlockStore, state.Store,
return blockStore, stateStore, nil
}
func eventReIndex(cmd *cobra.Command, es []indexer.EventSink, bs state.BlockStore, ss state.Store) error {
type eventReIndexArgs struct {
startHeight int64
endHeight int64
sinks []indexer.EventSink
blockStore state.BlockStore
stateStore state.Store
}
func eventReIndex(cmd *cobra.Command, args eventReIndexArgs) error {
var bar progressbar.Bar
bar.NewOption(startHeight-1, endHeight)
bar.NewOption(args.startHeight-1, args.endHeight)
fmt.Println("start re-indexing events:")
defer bar.Finish()
for i := startHeight; i <= endHeight; i++ {
for i := args.startHeight; i <= args.endHeight; i++ {
select {
case <-cmd.Context().Done():
return fmt.Errorf("event re-index terminated at height %d: %w", i, cmd.Context().Err())
default:
b := bs.LoadBlock(i)
b := args.blockStore.LoadBlock(i)
if b == nil {
return fmt.Errorf("not able to load block at height %d from the blockstore", i)
}
r, err := ss.LoadABCIResponses(i)
r, err := args.stateStore.LoadABCIResponses(i)
if err != nil {
return fmt.Errorf("not able to load ABCI Response at height %d from the statestore", i)
}
@ -192,11 +209,11 @@ func eventReIndex(cmd *cobra.Command, es []indexer.EventSink, bs state.BlockStor
if e.NumTxs > 0 {
batch = indexer.NewBatch(e.NumTxs)
for i, tx := range b.Data.Txs {
for i := range b.Data.Txs {
tr := abcitypes.TxResult{
Height: b.Height,
Index: uint32(i),
Tx: tx,
Tx: b.Data.Txs[i],
Result: *(r.DeliverTxs[i]),
}
@ -204,7 +221,7 @@ func eventReIndex(cmd *cobra.Command, es []indexer.EventSink, bs state.BlockStor
}
}
for _, sink := range es {
for _, sink := range args.sinks {
if err := sink.IndexBlockEvents(e); err != nil {
return fmt.Errorf("block event re-index at height %d failed: %w", i, err)
}
@ -223,40 +240,45 @@ func eventReIndex(cmd *cobra.Command, es []indexer.EventSink, bs state.BlockStor
return nil
}
func checkValidHeight(bs state.BlockStore) error {
type checkValidHeightArgs struct {
startHeight int64
endHeight int64
}
func checkValidHeight(bs state.BlockStore, args checkValidHeightArgs) error {
base := bs.Base()
if startHeight == 0 {
startHeight = base
if args.startHeight == 0 {
args.startHeight = base
fmt.Printf("set the start block height to the base height of the blockstore %d \n", base)
}
if startHeight < base {
if args.startHeight < base {
return fmt.Errorf("%s (requested start height: %d, base height: %d)",
coretypes.ErrHeightNotAvailable, startHeight, base)
coretypes.ErrHeightNotAvailable, args.startHeight, base)
}
height := bs.Height()
if startHeight > height {
if args.startHeight > height {
return fmt.Errorf(
"%s (requested start height: %d, store height: %d)", coretypes.ErrHeightNotAvailable, startHeight, height)
"%s (requested start height: %d, store height: %d)", coretypes.ErrHeightNotAvailable, args.startHeight, height)
}
if endHeight == 0 || endHeight > height {
endHeight = height
if args.endHeight == 0 || args.endHeight > height {
args.endHeight = height
fmt.Printf("set the end block height to the latest height of the blockstore %d \n", height)
}
if endHeight < base {
if args.endHeight < base {
return fmt.Errorf(
"%s (requested end height: %d, base height: %d)", coretypes.ErrHeightNotAvailable, endHeight, base)
"%s (requested end height: %d, base height: %d)", coretypes.ErrHeightNotAvailable, args.endHeight, base)
}
if endHeight < startHeight {
if args.endHeight < args.startHeight {
return fmt.Errorf(
"%s (requested the end height: %d is less than the start height: %d)",
coretypes.ErrInvalidRequest, startHeight, endHeight)
coretypes.ErrInvalidRequest, args.startHeight, args.endHeight)
}
return nil


+ 20
- 12
cmd/tendermint/commands/reindex_event_test.go View File

@ -10,9 +10,10 @@ import (
"github.com/stretchr/testify/require"
abcitypes "github.com/tendermint/tendermint/abci/types"
tmcfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/internal/state/indexer"
"github.com/tendermint/tendermint/internal/state/mocks"
"github.com/tendermint/tendermint/libs/log"
prototmstate "github.com/tendermint/tendermint/proto/tendermint/state"
"github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db"
@ -25,9 +26,11 @@ const (
base int64 = 2
)
func setupReIndexEventCmd(ctx context.Context) *cobra.Command {
func setupReIndexEventCmd(ctx context.Context, conf *config.Config, logger log.Logger) *cobra.Command {
cmd := MakeReindexEventCommand(conf, logger)
reIndexEventCmd := &cobra.Command{
Use: ReIndexEventCmd.Use,
Use: cmd.Use,
Run: func(cmd *cobra.Command, args []string) {},
}
@ -68,10 +71,7 @@ func TestReIndexEventCheckHeight(t *testing.T) {
}
for _, tc := range testCases {
startHeight = tc.startHeight
endHeight = tc.endHeight
err := checkValidHeight(mockBlockStore)
err := checkValidHeight(mockBlockStore, checkValidHeightArgs{startHeight: tc.startHeight, endHeight: tc.endHeight})
if tc.validHeight {
require.NoError(t, err)
} else {
@ -97,7 +97,7 @@ func TestLoadEventSink(t *testing.T) {
}
for _, tc := range testCases {
cfg := tmcfg.TestConfig()
cfg := config.TestConfig()
cfg.TxIndex.Indexer = tc.sinks
cfg.TxIndex.PsqlConn = tc.connURL
_, err := loadEventSinks(cfg)
@ -110,7 +110,7 @@ func TestLoadEventSink(t *testing.T) {
}
func TestLoadBlockStore(t *testing.T) {
testCfg, err := tmcfg.ResetTestRoot(t.Name())
testCfg, err := config.ResetTestRoot(t.Name())
require.NoError(t, err)
testCfg.DBBackend = "goleveldb"
_, _, err = loadStateAndBlockStore(testCfg)
@ -179,12 +179,20 @@ func TestReIndexEvent(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewNopLogger()
conf := config.DefaultConfig()
for _, tc := range testCases {
startHeight = tc.startHeight
endHeight = tc.endHeight
err := eventReIndex(
setupReIndexEventCmd(ctx, conf, logger),
eventReIndexArgs{
sinks: []indexer.EventSink{mockEventSink},
blockStore: mockBlockStore,
stateStore: mockStateStore,
startHeight: tc.startHeight,
endHeight: tc.endHeight,
})
err := eventReIndex(setupReIndexEventCmd(ctx), []indexer.EventSink{mockEventSink}, mockBlockStore, mockStateStore)
if tc.reIndexErr {
require.Error(t, err)
} else {


+ 20
- 15
cmd/tendermint/commands/replay.go View File

@ -2,24 +2,29 @@ package commands
import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/internal/consensus"
"github.com/tendermint/tendermint/libs/log"
)
// ReplayCmd allows replaying of messages from the WAL.
var ReplayCmd = &cobra.Command{
Use: "replay",
Short: "Replay messages from WAL",
RunE: func(cmd *cobra.Command, args []string) error {
return consensus.RunReplayFile(cmd.Context(), logger, config.BaseConfig, config.Consensus, false)
},
// MakeReplayCommand constructs a command to replay messages from the WAL into consensus.
func MakeReplayCommand(conf *config.Config, logger log.Logger) *cobra.Command {
return &cobra.Command{
Use: "replay",
Short: "Replay messages from WAL",
RunE: func(cmd *cobra.Command, args []string) error {
return consensus.RunReplayFile(cmd.Context(), logger, conf.BaseConfig, conf.Consensus, false)
},
}
}
// ReplayConsoleCmd allows replaying of messages from the WAL in a
// console.
var ReplayConsoleCmd = &cobra.Command{
Use: "replay-console",
Short: "Replay messages from WAL in a console",
RunE: func(cmd *cobra.Command, args []string) error {
return consensus.RunReplayFile(cmd.Context(), logger, config.BaseConfig, config.Consensus, true)
},
// MakeReplayConsoleCommand constructs a command to replay WAL messages to stdout.
func MakeReplayConsoleCommand(conf *config.Config, logger log.Logger) *cobra.Command {
return &cobra.Command{
Use: "replay-console",
Short: "Replay messages from WAL in a console",
RunE: func(cmd *cobra.Command, args []string) error {
return consensus.RunReplayFile(cmd.Context(), logger, conf.BaseConfig, conf.Consensus, true)
},
}
}

+ 34
- 27
cmd/tendermint/commands/reset_priv_validator.go View File

@ -5,51 +5,58 @@ import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/types"
)
// ResetAllCmd removes the database of this Tendermint core
// instance.
var ResetAllCmd = &cobra.Command{
Use: "unsafe-reset-all",
Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state",
RunE: resetAll,
// MakeResetAllCommand constructs a command that removes the database of
// the specified Tendermint core instance.
func MakeResetAllCommand(conf *config.Config, logger log.Logger) *cobra.Command {
var keyType string
cmd := &cobra.Command{
Use: "unsafe-reset-all",
Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state",
RunE: func(cmd *cobra.Command, args []string) error {
return resetAll(conf.DBDir(), conf.PrivValidator.KeyFile(),
conf.PrivValidator.StateFile(), logger, keyType)
},
}
cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
"Key type to generate privval file with. Options: ed25519, secp256k1")
return cmd
}
var keepAddrBook bool
func MakeResetPrivateValidatorCommand(conf *config.Config, logger log.Logger) *cobra.Command {
var keyType string
func init() {
ResetAllCmd.Flags().BoolVar(&keepAddrBook, "keep-addr-book", false, "keep the address book intact")
ResetPrivValidatorCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
cmd := &cobra.Command{
Use: "unsafe-reset-priv-validator",
Short: "(unsafe) Reset this node's validator to genesis state",
RunE: func(cmd *cobra.Command, args []string) error {
return resetFilePV(conf.PrivValidator.KeyFile(), conf.PrivValidator.StateFile(), logger, keyType)
},
}
cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
"Key type to generate privval file with. Options: ed25519, secp256k1")
}
return cmd
// ResetPrivValidatorCmd resets the private validator files.
var ResetPrivValidatorCmd = &cobra.Command{
Use: "unsafe-reset-priv-validator",
Short: "(unsafe) Reset this node's validator to genesis state",
RunE: resetPrivValidator,
}
// XXX: this is totally unsafe.
// it's only suitable for testnets.
func resetAll(cmd *cobra.Command, args []string) error {
return ResetAll(config.DBDir(), config.PrivValidator.KeyFile(),
config.PrivValidator.StateFile(), logger)
}
// XXX: this is totally unsafe.
// it's only suitable for testnets.
func resetPrivValidator(cmd *cobra.Command, args []string) error {
return resetFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile(), logger)
}
// ResetAll removes address book files plus all data, and resets the privValdiator data.
// resetAll removes address book files plus all data, and resets the privValdiator data.
// Exported so other CLI tools can use it.
func ResetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger) error {
func resetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error {
if err := os.RemoveAll(dbDir); err == nil {
logger.Info("Removed all blockchain history", "dir", dbDir)
} else {
@ -59,10 +66,10 @@ func ResetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger)
if err := tmos.EnsureDir(dbDir, 0700); err != nil {
logger.Error("unable to recreate dbDir", "err", err)
}
return resetFilePV(privValKeyFile, privValStateFile, logger)
return resetFilePV(privValKeyFile, privValStateFile, logger, keyType)
}
func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) error {
func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error {
if _, err := os.Stat(privValKeyFile); err == nil {
pv, err := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile)
if err != nil {


+ 18
- 15
cmd/tendermint/commands/rollback.go View File

@ -5,14 +5,15 @@ import (
"github.com/spf13/cobra"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/internal/state"
)
var RollbackStateCmd = &cobra.Command{
Use: "rollback",
Short: "rollback tendermint state by one height",
Long: `
func MakeRollbackStateCommand(conf *config.Config) *cobra.Command {
return &cobra.Command{
Use: "rollback",
Short: "rollback tendermint state by one height",
Long: `
A state rollback is performed to recover from an incorrect application state transition,
when Tendermint has persisted an incorrect app hash and is thus unable to make
progress. Rollback overwrites a state at height n with the state at height n - 1.
@ -20,21 +21,23 @@ The application should also roll back to height n - 1. No blocks are removed, so
restarting Tendermint the transactions in block n will be re-executed against the
application.
`,
RunE: func(cmd *cobra.Command, args []string) error {
height, hash, err := RollbackState(config)
if err != nil {
return fmt.Errorf("failed to rollback state: %w", err)
}
fmt.Printf("Rolled back state to height %d and hash %X", height, hash)
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
height, hash, err := RollbackState(conf)
if err != nil {
return fmt.Errorf("failed to rollback state: %w", err)
}
fmt.Printf("Rolled back state to height %d and hash %X", height, hash)
return nil
},
}
}
// RollbackState takes the state at the current height n and overwrites it with the state
// at height n - 1. Note state here refers to tendermint state not application state.
// Returns the latest state height and app hash alongside an error if there was one.
func RollbackState(config *cfg.Config) (int64, []byte, error) {
func RollbackState(config *config.Config) (int64, []byte, error) {
// use the parsed config to load the block and state store
blockStore, stateStore, err := loadStateAndBlockStore(config)
if err != nil {


+ 37
- 40
cmd/tendermint/commands/root.go View File

@ -2,65 +2,62 @@ package commands
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
)
var (
config = cfg.DefaultConfig()
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
ctxTimeout = 4 * time.Second
)
func init() {
registerFlagsRootCmd(RootCmd)
}
func registerFlagsRootCmd(cmd *cobra.Command) {
cmd.PersistentFlags().String("log-level", config.LogLevel, "log level")
}
const ctxTimeout = 4 * time.Second
// ParseConfig retrieves the default environment configuration,
// sets up the Tendermint root and ensures that the root exists
func ParseConfig() (*cfg.Config, error) {
conf := cfg.DefaultConfig()
err := viper.Unmarshal(conf)
if err != nil {
func ParseConfig(conf *config.Config) (*config.Config, error) {
if err := viper.Unmarshal(conf); err != nil {
return nil, err
}
conf.SetRoot(conf.RootDir)
cfg.EnsureRoot(conf.RootDir)
if err := conf.ValidateBasic(); err != nil {
return nil, fmt.Errorf("error in config file: %w", err)
}
return conf, nil
}
// RootCmd is the root command for Tendermint core.
var RootCmd = &cobra.Command{
Use: "tendermint",
Short: "BFT state machine replication for applications in any programming languages",
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
if cmd.Name() == VersionCmd.Name() {
return nil
}
config, err = ParseConfig()
if err != nil {
return err
}
// RootCommand constructs the root command-line entry point for Tendermint core.
func RootCommand(conf *config.Config, logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "tendermint",
Short: "BFT state machine replication for applications in any programming languages",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if cmd.Name() == VersionCmd.Name() {
return nil
}
if err := cli.BindFlagsLoadViper(cmd, args); err != nil {
return err
}
pconf, err := ParseConfig(conf)
if err != nil {
return err
}
*conf = *pconf
config.EnsureRoot(conf.RootDir)
logger, err = log.NewDefaultLogger(config.LogFormat, config.LogLevel)
if err != nil {
return err
}
logger = logger.With("module", "main")
return nil
},
return nil
},
}
cmd.PersistentFlags().StringP(cli.HomeFlag, "", os.ExpandEnv(filepath.Join("$HOME", config.DefaultTendermintDir)), "directory for config and data")
cmd.PersistentFlags().Bool(cli.TraceFlag, false, "print out full stack trace on errors")
cmd.PersistentFlags().String("log-level", conf.LogLevel, "log level")
cobra.OnInitialize(func() { cli.InitEnv("TM") })
return cmd
}

+ 60
- 57
cmd/tendermint/commands/root_test.go View File

@ -4,7 +4,6 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/spf13/cobra"
@ -14,42 +13,42 @@ import (
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
)
// clearConfig clears env vars, the given root dir, and resets viper.
func clearConfig(t *testing.T, dir string) {
func clearConfig(t *testing.T, dir string) *cfg.Config {
t.Helper()
require.NoError(t, os.Unsetenv("TMHOME"))
require.NoError(t, os.Unsetenv("TM_HOME"))
require.NoError(t, os.RemoveAll(dir))
viper.Reset()
config = cfg.DefaultConfig()
conf := cfg.DefaultConfig()
conf.RootDir = dir
return conf
}
// prepare new rootCmd
func testRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: RootCmd.Use,
PersistentPreRunE: RootCmd.PersistentPreRunE,
Run: func(cmd *cobra.Command, args []string) {},
}
registerFlagsRootCmd(rootCmd)
func testRootCmd(conf *cfg.Config) *cobra.Command {
logger := log.NewNopLogger()
cmd := RootCommand(conf, logger)
cmd.RunE = func(cmd *cobra.Command, args []string) error { return nil }
var l string
rootCmd.PersistentFlags().String("log", l, "Log")
return rootCmd
cmd.PersistentFlags().String("log", l, "Log")
return cmd
}
func testSetup(t *testing.T, rootDir string, args []string, env map[string]string) error {
func testSetup(t *testing.T, conf *cfg.Config, args []string, env map[string]string) error {
t.Helper()
clearConfig(t, rootDir)
rootCmd := testRootCmd()
cmd := cli.PrepareBaseCmd(rootCmd, "TM", rootDir)
cmd := testRootCmd(conf)
viper.Set(cli.HomeFlag, conf.RootDir)
// run with the args and env
args = append([]string{rootCmd.Use}, args...)
args = append([]string{cmd.Use}, args...)
return cli.RunWithArgs(cmd, args, env)
}
@ -67,22 +66,25 @@ func TestRootHome(t *testing.T) {
}
for i, tc := range cases {
idxString := strconv.Itoa(i)
t.Run(fmt.Sprint(i), func(t *testing.T) {
conf := clearConfig(t, tc.root)
err := testSetup(t, defaultRoot, tc.args, tc.env)
require.NoError(t, err, idxString)
err := testSetup(t, conf, tc.args, tc.env)
require.NoError(t, err)
assert.Equal(t, tc.root, config.RootDir, idxString)
assert.Equal(t, tc.root, config.P2P.RootDir, idxString)
assert.Equal(t, tc.root, config.Consensus.RootDir, idxString)
assert.Equal(t, tc.root, config.Mempool.RootDir, idxString)
require.Equal(t, tc.root, conf.RootDir)
require.Equal(t, tc.root, conf.P2P.RootDir)
require.Equal(t, tc.root, conf.Consensus.RootDir)
require.Equal(t, tc.root, conf.Mempool.RootDir)
})
}
}
func TestRootFlagsEnv(t *testing.T) {
// defaults
defaults := cfg.DefaultConfig()
defaultDir := t.TempDir()
defaultLogLvl := defaults.LogLevel
cases := []struct {
@ -97,19 +99,20 @@ func TestRootFlagsEnv(t *testing.T) {
{nil, map[string]string{"TM_LOG_LEVEL": "debug"}, "debug"}, // right env
}
defaultRoot := t.TempDir()
for i, tc := range cases {
idxString := strconv.Itoa(i)
t.Run(fmt.Sprint(i), func(t *testing.T) {
conf := clearConfig(t, defaultDir)
err := testSetup(t, conf, tc.args, tc.env)
require.NoError(t, err)
err := testSetup(t, defaultRoot, tc.args, tc.env)
require.NoError(t, err, idxString)
assert.Equal(t, tc.logLevel, conf.LogLevel)
})
assert.Equal(t, tc.logLevel, config.LogLevel, idxString)
}
}
func TestRootConfig(t *testing.T) {
// write non-default config
nonDefaultLogLvl := "debug"
cvals := map[string]string{
@ -117,9 +120,8 @@ func TestRootConfig(t *testing.T) {
}
cases := []struct {
args []string
env map[string]string
args []string
env map[string]string
logLvl string
}{
{nil, nil, nonDefaultLogLvl}, // should load config
@ -128,29 +130,30 @@ func TestRootConfig(t *testing.T) {
}
for i, tc := range cases {
defaultRoot := t.TempDir()
idxString := strconv.Itoa(i)
clearConfig(t, defaultRoot)
// XXX: path must match cfg.defaultConfigPath
configFilePath := filepath.Join(defaultRoot, "config")
err := tmos.EnsureDir(configFilePath, 0700)
require.NoError(t, err)
// write the non-defaults to a different path
// TODO: support writing sub configs so we can test that too
err = WriteConfigVals(configFilePath, cvals)
require.NoError(t, err)
rootCmd := testRootCmd()
cmd := cli.PrepareBaseCmd(rootCmd, "TM", defaultRoot)
// run with the args and env
tc.args = append([]string{rootCmd.Use}, tc.args...)
err = cli.RunWithArgs(cmd, tc.args, tc.env)
require.NoError(t, err, idxString)
assert.Equal(t, tc.logLvl, config.LogLevel, idxString)
t.Run(fmt.Sprint(i), func(t *testing.T) {
defaultRoot := t.TempDir()
conf := clearConfig(t, defaultRoot)
conf.LogLevel = tc.logLvl
// XXX: path must match cfg.defaultConfigPath
configFilePath := filepath.Join(defaultRoot, "config")
err := tmos.EnsureDir(configFilePath, 0700)
require.NoError(t, err)
// write the non-defaults to a different path
// TODO: support writing sub configs so we can test that too
err = WriteConfigVals(configFilePath, cvals)
require.NoError(t, err)
cmd := testRootCmd(conf)
// run with the args and env
tc.args = append([]string{cmd.Use}, tc.args...)
err = cli.RunWithArgs(cmd, tc.args, tc.env)
require.NoError(t, err)
require.Equal(t, tc.logLvl, conf.LogLevel)
})
}
}


+ 29
- 28
cmd/tendermint/commands/run_node.go View File

@ -12,25 +12,26 @@ import (
"github.com/spf13/cobra"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
)
var (
genesisHash []byte
)
// AddNodeFlags exposes some common configuration options on the command-line
// These are exposed for convenience of commands embedding a tendermint node
func AddNodeFlags(cmd *cobra.Command) {
// AddNodeFlags exposes some common configuration options from conf in the flag
// set for cmd. This is a convenience for commands embedding a Tendermint node.
func AddNodeFlags(cmd *cobra.Command, conf *cfg.Config) {
// bind flags
cmd.Flags().String("moniker", config.Moniker, "node name")
cmd.Flags().String("moniker", conf.Moniker, "node name")
// mode flags
cmd.Flags().String("mode", config.Mode, "node mode (full | validator | seed)")
cmd.Flags().String("mode", conf.Mode, "node mode (full | validator | seed)")
// priv val flags
cmd.Flags().String(
"priv-validator-laddr",
config.PrivValidator.ListenAddr,
conf.PrivValidator.ListenAddr,
"socket address to listen on for connections from external priv-validator process")
// node flags
@ -40,74 +41,74 @@ func AddNodeFlags(cmd *cobra.Command) {
"genesis-hash",
[]byte{},
"optional SHA-256 hash of the genesis file")
cmd.Flags().Int64("consensus.double-sign-check-height", config.Consensus.DoubleSignCheckHeight,
cmd.Flags().Int64("consensus.double-sign-check-height", conf.Consensus.DoubleSignCheckHeight,
"how many blocks to look back to check existence of the node's "+
"consensus votes before joining consensus")
// abci flags
cmd.Flags().String(
"proxy-app",
config.ProxyApp,
conf.ProxyApp,
"proxy app address, or one of: 'kvstore',"+
" 'persistent_kvstore', 'e2e' or 'noop' for local testing.")
cmd.Flags().String("abci", config.ABCI, "specify abci transport (socket | grpc)")
cmd.Flags().String("abci", conf.ABCI, "specify abci transport (socket | grpc)")
// rpc flags
cmd.Flags().String("rpc.laddr", config.RPC.ListenAddress, "RPC listen address. Port required")
cmd.Flags().Bool("rpc.unsafe", config.RPC.Unsafe, "enabled unsafe rpc methods")
cmd.Flags().String("rpc.pprof-laddr", config.RPC.PprofListenAddress, "pprof listen address (https://golang.org/pkg/net/http/pprof)")
cmd.Flags().String("rpc.laddr", conf.RPC.ListenAddress, "RPC listen address. Port required")
cmd.Flags().Bool("rpc.unsafe", conf.RPC.Unsafe, "enabled unsafe rpc methods")
cmd.Flags().String("rpc.pprof-laddr", conf.RPC.PprofListenAddress, "pprof listen address (https://golang.org/pkg/net/http/pprof)")
// p2p flags
cmd.Flags().String(
"p2p.laddr",
config.P2P.ListenAddress,
conf.P2P.ListenAddress,
"node listen address. (0.0.0.0:0 means any interface, any port)")
cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "comma-delimited ID@host:port seed nodes") //nolint: staticcheck
cmd.Flags().String("p2p.persistent-peers", config.P2P.PersistentPeers, "comma-delimited ID@host:port persistent peers")
cmd.Flags().Bool("p2p.upnp", config.P2P.UPNP, "enable/disable UPNP port forwarding")
cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "enable/disable Peer-Exchange")
cmd.Flags().String("p2p.private-peer-ids", config.P2P.PrivatePeerIDs, "comma-delimited private peer IDs")
cmd.Flags().String("p2p.seeds", conf.P2P.Seeds, "comma-delimited ID@host:port seed nodes") //nolint: staticcheck
cmd.Flags().String("p2p.persistent-peers", conf.P2P.PersistentPeers, "comma-delimited ID@host:port persistent peers")
cmd.Flags().Bool("p2p.upnp", conf.P2P.UPNP, "enable/disable UPNP port forwarding")
cmd.Flags().Bool("p2p.pex", conf.P2P.PexReactor, "enable/disable Peer-Exchange")
cmd.Flags().String("p2p.private-peer-ids", conf.P2P.PrivatePeerIDs, "comma-delimited private peer IDs")
// consensus flags
cmd.Flags().Bool(
"consensus.create-empty-blocks",
config.Consensus.CreateEmptyBlocks,
conf.Consensus.CreateEmptyBlocks,
"set this to false to only produce blocks when there are txs or when the AppHash changes")
cmd.Flags().String(
"consensus.create-empty-blocks-interval",
config.Consensus.CreateEmptyBlocksInterval.String(),
conf.Consensus.CreateEmptyBlocksInterval.String(),
"the possible interval between empty blocks")
addDBFlags(cmd)
addDBFlags(cmd, conf)
}
func addDBFlags(cmd *cobra.Command) {
func addDBFlags(cmd *cobra.Command, conf *cfg.Config) {
cmd.Flags().String(
"db-backend",
config.DBBackend,
conf.DBBackend,
"database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb")
cmd.Flags().String(
"db-dir",
config.DBPath,
conf.DBPath,
"database directory")
}
// NewRunNodeCmd returns the command that allows the CLI to start a node.
// It can be used with a custom PrivValidator and in-process ABCI application.
func NewRunNodeCmd(nodeProvider cfg.ServiceProvider) *cobra.Command {
func NewRunNodeCmd(nodeProvider cfg.ServiceProvider, conf *cfg.Config, logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Aliases: []string{"node", "run"},
Short: "Run the tendermint node",
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkGenesisHash(config); err != nil {
if err := checkGenesisHash(conf); err != nil {
return err
}
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM)
defer cancel()
n, err := nodeProvider(ctx, config, logger)
n, err := nodeProvider(ctx, conf, logger)
if err != nil {
return fmt.Errorf("failed to create node: %w", err)
}
@ -123,7 +124,7 @@ func NewRunNodeCmd(nodeProvider cfg.ServiceProvider) *cobra.Command {
},
}
AddNodeFlags(cmd)
AddNodeFlags(cmd, conf)
return cmd
}


+ 14
- 13
cmd/tendermint/commands/show_node_id.go View File

@ -4,21 +4,22 @@ import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/config"
)
// ShowNodeIDCmd dumps node's ID to the standard output.
var ShowNodeIDCmd = &cobra.Command{
Use: "show-node-id",
Short: "Show this node's ID",
RunE: showNodeID,
}
// MakeShowNodeIDCommand constructs a command to dump the node ID to stdout.
func MakeShowNodeIDCommand(conf *config.Config) *cobra.Command {
return &cobra.Command{
Use: "show-node-id",
Short: "Show this node's ID",
RunE: func(cmd *cobra.Command, args []string) error {
nodeKeyID, err := conf.LoadNodeKeyID()
if err != nil {
return err
}
func showNodeID(cmd *cobra.Command, args []string) error {
nodeKeyID, err := config.LoadNodeKeyID()
if err != nil {
return err
fmt.Println(nodeKeyID)
return nil
},
}
fmt.Println(nodeKeyID)
return nil
}

+ 56
- 53
cmd/tendermint/commands/show_validator.go View File

@ -6,75 +6,78 @@ import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/internal/jsontypes"
"github.com/tendermint/tendermint/libs/log"
tmnet "github.com/tendermint/tendermint/libs/net"
tmos "github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/privval"
tmgrpc "github.com/tendermint/tendermint/privval/grpc"
)
// ShowValidatorCmd adds capabilities for showing the validator info.
var ShowValidatorCmd = &cobra.Command{
Use: "show-validator",
Short: "Show this node's validator info",
RunE: showValidator,
}
// MakeShowValidatorCommand constructs a command to show the validator info.
func MakeShowValidatorCommand(conf *config.Config, logger log.Logger) *cobra.Command {
return &cobra.Command{
Use: "show-validator",
Short: "Show this node's validator info",
RunE: func(cmd *cobra.Command, args []string) error {
var (
pubKey crypto.PubKey
err error
bctx = cmd.Context()
)
//TODO: remove once gRPC is the only supported protocol
protocol, _ := tmnet.ProtocolAndAddress(conf.PrivValidator.ListenAddr)
switch protocol {
case "grpc":
pvsc, err := tmgrpc.DialRemoteSigner(
bctx,
conf.PrivValidator,
conf.ChainID(),
logger,
conf.Instrumentation.Prometheus,
)
if err != nil {
return fmt.Errorf("can't connect to remote validator %w", err)
}
func showValidator(cmd *cobra.Command, args []string) error {
var (
pubKey crypto.PubKey
err error
bctx = cmd.Context()
)
//TODO: remove once gRPC is the only supported protocol
protocol, _ := tmnet.ProtocolAndAddress(config.PrivValidator.ListenAddr)
switch protocol {
case "grpc":
pvsc, err := tmgrpc.DialRemoteSigner(
bctx,
config.PrivValidator,
config.ChainID(),
logger,
config.Instrumentation.Prometheus,
)
if err != nil {
return fmt.Errorf("can't connect to remote validator %w", err)
}
ctx, cancel := context.WithTimeout(bctx, ctxTimeout)
defer cancel()
ctx, cancel := context.WithTimeout(bctx, ctxTimeout)
defer cancel()
pubKey, err = pvsc.GetPubKey(ctx)
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
}
default:
pubKey, err = pvsc.GetPubKey(ctx)
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
}
default:
keyFilePath := conf.PrivValidator.KeyFile()
if !tmos.FileExists(keyFilePath) {
return fmt.Errorf("private validator file %s does not exist", keyFilePath)
}
keyFilePath := config.PrivValidator.KeyFile()
if !tmos.FileExists(keyFilePath) {
return fmt.Errorf("private validator file %s does not exist", keyFilePath)
}
pv, err := privval.LoadFilePV(keyFilePath, conf.PrivValidator.StateFile())
if err != nil {
return err
}
pv, err := privval.LoadFilePV(keyFilePath, config.PrivValidator.StateFile())
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(bctx, ctxTimeout)
defer cancel()
ctx, cancel := context.WithTimeout(bctx, ctxTimeout)
defer cancel()
pubKey, err = pv.GetPubKey(ctx)
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
}
}
pubKey, err = pv.GetPubKey(ctx)
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
}
}
bz, err := jsontypes.Marshal(pubKey)
if err != nil {
return fmt.Errorf("failed to marshal private validator pubkey: %w", err)
}
bz, err := jsontypes.Marshal(pubKey)
if err != nil {
return fmt.Errorf("failed to marshal private validator pubkey: %w", err)
fmt.Println(string(bz))
return nil
},
}
fmt.Println(string(bz))
return nil
}

+ 226
- 192
cmd/tendermint/commands/testnet.go View File

@ -13,285 +13,319 @@ import (
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/log"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtime "github.com/tendermint/tendermint/libs/time"
"github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/types"
)
var (
nValidators int
nNonValidators int
initialHeight int64
configFile string
outputDir string
nodeDirPrefix string
populatePersistentPeers bool
hostnamePrefix string
hostnameSuffix string
startingIPAddress string
hostnames []string
p2pPort int
randomMonikers bool
)
const (
nodeDirPerm = 0755
)
func init() {
TestnetFilesCmd.Flags().IntVar(&nValidators, "v", 4,
// MakeTestnetFilesCommand constructs a command to generate testnet config files.
func MakeTestnetFilesCommand(conf *cfg.Config, logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "testnet",
Short: "Initialize files for a Tendermint testnet",
Long: `testnet will create "v" + "n" number of directories and populate each with
necessary files (private validator, genesis, config, etc.).
Note, strict routability for addresses is turned off in the config file.
Optionally, it will fill in persistent-peers list in config file using either hostnames or IPs.
Example:
tendermint testnet --v 4 --o ./output --populate-persistent-peers --starting-ip-address 192.168.10.2
`,
}
var (
nValidators int
nNonValidators int
initialHeight int64
configFile string
outputDir string
nodeDirPrefix string
populatePersistentPeers bool
hostnamePrefix string
hostnameSuffix string
startingIPAddress string
hostnames []string
p2pPort int
randomMonikers bool
keyType string
)
cmd.Flags().IntVar(&nValidators, "v", 4,
"number of validators to initialize the testnet with")
TestnetFilesCmd.Flags().StringVar(&configFile, "config", "",
cmd.Flags().StringVar(&configFile, "config", "",
"config file to use (note some options may be overwritten)")
TestnetFilesCmd.Flags().IntVar(&nNonValidators, "n", 0,
cmd.Flags().IntVar(&nNonValidators, "n", 0,
"number of non-validators to initialize the testnet with")
TestnetFilesCmd.Flags().StringVar(&outputDir, "o", "./mytestnet",
cmd.Flags().StringVar(&outputDir, "o", "./mytestnet",
"directory to store initialization data for the testnet")
TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node",
cmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node",
"prefix the directory name for each node with (node results in node0, node1, ...)")
TestnetFilesCmd.Flags().Int64Var(&initialHeight, "initial-height", 0,
cmd.Flags().Int64Var(&initialHeight, "initial-height", 0,
"initial height of the first block")
TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true,
cmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true,
"update config of each node with the list of persistent peers build using either"+
" hostname-prefix or"+
" starting-ip-address")
TestnetFilesCmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node",
cmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node",
"hostname prefix (\"node\" results in persistent peers list ID0@node0:26656, ID1@node1:26656, ...)")
TestnetFilesCmd.Flags().StringVar(&hostnameSuffix, "hostname-suffix", "",
cmd.Flags().StringVar(&hostnameSuffix, "hostname-suffix", "",
"hostname suffix ("+
"\".xyz.com\""+
" results in persistent peers list ID0@node0.xyz.com:26656, ID1@node1.xyz.com:26656, ...)")
TestnetFilesCmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "",
cmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "",
"starting IP address ("+
"\"192.168.0.1\""+
" results in persistent peers list ID0@192.168.0.1:26656, ID1@192.168.0.2:26656, ...)")
TestnetFilesCmd.Flags().StringArrayVar(&hostnames, "hostname", []string{},
cmd.Flags().StringArrayVar(&hostnames, "hostname", []string{},
"manually override all hostnames of validators and non-validators (use --hostname multiple times for multiple hosts)")
TestnetFilesCmd.Flags().IntVar(&p2pPort, "p2p-port", 26656,
cmd.Flags().IntVar(&p2pPort, "p2p-port", 26656,
"P2P Port")
TestnetFilesCmd.Flags().BoolVar(&randomMonikers, "random-monikers", false,
cmd.Flags().BoolVar(&randomMonikers, "random-monikers", false,
"randomize the moniker for each generated node")
TestnetFilesCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519,
"Key type to generate privval file with. Options: ed25519, secp256k1")
}
// TestnetFilesCmd allows initialisation of files for a Tendermint testnet.
var TestnetFilesCmd = &cobra.Command{
Use: "testnet",
Short: "Initialize files for a Tendermint testnet",
Long: `testnet will create "v" + "n" number of directories and populate each with
necessary files (private validator, genesis, config, etc.).
Note, strict routability for addresses is turned off in the config file.
Optionally, it will fill in persistent-peers list in config file using either hostnames or IPs.
cmd.RunE = func(cmd *cobra.Command, args []string) error {
if len(hostnames) > 0 && len(hostnames) != (nValidators+nNonValidators) {
return fmt.Errorf(
"testnet needs precisely %d hostnames (number of validators plus non-validators) if --hostname parameter is used",
nValidators+nNonValidators,
)
}
Example:
// set mode to validator for testnet
config := cfg.DefaultValidatorConfig()
tendermint testnet --v 4 --o ./output --populate-persistent-peers --starting-ip-address 192.168.10.2
`,
RunE: testnetFiles,
}
// overwrite default config if set and valid
if configFile != "" {
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
return err
}
if err := viper.Unmarshal(config); err != nil {
return err
}
if err := config.ValidateBasic(); err != nil {
return err
}
}
func testnetFiles(cmd *cobra.Command, args []string) error {
if len(hostnames) > 0 && len(hostnames) != (nValidators+nNonValidators) {
return fmt.Errorf(
"testnet needs precisely %d hostnames (number of validators plus non-validators) if --hostname parameter is used",
nValidators+nNonValidators,
)
}
genVals := make([]types.GenesisValidator, nValidators)
ctx := cmd.Context()
for i := 0; i < nValidators; i++ {
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
nodeDir := filepath.Join(outputDir, nodeDirName)
config.SetRoot(nodeDir)
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
// set mode to validator for testnet
config := cfg.DefaultValidatorConfig()
if err := initFilesWithConfig(ctx, config, logger); err != nil {
return err
}
// overwrite default config if set and valid
if configFile != "" {
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
return err
}
if err := viper.Unmarshal(config); err != nil {
return err
}
if err := config.ValidateBasic(); err != nil {
return err
}
}
pvKeyFile := filepath.Join(nodeDir, config.PrivValidator.Key)
pvStateFile := filepath.Join(nodeDir, config.PrivValidator.State)
pv, err := privval.LoadFilePV(pvKeyFile, pvStateFile)
if err != nil {
return err
}
genVals := make([]types.GenesisValidator, nValidators)
ctx := cmd.Context()
for i := 0; i < nValidators; i++ {
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
nodeDir := filepath.Join(outputDir, nodeDirName)
config.SetRoot(nodeDir)
ctx, cancel := context.WithTimeout(ctx, ctxTimeout)
defer cancel()
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
pubKey, err := pv.GetPubKey(ctx)
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
}
genVals[i] = types.GenesisValidator{
Address: pubKey.Address(),
PubKey: pubKey,
Power: 1,
Name: nodeDirName,
}
}
if err := initFilesWithConfig(ctx, config); err != nil {
return err
}
for i := 0; i < nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i+nValidators))
config.SetRoot(nodeDir)
pvKeyFile := filepath.Join(nodeDir, config.PrivValidator.Key)
pvStateFile := filepath.Join(nodeDir, config.PrivValidator.State)
pv, err := privval.LoadFilePV(pvKeyFile, pvStateFile)
if err != nil {
return err
}
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
ctx, cancel := context.WithTimeout(ctx, ctxTimeout)
defer cancel()
err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
pubKey, err := pv.GetPubKey(ctx)
if err != nil {
return fmt.Errorf("can't get pubkey: %w", err)
}
genVals[i] = types.GenesisValidator{
Address: pubKey.Address(),
PubKey: pubKey,
Power: 1,
Name: nodeDirName,
if err := initFilesWithConfig(ctx, conf, logger); err != nil {
return err
}
}
}
for i := 0; i < nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i+nValidators))
config.SetRoot(nodeDir)
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
// Generate genesis doc from generated validators
genDoc := &types.GenesisDoc{
ChainID: "chain-" + tmrand.Str(6),
GenesisTime: tmtime.Now(),
InitialHeight: initialHeight,
Validators: genVals,
ConsensusParams: types.DefaultConsensusParams(),
}
err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
if keyType == "secp256k1" {
genDoc.ConsensusParams.Validator = types.ValidatorParams{
PubKeyTypes: []string{types.ABCIPubKeyTypeSecp256k1},
}
}
if err := initFilesWithConfig(ctx, config); err != nil {
return err
// Write genesis file.
for i := 0; i < nValidators+nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil {
_ = os.RemoveAll(outputDir)
return err
}
}
}
// Generate genesis doc from generated validators
genDoc := &types.GenesisDoc{
ChainID: "chain-" + tmrand.Str(6),
GenesisTime: tmtime.Now(),
InitialHeight: initialHeight,
Validators: genVals,
ConsensusParams: types.DefaultConsensusParams(),
}
if keyType == "secp256k1" {
genDoc.ConsensusParams.Validator = types.ValidatorParams{
PubKeyTypes: []string{types.ABCIPubKeyTypeSecp256k1},
// Gather persistent peer addresses.
var (
persistentPeers = make([]string, 0)
err error
)
tpargs := testnetPeerArgs{
numValidators: nValidators,
numNonValidators: nNonValidators,
peerToPeerPort: p2pPort,
nodeDirPrefix: nodeDirPrefix,
outputDir: outputDir,
hostnames: hostnames,
startingIPAddr: startingIPAddress,
hostnamePrefix: hostnamePrefix,
hostnameSuffix: hostnameSuffix,
randomMonikers: randomMonikers,
}
}
// Write genesis file.
for i := 0; i < nValidators+nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil {
_ = os.RemoveAll(outputDir)
return err
}
}
if populatePersistentPeers {
// Gather persistent peer addresses.
var (
persistentPeers = make([]string, 0)
err error
)
if populatePersistentPeers {
persistentPeers, err = persistentPeersArray(config)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
persistentPeers, err = persistentPeersArray(config, tpargs)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
}
}
// Overwrite default config.
for i := 0; i < nValidators+nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
config.SetRoot(nodeDir)
config.P2P.AllowDuplicateIP = true
if populatePersistentPeers {
persistentPeersWithoutSelf := make([]string, 0)
for j := 0; j < len(persistentPeers); j++ {
if j == i {
continue
// Overwrite default config.
for i := 0; i < nValidators+nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
config.SetRoot(nodeDir)
config.P2P.AllowDuplicateIP = true
if populatePersistentPeers {
persistentPeersWithoutSelf := make([]string, 0)
for j := 0; j < len(persistentPeers); j++ {
if j == i {
continue
}
persistentPeersWithoutSelf = append(persistentPeersWithoutSelf, persistentPeers[j])
}
persistentPeersWithoutSelf = append(persistentPeersWithoutSelf, persistentPeers[j])
config.P2P.PersistentPeers = strings.Join(persistentPeersWithoutSelf, ",")
}
config.P2P.PersistentPeers = strings.Join(persistentPeersWithoutSelf, ",")
}
config.Moniker = moniker(i)
config.Moniker = tpargs.moniker(i)
if err := cfg.WriteConfigFile(nodeDir, config); err != nil {
return err
if err := cfg.WriteConfigFile(nodeDir, config); err != nil {
return err
}
}
fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
return nil
}
fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
return nil
return cmd
}
type testnetPeerArgs struct {
numValidators int
numNonValidators int
peerToPeerPort int
nodeDirPrefix string
outputDir string
hostnames []string
startingIPAddr string
hostnamePrefix string
hostnameSuffix string
randomMonikers bool
}
func hostnameOrIP(i int) string {
if len(hostnames) > 0 && i < len(hostnames) {
return hostnames[i]
func (args *testnetPeerArgs) hostnameOrIP(i int) (string, error) {
if len(args.hostnames) > 0 && i < len(args.hostnames) {
return args.hostnames[i], nil
}
if startingIPAddress == "" {
return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
if args.startingIPAddr == "" {
return fmt.Sprintf("%s%d%s", args.hostnamePrefix, i, args.hostnameSuffix), nil
}
ip := net.ParseIP(startingIPAddress)
ip := net.ParseIP(args.startingIPAddr)
ip = ip.To4()
if ip == nil {
fmt.Printf("%v: non ipv4 address\n", startingIPAddress)
os.Exit(1)
return "", fmt.Errorf("%v is non-ipv4 address", args.startingIPAddr)
}
for j := 0; j < i; j++ {
ip[3]++
}
return ip.String()
return ip.String(), nil
}
// get an array of persistent peers
func persistentPeersArray(config *cfg.Config) ([]string, error) {
peers := make([]string, nValidators+nNonValidators)
for i := 0; i < nValidators+nNonValidators; i++ {
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
func persistentPeersArray(config *cfg.Config, args testnetPeerArgs) ([]string, error) {
peers := make([]string, args.numValidators+args.numNonValidators)
for i := 0; i < len(peers); i++ {
nodeDir := filepath.Join(args.outputDir, fmt.Sprintf("%s%d", args.nodeDirPrefix, i))
config.SetRoot(nodeDir)
nodeKey, err := config.LoadNodeKeyID()
if err != nil {
return []string{}, err
return nil, err
}
peers[i] = nodeKey.AddressString(fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort))
addr, err := args.hostnameOrIP(i)
if err != nil {
return nil, err
}
peers[i] = nodeKey.AddressString(fmt.Sprintf("%s:%d", addr, args.peerToPeerPort))
}
return peers, nil
}
func moniker(i int) string {
if randomMonikers {
func (args *testnetPeerArgs) moniker(i int) string {
if args.randomMonikers {
return randomMoniker()
}
if len(hostnames) > 0 && i < len(hostnames) {
return hostnames[i]
if len(args.hostnames) > 0 && i < len(args.hostnames) {
return args.hostnames[i]
}
if startingIPAddress == "" {
return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
if args.startingIPAddr == "" {
return fmt.Sprintf("%s%d%s", args.hostnamePrefix, i, args.hostnameSuffix)
}
return randomMoniker()
}


+ 37
- 25
cmd/tendermint/main.go View File

@ -1,37 +1,50 @@
package main
import (
"os"
"path/filepath"
"context"
cmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/cmd/tendermint/commands/debug"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/node"
)
func main() {
rootCmd := cmd.RootCmd
rootCmd.AddCommand(
cmd.GenValidatorCmd,
cmd.ReIndexEventCmd,
cmd.InitFilesCmd,
cmd.LightCmd,
cmd.ReplayCmd,
cmd.ReplayConsoleCmd,
cmd.ResetAllCmd,
cmd.ResetPrivValidatorCmd,
cmd.ShowValidatorCmd,
cmd.TestnetFilesCmd,
cmd.ShowNodeIDCmd,
cmd.GenNodeKeyCmd,
cmd.VersionCmd,
cmd.InspectCmd,
cmd.RollbackStateCmd,
cmd.MakeKeyMigrateCommand(),
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conf, err := commands.ParseConfig(config.DefaultConfig())
if err != nil {
panic(err)
}
logger, err := log.NewDefaultLogger(conf.LogFormat, conf.LogLevel)
if err != nil {
panic(err)
}
rcmd := commands.RootCommand(conf, logger)
rcmd.AddCommand(
commands.MakeGenValidatorCommand(),
commands.MakeReindexEventCommand(conf, logger),
commands.MakeInitFilesCommand(conf, logger),
commands.MakeLightCommand(conf, logger),
commands.MakeReplayCommand(conf, logger),
commands.MakeReplayConsoleCommand(conf, logger),
commands.MakeResetAllCommand(conf, logger),
commands.MakeResetPrivateValidatorCommand(conf, logger),
commands.MakeShowValidatorCommand(conf, logger),
commands.MakeTestnetFilesCommand(conf, logger),
commands.MakeShowNodeIDCommand(conf),
commands.GenNodeKeyCmd,
commands.VersionCmd,
commands.MakeInspectCommand(conf, logger),
commands.MakeRollbackStateCommand(conf),
commands.MakeKeyMigrateCommand(conf, logger),
debug.DebugCmd,
cli.NewCompletionCmd(rootCmd, true),
cli.NewCompletionCmd(rcmd, true),
)
// NOTE:
@ -45,10 +58,9 @@ func main() {
nodeFunc := node.NewDefault
// Create & start node
rootCmd.AddCommand(cmd.NewRunNodeCmd(nodeFunc))
rcmd.AddCommand(commands.NewRunNodeCmd(nodeFunc, conf, logger))
cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv(filepath.Join("$HOME", config.DefaultTendermintDir)))
if err := cmd.Execute(); err != nil {
if err := rcmd.ExecuteContext(ctx); err != nil {
panic(err)
}
}

+ 5
- 1
docs/tutorials/go.md View File

@ -367,7 +367,11 @@ func main() {
flag.Parse()
logger := log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to configure logger: %v", err)
os.Exit(1)
}
server := abciserver.NewSocketServer(socketAddr, app)
server.SetLogger(logger)


+ 5
- 5
libs/cli/setup.go View File

@ -28,10 +28,10 @@ type Executable interface {
// PrepareBaseCmd is meant for tendermint and other servers
func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor {
cobra.OnInitialize(func() { initEnv(envPrefix) })
cobra.OnInitialize(func() { InitEnv(envPrefix) })
cmd.PersistentFlags().StringP(HomeFlag, "", defaultHome, "directory for config and data")
cmd.PersistentFlags().Bool(TraceFlag, false, "print out full stack trace on errors")
cmd.PersistentPreRunE = concatCobraCmdFuncs(bindFlagsLoadViper, cmd.PersistentPreRunE)
cmd.PersistentPreRunE = concatCobraCmdFuncs(BindFlagsLoadViper, cmd.PersistentPreRunE)
return Executor{cmd, os.Exit}
}
@ -46,8 +46,8 @@ func PrepareMainCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor
return PrepareBaseCmd(cmd, envPrefix, defaultHome)
}
// initEnv sets to use ENV variables if set.
func initEnv(prefix string) {
// InitEnv sets to use ENV variables if set.
func InitEnv(prefix string) {
copyEnvVars(prefix)
// env variables with TM prefix (eg. TM_ROOT)
@ -127,7 +127,7 @@ func concatCobraCmdFuncs(fs ...cobraCmdFunc) cobraCmdFunc {
}
// Bind all flags and read the config into viper
func bindFlagsLoadViper(cmd *cobra.Command, args []string) error {
func BindFlagsLoadViper(cmd *cobra.Command, args []string) error {
// cmd.Flags() includes flags from this command and all persistent flags from the parent
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return err


+ 0
- 3
libs/log/default_test.go View File

@ -37,9 +37,6 @@ func TestNewDefaultLogger(t *testing.T) {
_, err := log.NewDefaultLogger(tc.format, tc.level)
if tc.expectErr {
require.Error(t, err)
require.Panics(t, func() {
_ = log.MustNewDefaultLogger(tc.format, tc.level)
})
} else {
require.NoError(t, err)
}


Loading…
Cancel
Save