Alessio/server refactor (#2472)

* Add arg to PrintUnsignedStdTx() to actually operate in offline mode
* WIP simplifying server module
* Expose ExternalIP()
* Move {GenTx,Init}Cmd into gaia's new init package
This commit is contained in:
Alessio Treglia 2018-10-10 15:45:41 -07:00 committed by Jae Kwon
parent e251088672
commit 0f4a03b44e
22 changed files with 533 additions and 563 deletions

3
.gitignore vendored
View File

@ -2,6 +2,9 @@
.DS_Store
*.swp
*.swo
*.swl
*.swm
*.swn
.vscode
.idea

View File

@ -87,8 +87,14 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *
}
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (err error) {
stdTx, err := buildUnsignedStdTx(txBldr, cliCtx, msgs)
// Don't perform online validation or lookups if offline is true.
func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool) (err error) {
var stdTx auth.StdTx
if offline {
stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
} else {
stdTx, err = buildUnsignedStdTx(txBldr, cliCtx, msgs)
}
if err != nil {
return
}
@ -204,6 +210,10 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg
if err != nil {
return
}
return buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
}
func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
if txBldr.SimulateGas {
var name string
name, err = cliCtx.GetFromName()

View File

@ -16,6 +16,7 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/server"
)
@ -28,10 +29,12 @@ func main() {
Short: "Gaia Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
appInit := app.GaiaAppInit()
rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit))
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit))
server.AddCommands(ctx, cdc, rootCmd, app.GaiaAppInit(),
server.ConstructAppCreator(newApp, "gaia"),
server.ConstructAppExporter(exportAppStateAndTMValidators, "gaia"))
server.AddCommands(ctx, cdc, rootCmd, appInit,
newApp, exportAppStateAndTMValidators)
// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome)

321
cmd/gaia/init/init.go Normal file
View File

@ -0,0 +1,321 @@
package init
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
servercfg "github.com/cosmos/cosmos-sdk/server/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/types"
)
// get cmd to initialize all files for tendermint and application
func GenTxCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "gen-tx",
Short: "Create genesis transaction file (under [--home]/config/gentx/gentx-[nodeID].json)",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, args []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag))
ip := viper.GetString(server.FlagIP)
if len(ip) == 0 {
eip, err := server.ExternalIP()
if err != nil {
return err
}
ip = eip
}
genTxConfig := servercfg.GenTx{
viper.GetString(server.FlagName),
viper.GetString(server.FlagClientHome),
viper.GetBool(server.FlagOWK),
ip,
}
cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig)
if err != nil {
return err
}
toPrint := struct {
AppMessage json.RawMessage `json:"app_message"`
GenTxFile json.RawMessage `json:"gen_tx_file"`
}{
cliPrint,
genTxFile,
}
out, err := codec.MarshalJSONIndent(cdc, toPrint)
if err != nil {
return err
}
fmt.Println(string(out))
return nil
},
}
cmd.Flags().String(server.FlagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine")
cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx)
return cmd
}
// NOTE: This will update (write) the config file with
// updated name (moniker) for node.
func gentxWithConfig(cdc *codec.Codec, appInit server.AppInit, config *cfg.Config, genTxConfig servercfg.GenTx) (
cliPrint json.RawMessage, genTxFile json.RawMessage, err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return
}
nodeID := string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
if err != nil {
return
}
tx := server.GenesisTx{
NodeID: nodeID,
IP: genTxConfig.IP,
Validator: validator,
AppGenTx: appGenTx,
}
bz, err := codec.MarshalJSONIndent(cdc, tx)
if err != nil {
return
}
genTxFile = json.RawMessage(bz)
name := fmt.Sprintf("gentx-%v.json", nodeID)
writePath := filepath.Join(config.RootDir, "config", "gentx")
file := filepath.Join(writePath, name)
err = common.EnsureDir(writePath, 0700)
if err != nil {
return
}
err = common.WriteFile(file, bz, 0644)
if err != nil {
return
}
// Write updated config with moniker
config.Moniker = genTxConfig.Name
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
return
}
// get cmd to initialize all files for tendermint and application
// nolint: golint
func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize genesis config, priv-validator file, and p2p-node file",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag))
initConfig := server.InitConfig{
viper.GetString(server.FlagChainID),
viper.GetBool(server.FlagWithTxs),
filepath.Join(config.RootDir, "config", "gentx"),
viper.GetBool(server.FlagOverwrite),
}
chainID, nodeID, appMessage, err := initWithConfig(cdc, appInit, config, initConfig)
if err != nil {
return err
}
// print out some key information
toPrint := struct {
ChainID string `json:"chain_id"`
NodeID string `json:"node_id"`
AppMessage json.RawMessage `json:"app_message"`
}{
chainID,
nodeID,
appMessage,
}
out, err := codec.MarshalJSONIndent(cdc, toPrint)
if err != nil {
return err
}
fmt.Println(string(out))
return nil
},
}
cmd.Flags().BoolP(server.FlagOverwrite, "o", false, "overwrite the genesis.json file")
cmd.Flags().String(server.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().Bool(server.FlagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/")
cmd.Flags().AddFlagSet(appInit.FlagsAppGenState)
cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) // need to add this flagset for when no GenTx's provided
cmd.AddCommand(GenTxCmd(ctx, cdc, appInit))
return cmd
}
func initWithConfig(cdc *codec.Codec, appInit server.AppInit, config *cfg.Config, initConfig server.InitConfig) (
chainID string, nodeID string, appMessage json.RawMessage, err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return
}
nodeID = string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
if initConfig.ChainID == "" {
initConfig.ChainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
}
chainID = initConfig.ChainID
genFile := config.GenesisFile()
if !initConfig.Overwrite && common.FileExists(genFile) {
err = fmt.Errorf("genesis.json file already exists: %v", genFile)
return
}
// process genesis transactions, or otherwise create one for defaults
var appGenTxs []json.RawMessage
var validators []types.GenesisValidator
var persistentPeers string
if initConfig.GenTxs {
validators, appGenTxs, persistentPeers, err = processGenTxs(initConfig.GenTxsDir, cdc)
if err != nil {
return
}
config.P2P.PersistentPeers = persistentPeers
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
} else {
genTxConfig := servercfg.GenTx{
viper.GetString(server.FlagName),
viper.GetString(server.FlagClientHome),
viper.GetBool(server.FlagOWK),
"127.0.0.1",
}
// Write updated config with moniker
config.Moniker = genTxConfig.Name
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
appMessage = am
if err != nil {
return "", "", nil, err
}
validators = []types.GenesisValidator{validator}
appGenTxs = []json.RawMessage{appGenTx}
}
appState, err := appInit.AppGenState(cdc, appGenTxs)
if err != nil {
return
}
err = writeGenesisFile(cdc, genFile, initConfig.ChainID, validators, appState)
if err != nil {
return
}
return
}
// append a genesis-piece
func processGenTxs(genTxsDir string, cdc *codec.Codec) (
validators []types.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) {
var fos []os.FileInfo
fos, err = ioutil.ReadDir(genTxsDir)
if err != nil {
return
}
genTxs := make(map[string]server.GenesisTx)
var nodeIDs []string
for _, fo := range fos {
filename := path.Join(genTxsDir, fo.Name())
if !fo.IsDir() && (path.Ext(filename) != ".json") {
continue
}
// get the genTx
var bz []byte
bz, err = ioutil.ReadFile(filename)
if err != nil {
return
}
var genTx server.GenesisTx
err = cdc.UnmarshalJSON(bz, &genTx)
if err != nil {
return
}
genTxs[genTx.NodeID] = genTx
nodeIDs = append(nodeIDs, genTx.NodeID)
}
sort.Strings(nodeIDs)
for _, nodeID := range nodeIDs {
genTx := genTxs[nodeID]
// combine some stuff
validators = append(validators, genTx.Validator)
appGenTxs = append(appGenTxs, genTx.AppGenTx)
// Add a persistent peer
comma := ","
if len(persistentPeers) == 0 {
comma = ""
}
persistentPeers += fmt.Sprintf("%s%s@%s:26656", comma, genTx.NodeID, genTx.IP)
}
return
}
// read of create the private key file for this config
func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey {
// private validator
privValFile := tmConfig.PrivValidatorFile()
var privValidator *privval.FilePV
if common.FileExists(privValFile) {
privValidator = privval.LoadFilePV(privValFile)
} else {
privValidator = privval.GenFilePV(privValFile)
privValidator.Save()
}
return privValidator.GetPubKey()
}
// writeGenesisFile creates and writes the genesis configuration to disk. An
// error is returned if building or writing the configuration to file fails.
// nolint: unparam
func writeGenesisFile(cdc *codec.Codec, genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error {
genDoc := types.GenesisDoc{
ChainID: chainID,
Validators: validators,
AppState: appState,
}
if err := genDoc.ValidateAndComplete(); err != nil {
return err
}
return genDoc.SaveAs(genesisFile)
}

111
cmd/gaia/init/init_test.go Normal file
View File

@ -0,0 +1,111 @@
package init
import (
"bytes"
"io"
"io/ioutil"
"os"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/stretchr/testify/require"
abciServer "github.com/tendermint/tendermint/abci/server"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/libs/log"
)
func TestInitCmd(t *testing.T) {
defer server.SetupViper(t)()
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := server.NewContext(cfg, logger)
cdc := codec.New()
appInit := server.AppInit{
AppGenState: mock.AppGenState,
AppGenTx: mock.AppGenTx,
}
cmd := InitCmd(ctx, cdc, appInit)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
}
func TestEmptyState(t *testing.T) {
defer server.SetupViper(t)()
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := server.NewContext(cfg, logger)
cdc := codec.New()
appInit := server.AppInit{
AppGenTx: mock.AppGenTx,
AppGenState: mock.AppGenStateEmpty,
}
cmd := InitCmd(ctx, cdc, appInit)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
cmd = server.ExportCmd(ctx, cdc, nil)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
outC := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
outC <- buf.String()
}()
w.Close()
os.Stdout = old
out := <-outC
require.Contains(t, out, "WARNING: State is not initialized")
require.Contains(t, out, "genesis_time")
require.Contains(t, out, "chain_id")
require.Contains(t, out, "consensus_params")
require.Contains(t, out, "validators")
require.Contains(t, out, "app_hash")
}
func TestStartStandAlone(t *testing.T) {
home, err := ioutil.TempDir("", "mock-sdk-cmd")
require.Nil(t, err)
defer func() {
os.RemoveAll(home)
}()
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := server.NewContext(cfg, logger)
cdc := codec.New()
appInit := server.AppInit{
AppGenState: mock.AppGenState,
AppGenTx: mock.AppGenTx,
}
initCmd := InitCmd(ctx, cdc, appInit)
err = initCmd.RunE(nil, nil)
require.NoError(t, err)
app, err := mock.NewApp(home, logger)
require.Nil(t, err)
svrAddr, _, err := server.FreeTCPAddr()
require.Nil(t, err)
svr, err := abciServer.NewServer(svrAddr, "socket", app)
require.Nil(t, err, "error creating listener")
svr.SetLogger(logger.With("module", "abci-server"))
svr.Start()
timer := time.NewTimer(time.Duration(2) * time.Second)
select {
case <-timer.C:
svr.Stop()
}
}

View File

@ -1,7 +1,8 @@
package server
package init
import (
"fmt"
"github.com/cosmos/cosmos-sdk/server"
"net"
"path/filepath"
@ -30,7 +31,7 @@ var (
const nodeDirPerm = 0755
// get cmd to initialize all files for tendermint testnet and application
func TestnetFilesCmd(ctx *Context, cdc *codec.Codec, appInit AppInit) *cobra.Command {
func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "testnet",
Short: "Initialize files for a Gaiad testnet",
@ -65,7 +66,7 @@ Example:
return cmd
}
func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit AppInit) error {
func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppInit) error {
outDir := viper.GetString(outputDir)
numValidators := viper.GetInt(nValidators)
@ -133,7 +134,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit AppInit) er
nodeDaemonHomeName := viper.GetString(nodeDaemonHome)
nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName)
gentxsDir := filepath.Join(outDir, "gentxs")
initConfig := InitConfig{
initConfig := server.InitConfig{
chainID,
true,
gentxsDir,
@ -156,7 +157,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit AppInit) er
func getIP(i int) (ip string, err error) {
ip = viper.GetString(startingIPAddress)
if len(ip) == 0 {
ip, err = externalIP()
ip, err = server.ExternalIP()
if err != nil {
return "", err
}

View File

@ -6,6 +6,7 @@ import (
"os"
"github.com/cosmos/cosmos-sdk/baseapp"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
@ -28,9 +29,12 @@ func main() {
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
server.AddCommands(ctx, cdc, rootCmd, server.DefaultAppInit,
server.ConstructAppCreator(newApp, "basecoin"),
server.ConstructAppExporter(exportAppStateAndTMValidators, "basecoin"))
appInit := server.DefaultAppInit
rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit))
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit))
server.AddCommands(ctx, cdc, rootCmd, appInit,
newApp, exportAppStateAndTMValidators)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.basecoind")

View File

@ -13,6 +13,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/examples/democoin/app"
"github.com/cosmos/cosmos-sdk/server"
@ -70,9 +71,11 @@ func main() {
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, CoolAppInit))
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, CoolAppInit))
server.AddCommands(ctx, cdc, rootCmd, CoolAppInit,
server.ConstructAppCreator(newApp, "democoin"),
server.ConstructAppExporter(exportAppStateAndTMValidators, "democoin"))
newApp, exportAppStateAndTMValidators)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.democoind")

View File

@ -13,72 +13,29 @@ import (
)
type (
// AppCreator reflects a function that allows us to lazily initialize an
// AppCreator is a function that allows us to lazily initialize an
// application using various configurations.
AppCreator func(home string, logger log.Logger, traceStore string) (abci.Application, error)
AppCreator func(log.Logger, dbm.DB, io.Writer) abci.Application
// AppExporter reflects a function that dumps all app state to
// AppExporter is a function that dumps all app state to
// JSON-serializable structure and returns the current validator set.
AppExporter func(home string, logger log.Logger, traceStore string) (json.RawMessage, []tmtypes.GenesisValidator, error)
// AppCreatorInit reflects a function that performs initialization of an
// AppCreator.
AppCreatorInit func(log.Logger, dbm.DB, io.Writer) abci.Application
// AppExporterInit reflects a function that performs initialization of an
// AppExporter.
AppExporterInit func(log.Logger, dbm.DB, io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error)
AppExporter func(log.Logger, dbm.DB, io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error)
)
// ConstructAppCreator returns an application generation function.
func ConstructAppCreator(appFn AppCreatorInit, name string) AppCreator {
return func(rootDir string, logger log.Logger, traceStore string) (abci.Application, error) {
dataDir := filepath.Join(rootDir, "data")
db, err := dbm.NewGoLevelDB(name, dataDir)
if err != nil {
return nil, err
}
var traceStoreWriter io.Writer
if traceStore != "" {
traceStoreWriter, err = os.OpenFile(
traceStore,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
return nil, err
}
}
app := appFn(logger, db, traceStoreWriter)
return app, nil
}
func openDB(rootDir string) (dbm.DB, error) {
dataDir := filepath.Join(rootDir, "data")
db, err := dbm.NewGoLevelDB("application", dataDir)
return db, err
}
// ConstructAppExporter returns an application export function.
func ConstructAppExporter(appFn AppExporterInit, name string) AppExporter {
return func(rootDir string, logger log.Logger, traceStore string) (json.RawMessage, []tmtypes.GenesisValidator, error) {
dataDir := filepath.Join(rootDir, "data")
db, err := dbm.NewGoLevelDB(name, dataDir)
if err != nil {
return nil, nil, err
}
var traceStoreWriter io.Writer
if traceStore != "" {
traceStoreWriter, err = os.OpenFile(
traceStore,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
return nil, nil, err
}
}
return appFn(logger, db, traceStoreWriter)
func openTraceWriter(traceWriterFile string) (w io.Writer, err error) {
if traceWriterFile != "" {
w, err = os.OpenFile(
traceWriterFile,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
return
}
return
}

View File

@ -20,7 +20,7 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C
Short: "Export state to JSON",
RunE: func(cmd *cobra.Command, args []string) error {
home := viper.GetString("home")
traceStore := viper.GetString(flagTraceStore)
traceWriterFile := viper.GetString(flagTraceStore)
emptyState, err := isEmptyState(home)
if err != nil {
return err
@ -37,7 +37,15 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C
return nil
}
appState, validators, err := appExporter(home, ctx.Logger, traceStore)
db, err := openDB(home)
if err != nil {
return err
}
traceWriter, err := openTraceWriter(traceWriterFile)
if err != nil {
return err
}
appState, validators, err := appExporter(ctx.Logger, db, traceWriter)
if err != nil {
return errors.Errorf("error exporting state: %v\n", err)
}

View File

@ -1,53 +0,0 @@
package server
import (
"bytes"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/stretchr/testify/require"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/libs/log"
"io"
"os"
"testing"
)
func TestEmptyState(t *testing.T) {
defer setupViper(t)()
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := NewContext(cfg, logger)
cdc := codec.New()
appInit := AppInit{
AppGenTx: mock.AppGenTx,
AppGenState: mock.AppGenStateEmpty,
}
cmd := InitCmd(ctx, cdc, appInit)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
cmd = ExportCmd(ctx, cdc, nil)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
outC := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
outC <- buf.String()
}()
w.Close()
os.Stdout = old
out := <-outC
require.Contains(t, out, "WARNING: State is not initialized")
require.Contains(t, out, "genesis_time")
require.Contains(t, out, "chain_id")
require.Contains(t, out, "consensus_params")
require.Contains(t, out, "validators")
require.Contains(t, out, "app_hash")
}

View File

@ -3,26 +3,12 @@ package server
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/tendermint/tendermint/crypto"
cfg "github.com/tendermint/tendermint/config"
tmcli "github.com/tendermint/tendermint/libs/cli"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval"
tmtypes "github.com/tendermint/tendermint/types"
clkeys "github.com/cosmos/cosmos-sdk/client/keys"
@ -62,303 +48,8 @@ type InitConfig struct {
Overwrite bool
}
// get cmd to initialize all files for tendermint and application
func GenTxCmd(ctx *Context, cdc *codec.Codec, appInit AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "gen-tx",
Short: "Create genesis transaction file (under [--home]/config/gentx/gentx-[nodeID].json)",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, args []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(tmcli.HomeFlag))
ip := viper.GetString(FlagIP)
if len(ip) == 0 {
eip, err := externalIP()
if err != nil {
return err
}
ip = eip
}
genTxConfig := serverconfig.GenTx{
viper.GetString(FlagName),
viper.GetString(FlagClientHome),
viper.GetBool(FlagOWK),
ip,
}
cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig)
if err != nil {
return err
}
toPrint := struct {
AppMessage json.RawMessage `json:"app_message"`
GenTxFile json.RawMessage `json:"gen_tx_file"`
}{
cliPrint,
genTxFile,
}
out, err := codec.MarshalJSONIndent(cdc, toPrint)
if err != nil {
return err
}
fmt.Println(string(out))
return nil
},
}
cmd.Flags().String(FlagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine")
cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx)
return cmd
}
func gentxWithConfig(cdc *codec.Codec, appInit AppInit, config *cfg.Config, genTxConfig serverconfig.GenTx) (
cliPrint json.RawMessage, genTxFile json.RawMessage, err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return
}
nodeID := string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
if err != nil {
return
}
tx := GenesisTx{
NodeID: nodeID,
IP: genTxConfig.IP,
Validator: validator,
AppGenTx: appGenTx,
}
bz, err := codec.MarshalJSONIndent(cdc, tx)
if err != nil {
return
}
genTxFile = json.RawMessage(bz)
name := fmt.Sprintf("gentx-%v.json", nodeID)
writePath := filepath.Join(config.RootDir, "config", "gentx")
file := filepath.Join(writePath, name)
err = cmn.EnsureDir(writePath, 0700)
if err != nil {
return
}
err = cmn.WriteFile(file, bz, 0644)
if err != nil {
return
}
// Write updated config with moniker
config.Moniker = genTxConfig.Name
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
return
}
// get cmd to initialize all files for tendermint and application
func InitCmd(ctx *Context, cdc *codec.Codec, appInit AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize genesis config, priv-validator file, and p2p-node file",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(tmcli.HomeFlag))
initConfig := InitConfig{
viper.GetString(FlagChainID),
viper.GetBool(FlagWithTxs),
filepath.Join(config.RootDir, "config", "gentx"),
viper.GetBool(FlagOverwrite),
}
chainID, nodeID, appMessage, err := initWithConfig(cdc, appInit, config, initConfig)
if err != nil {
return err
}
// print out some key information
toPrint := struct {
ChainID string `json:"chain_id"`
NodeID string `json:"node_id"`
AppMessage json.RawMessage `json:"app_message"`
}{
chainID,
nodeID,
appMessage,
}
out, err := codec.MarshalJSONIndent(cdc, toPrint)
if err != nil {
return err
}
fmt.Println(string(out))
return nil
},
}
cmd.Flags().BoolP(FlagOverwrite, "o", false, "overwrite the genesis.json file")
cmd.Flags().String(FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().Bool(FlagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/")
cmd.Flags().AddFlagSet(appInit.FlagsAppGenState)
cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) // need to add this flagset for when no GenTx's provided
cmd.AddCommand(GenTxCmd(ctx, cdc, appInit))
return cmd
}
func initWithConfig(cdc *codec.Codec, appInit AppInit, config *cfg.Config, initConfig InitConfig) (
chainID string, nodeID string, appMessage json.RawMessage, err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return
}
nodeID = string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
if initConfig.ChainID == "" {
initConfig.ChainID = fmt.Sprintf("test-chain-%v", cmn.RandStr(6))
}
chainID = initConfig.ChainID
genFile := config.GenesisFile()
if !initConfig.Overwrite && cmn.FileExists(genFile) {
err = fmt.Errorf("genesis.json file already exists: %v", genFile)
return
}
// process genesis transactions, or otherwise create one for defaults
var appGenTxs []json.RawMessage
var validators []tmtypes.GenesisValidator
var persistentPeers string
if initConfig.GenTxs {
validators, appGenTxs, persistentPeers, err = processGenTxs(initConfig.GenTxsDir, cdc)
if err != nil {
return
}
config.P2P.PersistentPeers = persistentPeers
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
} else {
genTxConfig := serverconfig.GenTx{
viper.GetString(FlagName),
viper.GetString(FlagClientHome),
viper.GetBool(FlagOWK),
"127.0.0.1",
}
// Write updated config with moniker
config.Moniker = genTxConfig.Name
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
appMessage = am
if err != nil {
return "", "", nil, err
}
validators = []tmtypes.GenesisValidator{validator}
appGenTxs = []json.RawMessage{appGenTx}
}
appState, err := appInit.AppGenState(cdc, appGenTxs)
if err != nil {
return
}
err = writeGenesisFile(cdc, genFile, initConfig.ChainID, validators, appState)
if err != nil {
return
}
return
}
// append a genesis-piece
func processGenTxs(genTxsDir string, cdc *codec.Codec) (
validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) {
var fos []os.FileInfo
fos, err = ioutil.ReadDir(genTxsDir)
if err != nil {
return
}
genTxs := make(map[string]GenesisTx)
var nodeIDs []string
for _, fo := range fos {
filename := path.Join(genTxsDir, fo.Name())
if !fo.IsDir() && (path.Ext(filename) != ".json") {
continue
}
// get the genTx
var bz []byte
bz, err = ioutil.ReadFile(filename)
if err != nil {
return
}
var genTx GenesisTx
err = cdc.UnmarshalJSON(bz, &genTx)
if err != nil {
return
}
genTxs[genTx.NodeID] = genTx
nodeIDs = append(nodeIDs, genTx.NodeID)
}
sort.Strings(nodeIDs)
for _, nodeID := range nodeIDs {
genTx := genTxs[nodeID]
// combine some stuff
validators = append(validators, genTx.Validator)
appGenTxs = append(appGenTxs, genTx.AppGenTx)
// Add a persistent peer
comma := ","
if len(persistentPeers) == 0 {
comma = ""
}
persistentPeers += fmt.Sprintf("%s%s@%s:26656", comma, genTx.NodeID, genTx.IP)
}
return
}
//________________________________________________________________________________________
// read of create the private key file for this config
func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey {
// private validator
privValFile := tmConfig.PrivValidatorFile()
var privValidator *pvm.FilePV
if cmn.FileExists(privValFile) {
privValidator = pvm.LoadFilePV(privValFile)
} else {
privValidator = pvm.GenFilePV(privValFile)
privValidator.Save()
}
return privValidator.GetPubKey()
}
// writeGenesisFile creates and writes the genesis configuration to disk. An
// error is returned if building or writing the configuration to file fails.
// nolint: unparam
func writeGenesisFile(cdc *codec.Codec, genesisFile, chainID string, validators []tmtypes.GenesisValidator, appState json.RawMessage) error {
genDoc := tmtypes.GenesisDoc{
ChainID: chainID,
Validators: validators,
AppState: appState,
}
if err := genDoc.ValidateAndComplete(); err != nil {
return err
}
return genDoc.SaveAs(genesisFile)
}
//_____________________________________________________________________
// Core functionality passed from the application to the server init command

View File

@ -1,47 +0,0 @@
package server
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server/mock"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
)
// TODO update
func TestInitCmd(t *testing.T) {
defer setupViper(t)()
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := NewContext(cfg, logger)
cdc := codec.New()
appInit := AppInit{
AppGenState: mock.AppGenState,
AppGenTx: mock.AppGenTx,
}
cmd := InitCmd(ctx, cdc, appInit)
err = cmd.RunE(nil, nil)
require.NoError(t, err)
}
func TestGenTxCmd(t *testing.T) {
// TODO
}
func TestTestnetFilesCmd(t *testing.T) {
// TODO
}
func TestSimpleAppGenTx(t *testing.T) {
// TODO
}
func TestSimpleAppGenState(t *testing.T) {
// TODO
}

View File

@ -57,12 +57,18 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command {
func startStandAlone(ctx *Context, appCreator AppCreator) error {
addr := viper.GetString(flagAddress)
home := viper.GetString("home")
traceStore := viper.GetString(flagTraceStore)
traceWriterFile := viper.GetString(flagTraceStore)
app, err := appCreator(home, ctx.Logger, traceStore)
db, err := openDB(home)
if err != nil {
return err
}
traceWriter, err := openTraceWriter(traceWriterFile)
if err != nil {
return err
}
app := appCreator(ctx.Logger, db, traceWriter)
svr, err := server.NewServer(addr, "socket", app)
if err != nil {
@ -91,12 +97,18 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error {
func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) {
cfg := ctx.Config
home := cfg.RootDir
traceStore := viper.GetString(flagTraceStore)
traceWriterFile := viper.GetString(flagTraceStore)
app, err := appCreator(home, ctx.Logger, traceStore)
db, err := openDB(home)
if err != nil {
return nil, err
}
traceWriter, err := openTraceWriter(traceWriterFile)
if err != nil {
return nil, err
}
app := appCreator(ctx.Logger, db, traceWriter)
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
if err != nil {

View File

@ -1,52 +0,0 @@
package server
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/tendermint/tendermint/abci/server"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/libs/log"
)
func TestStartStandAlone(t *testing.T) {
home, err := ioutil.TempDir("", "mock-sdk-cmd")
require.Nil(t, err)
defer func() {
os.RemoveAll(home)
}()
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := NewContext(cfg, logger)
cdc := codec.New()
appInit := AppInit{
AppGenState: mock.AppGenState,
AppGenTx: mock.AppGenTx,
}
initCmd := InitCmd(ctx, cdc, appInit)
err = initCmd.RunE(nil, nil)
require.NoError(t, err)
app, err := mock.NewApp(home, logger)
require.Nil(t, err)
svrAddr, _, err := FreeTCPAddr()
require.Nil(t, err)
svr, err := server.NewServer(svrAddr, "socket", app)
require.Nil(t, err, "error creating listener")
svr.SetLogger(logger.With("module", "abci-server"))
svr.Start()
timer := time.NewTimer(time.Duration(2) * time.Second)
select {
case <-timer.C:
svr.Stop()
}
}

View File

@ -36,9 +36,9 @@ func FreeTCPAddr() (addr, port string, err error) {
return
}
// setupViper creates a homedir to run inside,
// SetupViper creates a homedir to run inside,
// and returns a cleanup function to defer
func setupViper(t *testing.T) func() {
func SetupViper(t *testing.T) func() {
rootDir, err := ioutil.TempDir("", "mock-sdk-cmd")
require.Nil(t, err)
viper.Set(cli.HomeFlag, rootDir)

View File

@ -42,7 +42,7 @@ func NewContext(config *cfg.Config, logger log.Logger) *Context {
// PersistentPreRunEFn returns a PersistentPreRunE function for cobra
// that initailizes the passed in context with a properly configured
// logger and config objecy
// logger and config object.
func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
if cmd.Name() == version.VersionCmd.Name() {
@ -85,7 +85,7 @@ func interceptLoadConfig() (conf *cfg.Config, err error) {
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
// the following parse config is needed to create directories
conf, _ = tcmd.ParseConfig()
conf, _ = tcmd.ParseConfig() // NOTE: ParseConfig() creates dir/files as necessary.
conf.ProfListenAddress = "localhost:6060"
conf.P2P.RecvRate = 5120000
conf.P2P.SendRate = 5120000
@ -96,7 +96,7 @@ func interceptLoadConfig() (conf *cfg.Config, err error) {
}
if conf == nil {
conf, err = tcmd.ParseConfig()
conf, err = tcmd.ParseConfig() // NOTE: ParseConfig() creates dir/files as necessary.
}
cosmosConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml")
@ -143,8 +143,6 @@ func AddCommands(
)
rootCmd.AddCommand(
InitCmd(ctx, cdc, appInit),
TestnetFilesCmd(ctx, cdc, appInit),
StartCmd(ctx, appCreator),
UnsafeResetAllCmd(ctx),
client.LineBreak,
@ -177,7 +175,7 @@ func InsertKeyJSON(cdc *codec.Codec, baseJSON []byte, key string, value json.Raw
// https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
// TODO there must be a better way to get external IP
func externalIP() (string, error) {
func ExternalIP() (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err

View File

@ -66,7 +66,7 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command {
// build and sign the transaction, then broadcast to Tendermint
msg := client.CreateMsg(from, to, coins)
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})

View File

@ -103,7 +103,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome
}
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
// Build and sign the transaction, then broadcast to Tendermint
@ -183,7 +183,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command {
}
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
// Build and sign the transaction, then broadcast to a Tendermint
@ -229,7 +229,7 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command {
}
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]",

View File

@ -42,7 +42,7 @@ func IBCTransferCmd(cdc *codec.Codec) *cobra.Command {
return err
}
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})

View File

@ -31,7 +31,7 @@ func GetCmdUnjail(cdc *codec.Codec) *cobra.Command {
msg := slashing.NewMsgUnjail(sdk.ValAddress(valAddr))
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
},

View File

@ -88,7 +88,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command {
}
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true)
}
// build and sign the transaction, then broadcast to Tendermint
@ -143,7 +143,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command {
msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate)
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
// build and sign the transaction, then broadcast to Tendermint
@ -186,7 +186,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command {
msg := stake.NewMsgDelegate(delAddr, valAddr, amount)
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
// build and sign the transaction, then broadcast to Tendermint
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
@ -256,7 +256,7 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command {
msg := stake.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, sharesAmount)
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
// build and sign the transaction, then broadcast to Tendermint
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
@ -319,7 +319,7 @@ func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command {
msg := stake.NewMsgBeginUnbonding(delAddr, valAddr, sharesAmount)
if cliCtx.GenerateOnly {
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false)
}
// build and sign the transaction, then broadcast to Tendermint
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})