From 2c129447fdadd42d397611b68941b5073f985094 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 30 Aug 2017 22:21:32 +0200 Subject: [PATCH] Example that showcases how to build your own tendermint node This example shows how a user of the tendermint library can build their own node and supply it with its own commands. It includes two todos in order to make it easier for library users to use tendermint. --- Makefile | 4 + cmd/hsm/commands/run_node.go | 107 ++++++++++++++++++ cmd/hsm/main.go | 35 ++++++ cmd/tendermint/commands/gen_validator.go | 6 +- cmd/tendermint/commands/init.go | 6 +- cmd/tendermint/commands/probe_upnp.go | 6 +- cmd/tendermint/commands/replay.go | 9 +- .../commands/reset_priv_validator.go | 9 +- cmd/tendermint/commands/run_node.go | 5 +- cmd/tendermint/commands/show_validator.go | 6 +- cmd/tendermint/commands/testnet.go | 8 +- cmd/tendermint/commands/version.go | 8 +- 12 files changed, 161 insertions(+), 48 deletions(-) create mode 100644 cmd/hsm/commands/run_node.go create mode 100644 cmd/hsm/main.go diff --git a/Makefile b/Makefile index 8c9c5214..328e65f8 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ build: go build \ --ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse HEAD`" -o build/tendermint ./cmd/tendermint/ +build_hsm: + go build \ + --ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse HEAD`" -o build/tendermint ./cmd/hsm/ + build_race: go build -race -o build/tendermint ./cmd/tendermint diff --git a/cmd/hsm/commands/run_node.go b/cmd/hsm/commands/run_node.go new file mode 100644 index 00000000..94a1c548 --- /dev/null +++ b/cmd/hsm/commands/run_node.go @@ -0,0 +1,107 @@ +package commands + +import ( + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/types" + + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" +) + +var ( + config = cfg.DefaultConfig() + logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main") +) + +var RunNodeCmd = &cobra.Command{ + Use: "node", + Short: "Run the tendermint node", + RunE: runNode, +} + +func init() { + AddNodeFlags(RunNodeCmd) +} + +// 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) { + // bind flags + cmd.Flags().String("moniker", config.Moniker, "Node Name") + + // node flags + cmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing") + + // abci flags + cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'dummy' for local testing.") + cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)") + + // rpc flags + cmd.Flags().String("rpc.laddr", config.RPC.ListenAddress, "RPC listen address. Port required") + cmd.Flags().String("rpc.grpc_laddr", config.RPC.GRPCListenAddress, "GRPC listen address (BroadcastTx only). Port required") + cmd.Flags().Bool("rpc.unsafe", config.RPC.Unsafe, "Enabled unsafe rpc methods") + + // p2p flags + cmd.Flags().String("p2p.laddr", config.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 host:port seed nodes") + cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") + cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable Peer-Exchange (dev feature)") + + // consensus flags + cmd.Flags().Bool("consensus.create_empty_blocks", config.Consensus.CreateEmptyBlocks, "Set this to false to only produce blocks when there are txs or when the AppHash changes") +} + +// Users wishing to: +// * Use an external signer for their validators +// * Supply an in-proc abci app +// should import github.com/tendermint/tendermint/node and implement +// their own run_node to call node.NewNode (instead of node.NewNodeDefault) +// with their custom priv validator and/or custom proxy.ClientCreator +func runNode(cmd *cobra.Command, args []string) error { + + // Wait until the genesis doc becomes available + // This is for Mintnet compatibility. + // TODO: If Mintnet gets deprecated or genesis_file is + // always available, remove. + genDocFile := config.GenesisFile() + for !cmn.FileExists(genDocFile) { + logger.Info(cmn.Fmt("Waiting for genesis file %v...", genDocFile)) + time.Sleep(time.Second) + } + + genDoc, err := types.GenesisDocFromFile(genDocFile) + if err != nil { + return err + } + config.ChainID = genDoc.ChainID + + // Create & start node + // n := node.NewNodeDefault(config, logger.With("module", "node")) + + // TODO: Make types.PrivValidator an interface so that it can be provided + // by a hardware wallet or any other wallet provider. + + // The next two lines show how a private validator is setup. + privValidator := types.LoadOrGenPrivValidator(config.PrivValidatorFile(), logger) + privValidator.SetSigner(types.NewDefaultSigner(privValidator.PrivKey)) + + n := node.NewNode(config, privValidator, proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), logger) + if _, err := n.Start(); err != nil { + return fmt.Errorf("Failed to start node: %v", err) + } else { + logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo()) + } + + // Trap signal, run forever. + n.RunForever() + + return nil +} diff --git a/cmd/hsm/main.go b/cmd/hsm/main.go new file mode 100644 index 00000000..743a8fcd --- /dev/null +++ b/cmd/hsm/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "os" + + tc "github.com/tendermint/tendermint/cmd/tendermint/commands" + "github.com/tendermint/tmlibs/cli" + + "github.com/tendermint/tendermint/cmd/hsm/commands" +) + +func main() { + // TODO: Make it easier to build a tendermint instance from scratch. + // All commands should be exported and it should be easy to override + // certain aspects of a single command. + // Probably every command should have a constructor that allows a user + // to vary the configuration. This is at least true for run_node.go + + rootCmd := tc.RootCmd + rootCmd.AddCommand(tc.GenValidatorCmd) + rootCmd.AddCommand(tc.InitFilesCmd) + rootCmd.AddCommand(tc.ProbeUpnpCmd) + rootCmd.AddCommand(tc.ReplayCmd) + rootCmd.AddCommand(tc.ReplayConsoleCmd) + rootCmd.AddCommand(tc.ResetAllCmd) + rootCmd.AddCommand(tc.ResetPrivValidatorCmd) + rootCmd.AddCommand(tc.ShowValidatorCmd) + rootCmd.AddCommand(tc.TestnetFilesCmd) + rootCmd.AddCommand(tc.VersionCmd) + + rootCmd.AddCommand(commands.RunNodeCmd) + + cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv("$HOME/.tendermint")) + cmd.Execute() +} diff --git a/cmd/tendermint/commands/gen_validator.go b/cmd/tendermint/commands/gen_validator.go index 97c583c2..df82554d 100644 --- a/cmd/tendermint/commands/gen_validator.go +++ b/cmd/tendermint/commands/gen_validator.go @@ -9,16 +9,12 @@ import ( "github.com/tendermint/tendermint/types" ) -var genValidatorCmd = &cobra.Command{ +var GenValidatorCmd = &cobra.Command{ Use: "gen_validator", Short: "Generate new validator keypair", Run: genValidator, } -func init() { - RootCmd.AddCommand(genValidatorCmd) -} - func genValidator(cmd *cobra.Command, args []string) { privValidator := types.GenPrivValidator() privValidatorJSONBytes, _ := json.MarshalIndent(privValidator, "", "\t") diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index ce900def..b844386c 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -9,16 +9,12 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) -var initFilesCmd = &cobra.Command{ +var InitFilesCmd = &cobra.Command{ Use: "init", Short: "Initialize Tendermint", Run: initFiles, } -func init() { - RootCmd.AddCommand(initFilesCmd) -} - func initFiles(cmd *cobra.Command, args []string) { privValFile := config.PrivValidatorFile() if _, err := os.Stat(privValFile); os.IsNotExist(err) { diff --git a/cmd/tendermint/commands/probe_upnp.go b/cmd/tendermint/commands/probe_upnp.go index e23e4897..a4c71d59 100644 --- a/cmd/tendermint/commands/probe_upnp.go +++ b/cmd/tendermint/commands/probe_upnp.go @@ -9,16 +9,12 @@ import ( "github.com/tendermint/tendermint/p2p/upnp" ) -var probeUpnpCmd = &cobra.Command{ +var ProbeUpnpCmd = &cobra.Command{ Use: "probe_upnp", Short: "Test UPnP functionality", RunE: probeUpnp, } -func init() { - RootCmd.AddCommand(probeUpnpCmd) -} - func probeUpnp(cmd *cobra.Command, args []string) error { capabilities, err := upnp.Probe(logger) if err != nil { diff --git a/cmd/tendermint/commands/replay.go b/cmd/tendermint/commands/replay.go index 0c88b244..422d4415 100644 --- a/cmd/tendermint/commands/replay.go +++ b/cmd/tendermint/commands/replay.go @@ -6,7 +6,7 @@ import ( "github.com/tendermint/tendermint/consensus" ) -var replayCmd = &cobra.Command{ +var ReplayCmd = &cobra.Command{ Use: "replay", Short: "Replay messages from WAL", Run: func(cmd *cobra.Command, args []string) { @@ -14,15 +14,10 @@ var replayCmd = &cobra.Command{ }, } -var replayConsoleCmd = &cobra.Command{ +var ReplayConsoleCmd = &cobra.Command{ Use: "replay_console", Short: "Replay messages from WAL in a console", Run: func(cmd *cobra.Command, args []string) { consensus.RunReplayFile(config.BaseConfig, config.Consensus, true) }, } - -func init() { - RootCmd.AddCommand(replayCmd) - RootCmd.AddCommand(replayConsoleCmd) -} diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index fa12be4e..389e5b79 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -9,23 +9,18 @@ import ( "github.com/tendermint/tmlibs/log" ) -var resetAllCmd = &cobra.Command{ +var ResetAllCmd = &cobra.Command{ Use: "unsafe_reset_all", Short: "(unsafe) Remove all the data and WAL, reset this node's validator", Run: resetAll, } -var resetPrivValidatorCmd = &cobra.Command{ +var ResetPrivValidatorCmd = &cobra.Command{ Use: "unsafe_reset_priv_validator", Short: "(unsafe) Reset this node's validator", Run: resetPrivValidator, } -func init() { - RootCmd.AddCommand(resetAllCmd) - RootCmd.AddCommand(resetPrivValidatorCmd) -} - // XXX: this is totally unsafe. // it's only suitable for testnets. func resetAll(cmd *cobra.Command, args []string) { diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 6740df28..413e2cc7 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -9,15 +9,14 @@ import ( "github.com/tendermint/tendermint/types" ) -var runNodeCmd = &cobra.Command{ +var RunNodeCmd = &cobra.Command{ Use: "node", Short: "Run the tendermint node", RunE: runNode, } func init() { - AddNodeFlags(runNodeCmd) - RootCmd.AddCommand(runNodeCmd) + AddNodeFlags(RunNodeCmd) } // AddNodeFlags exposes some common configuration options on the command-line diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index 53a687c6..21cc9027 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -9,16 +9,12 @@ import ( "github.com/tendermint/tendermint/types" ) -var showValidatorCmd = &cobra.Command{ +var ShowValidatorCmd = &cobra.Command{ Use: "show_validator", Short: "Show this node's validator info", Run: showValidator, } -func init() { - RootCmd.AddCommand(showValidatorCmd) -} - func showValidator(cmd *cobra.Command, args []string) { privValidator := types.LoadOrGenPrivValidator(config.PrivValidatorFile(), logger) pubKeyJSONBytes, _ := data.ToJSON(privValidator.PubKey) diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 315f3b49..75e016c9 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -11,7 +11,7 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) -var testnetFilesCmd = &cobra.Command{ +var TestnetFilesCmd = &cobra.Command{ Use: "testnet", Short: "Initialize files for a Tendermint testnet", Run: testnetFiles, @@ -24,12 +24,10 @@ var ( ) func init() { - testnetFilesCmd.Flags().IntVar(&nValidators, "n", 4, + TestnetFilesCmd.Flags().IntVar(&nValidators, "n", 4, "Number of validators to initialize the testnet with") - testnetFilesCmd.Flags().StringVar(&dataDir, "dir", "mytestnet", + TestnetFilesCmd.Flags().StringVar(&dataDir, "dir", "mytestnet", "Directory to store initialization data for the testnet") - - RootCmd.AddCommand(testnetFilesCmd) } func testnetFiles(cmd *cobra.Command, args []string) { diff --git a/cmd/tendermint/commands/version.go b/cmd/tendermint/commands/version.go index 5c92160e..692cba4a 100644 --- a/cmd/tendermint/commands/version.go +++ b/cmd/tendermint/commands/version.go @@ -8,14 +8,10 @@ import ( "github.com/tendermint/tendermint/version" ) -var versionCmd = &cobra.Command{ +var VersionCmd = &cobra.Command{ Use: "version", - Short: "Show version info", + Short: "Show version infoooooooo", Run: func(cmd *cobra.Command, args []string) { fmt.Println(version.Version) }, } - -func init() { - RootCmd.AddCommand(versionCmd) -}