diff --git a/.gitignore b/.gitignore index c6713cae8c..dec322b7ba 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ .DS_Store *.swp *.swo +*.swl +*.swm +*.swn .vscode .idea diff --git a/client/utils/utils.go b/client/utils/utils.go index 9e72c552d1..364b9e692f 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -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() diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 0b5f0e505e..adfb12d573 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -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) diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go new file mode 100644 index 0000000000..a04c1d2ae7 --- /dev/null +++ b/cmd/gaia/init/init.go @@ -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) +} diff --git a/cmd/gaia/init/init_test.go b/cmd/gaia/init/init_test.go new file mode 100644 index 0000000000..3a7f0a358d --- /dev/null +++ b/cmd/gaia/init/init_test.go @@ -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() + } +} diff --git a/server/testnet.go b/cmd/gaia/init/testnet.go similarity index 95% rename from server/testnet.go rename to cmd/gaia/init/testnet.go index e762631608..48b714f2de 100644 --- a/server/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -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 } diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index e9448bca82..b42bb35926 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -8,7 +8,7 @@ The core IBC protocol is payload-agnostic. On top of IBC, developers can impleme IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically verifiable packets can be sent between them. -IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This document supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation. +IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/blob/master/CosmosIBCSpecification.pdf). This document supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation. ## Contents diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 420508e723..3621935444 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -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") diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index e22b0fc3e0..7f8c7c54d1 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -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") diff --git a/server/constructors.go b/server/constructors.go index 84be219bbb..57282e43b1 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -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 } diff --git a/server/export.go b/server/export.go index c3d6b5283a..43ea10866e 100644 --- a/server/export.go +++ b/server/export.go @@ -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) } diff --git a/server/export_test.go b/server/export_test.go deleted file mode 100644 index 999ba3c005..0000000000 --- a/server/export_test.go +++ /dev/null @@ -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") -} diff --git a/server/init.go b/server/init.go index adec4fba39..091ffa9484 100644 --- a/server/init.go +++ b/server/init.go @@ -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 diff --git a/server/init_test.go b/server/init_test.go deleted file mode 100644 index 1913ac6717..0000000000 --- a/server/init_test.go +++ /dev/null @@ -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 -} diff --git a/server/start.go b/server/start.go index 170ff9dcb3..82fbbbbeba 100644 --- a/server/start.go +++ b/server/start.go @@ -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 { diff --git a/server/start_test.go b/server/start_test.go deleted file mode 100644 index db9fcd40f8..0000000000 --- a/server/start_test.go +++ /dev/null @@ -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() - } -} diff --git a/server/test_helpers.go b/server/test_helpers.go index d1230898dd..8de164e08a 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -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) diff --git a/server/util.go b/server/util.go index 9508e04894..5199c12071 100644 --- a/server/util.go +++ b/server/util.go @@ -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 diff --git a/store/prefixstore.go b/store/prefixstore.go index 835a210385..55af1084e3 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "io" sdk "github.com/cosmos/cosmos-sdk/types" @@ -8,11 +9,27 @@ import ( var _ KVStore = prefixStore{} +// prefixStore is similar with tendermint/tendermint/libs/db/prefix_db +// both gives access only to the limited subset of the store +// for convinience or safety + type prefixStore struct { parent KVStore prefix []byte } +func cloneAppend(bz []byte, tail []byte) (res []byte) { + res = make([]byte, len(bz)+len(tail)) + copy(res, bz) + copy(res[len(bz):], tail) + return +} + +func (s prefixStore) key(key []byte) (res []byte) { + res = cloneAppend(s.prefix, key) + return +} + // Implements Store func (s prefixStore) GetStoreType() StoreType { return s.parent.GetStoreType() @@ -30,22 +47,23 @@ func (s prefixStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap // Implements KVStore func (s prefixStore) Get(key []byte) []byte { - return s.parent.Get(append(s.prefix, key...)) + res := s.parent.Get(s.key(key)) + return res } // Implements KVStore func (s prefixStore) Has(key []byte) bool { - return s.parent.Has(append(s.prefix, key...)) + return s.parent.Has(s.key(key)) } // Implements KVStore func (s prefixStore) Set(key, value []byte) { - s.parent.Set(append(s.prefix, key...), value) + s.parent.Set(s.key(key), value) } // Implements KVStore func (s prefixStore) Delete(key []byte) { - s.parent.Delete(append(s.prefix, key...)) + s.parent.Delete(s.key(key)) } // Implements KVStore @@ -59,68 +77,147 @@ func (s prefixStore) Gas(meter GasMeter, config GasConfig) KVStore { } // Implements KVStore +// Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L106 func (s prefixStore) Iterator(start, end []byte) Iterator { + newstart := cloneAppend(s.prefix, start) + + var newend []byte if end == nil { - end = sdk.PrefixEndBytes(s.prefix) + newend = cpIncr(s.prefix) } else { - end = append(s.prefix, end...) - } - return prefixIterator{ - prefix: s.prefix, - iter: s.parent.Iterator(append(s.prefix, start...), end), + newend = cloneAppend(s.prefix, end) } + + iter := s.parent.Iterator(newstart, newend) + + return newPrefixIterator(s.prefix, start, end, iter) } // Implements KVStore +// Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L129 func (s prefixStore) ReverseIterator(start, end []byte) Iterator { - if end == nil { - end = sdk.PrefixEndBytes(s.prefix) + var newstart []byte + if start == nil { + newstart = cpIncr(s.prefix) } else { - end = append(s.prefix, end...) + newstart = cloneAppend(s.prefix, start) } - return prefixIterator{ - prefix: s.prefix, - iter: s.parent.ReverseIterator(start, end), + + var newend []byte + if end == nil { + newend = cpDecr(s.prefix) + } else { + newend = cloneAppend(s.prefix, end) } + + iter := s.parent.ReverseIterator(newstart, newend) + if start == nil { + skipOne(iter, cpIncr(s.prefix)) + } + + return newPrefixIterator(s.prefix, start, end, iter) } +var _ sdk.Iterator = (*prefixIterator)(nil) + type prefixIterator struct { - prefix []byte + prefix []byte + start, end []byte + iter Iterator + valid bool +} - iter Iterator +func newPrefixIterator(prefix, start, end []byte, parent Iterator) *prefixIterator { + return &prefixIterator{ + prefix: prefix, + start: start, + end: end, + iter: parent, + valid: parent.Valid() && bytes.HasPrefix(parent.Key(), prefix), + } } // Implements Iterator -func (iter prefixIterator) Domain() (start []byte, end []byte) { - start, end = iter.iter.Domain() - start = start[len(iter.prefix):] - end = end[len(iter.prefix):] - return +func (iter *prefixIterator) Domain() ([]byte, []byte) { + return iter.start, iter.end } // Implements Iterator -func (iter prefixIterator) Valid() bool { - return iter.iter.Valid() +func (iter *prefixIterator) Valid() bool { + return iter.valid && iter.iter.Valid() } // Implements Iterator -func (iter prefixIterator) Next() { +func (iter *prefixIterator) Next() { + if !iter.valid { + panic("prefixIterator invalid, cannot call Next()") + } iter.iter.Next() + if !iter.iter.Valid() || !bytes.HasPrefix(iter.iter.Key(), iter.prefix) { + iter.valid = false + } } // Implements Iterator -func (iter prefixIterator) Key() (key []byte) { +func (iter *prefixIterator) Key() (key []byte) { + if !iter.valid { + panic("prefixIterator invalid, cannot call Key()") + } key = iter.iter.Key() - key = key[len(iter.prefix):] + key = stripPrefix(key, iter.prefix) return } // Implements Iterator -func (iter prefixIterator) Value() []byte { +func (iter *prefixIterator) Value() []byte { + if !iter.valid { + panic("prefixIterator invalid, cannot call Value()") + } return iter.iter.Value() } // Implements Iterator -func (iter prefixIterator) Close() { +func (iter *prefixIterator) Close() { iter.iter.Close() } + +// copied from github.com/tendermint/tendermint/libs/db/prefix_db.go +func stripPrefix(key []byte, prefix []byte) []byte { + if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) { + panic("should not happen") + } + return key[len(prefix):] +} + +// wrapping sdk.PrefixEndBytes +func cpIncr(bz []byte) []byte { + return sdk.PrefixEndBytes(bz) +} + +// copied from github.com/tendermint/tendermint/libs/db/util.go +func cpDecr(bz []byte) (ret []byte) { + if len(bz) == 0 { + panic("cpDecr expects non-zero bz length") + } + ret = make([]byte, len(bz)) + copy(ret, bz) + for i := len(bz) - 1; i >= 0; i-- { + if ret[i] > byte(0x00) { + ret[i]-- + return + } + ret[i] = byte(0xFF) + if i == 0 { + return nil + } + } + return nil +} + +func skipOne(iter Iterator, skipKey []byte) { + if iter.Valid() { + if bytes.Equal(iter.Key(), skipKey) { + iter.Next() + } + } +} diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go index 49bc68037a..499f30d500 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -105,7 +105,286 @@ func TestPrefixStoreIterate(t *testing.T) { pIter.Next() } - require.Equal(t, bIter.Valid(), pIter.Valid()) bIter.Close() pIter.Close() } + +func TestPrefixStoreIteratorEdgeCase(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + + // overflow in cpIncr + prefix := []byte{0xAA, 0xFF, 0xFF} + prefixStore := baseStore.Prefix(prefix) + + // ascending order + baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) + + iter := prefixStore.Iterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() +} + +func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + + // overflow in cpIncr + prefix := []byte{0xAA, 0xFF, 0xFF} + prefixStore := baseStore.Prefix(prefix) + + // descending order + baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) + + iter := prefixStore.ReverseIterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() + + db = dbm.NewMemDB() + baseStore = dbStoreAdapter{db} + + // underflow in cpDecr + prefix = []byte{0xAA, 0x00, 0x00} + prefixStore = baseStore.Prefix(prefix) + + baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x01}, []byte{}) + baseStore.Set([]byte{0xAA, 0x00, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xA9, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xA9, 0xFF, 0xFF}, []byte{}) + + iter = prefixStore.ReverseIterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() +} + +// Tests below are ported from https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db_test.go + +func mockStoreWithStuff() sdk.KVStore { + db := dbm.NewMemDB() + store := dbStoreAdapter{db} + // Under "key" prefix + store.Set(bz("key"), bz("value")) + store.Set(bz("key1"), bz("value1")) + store.Set(bz("key2"), bz("value2")) + store.Set(bz("key3"), bz("value3")) + store.Set(bz("something"), bz("else")) + store.Set(bz(""), bz("")) + store.Set(bz("k"), bz("val")) + store.Set(bz("ke"), bz("valu")) + store.Set(bz("kee"), bz("valuu")) + return store +} + +func checkValue(t *testing.T, store sdk.KVStore, key []byte, expected []byte) { + bz := store.Get(key) + require.Equal(t, expected, bz) +} + +func checkValid(t *testing.T, itr sdk.Iterator, expected bool) { + valid := itr.Valid() + require.Equal(t, expected, valid) +} + +func checkNext(t *testing.T, itr sdk.Iterator, expected bool) { + itr.Next() + valid := itr.Valid() + require.Equal(t, expected, valid) +} + +func checkDomain(t *testing.T, itr sdk.Iterator, start, end []byte) { + ds, de := itr.Domain() + require.Equal(t, start, ds) + require.Equal(t, end, de) +} + +func checkItem(t *testing.T, itr sdk.Iterator, key, value []byte) { + require.Exactly(t, key, itr.Key()) + require.Exactly(t, value, itr.Value()) +} + +func checkInvalid(t *testing.T, itr sdk.Iterator) { + checkValid(t, itr, false) + checkKeyPanics(t, itr) + checkValuePanics(t, itr) + checkNextPanics(t, itr) +} + +func checkKeyPanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Key() }) +} + +func checkValuePanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Value() }) +} + +func checkNextPanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Next() }) +} + +func TestPrefixDBSimple(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + checkValue(t, pstore, bz("key"), nil) + checkValue(t, pstore, bz(""), bz("value")) + checkValue(t, pstore, bz("key1"), nil) + checkValue(t, pstore, bz("1"), bz("value1")) + checkValue(t, pstore, bz("key2"), nil) + checkValue(t, pstore, bz("2"), bz("value2")) + checkValue(t, pstore, bz("key3"), nil) + checkValue(t, pstore, bz("3"), bz("value3")) + checkValue(t, pstore, bz("something"), nil) + checkValue(t, pstore, bz("k"), nil) + checkValue(t, pstore, bz("ke"), nil) + checkValue(t, pstore, bz("kee"), nil) +} + +func TestPrefixDBIterator1(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(nil, nil) + checkDomain(t, itr, nil, nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator2(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(nil, bz("")) + checkDomain(t, itr, nil, bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator3(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator4(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(bz(""), bz("")) + checkDomain(t, itr, bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator1(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(nil, nil) + checkDomain(t, itr, nil, nil) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator2(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(nil, bz("")) + checkDomain(t, itr, nil, bz("")) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator3(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator4(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +} diff --git a/tools/gometalinter.json b/tools/gometalinter.json index c4be8e839f..fe1084821b 100644 --- a/tools/gometalinter.json +++ b/tools/gometalinter.json @@ -2,7 +2,7 @@ "Linters": { "vet": "go tool vet -composites=false :PATH:LINE:MESSAGE" }, - "Enable": ["golint", "vet", "ineffassign", "unparam", "unconvert", "misspell"], + "Enable": ["golint", "vet", "ineffassign", "unconvert", "misspell"], "Deadline": "500s", "Vendor": true, "Cyclo": 11, diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 573e4d99b7..8200563b0e 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -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}) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index a7b0b4c5b0..23e95105e0 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -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]", diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index 3b024d9f95..be6dfc940d 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -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}) diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index a3508f8808..f70be18712 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -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}) }, diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 4272f4e9fa..9640c5c050 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -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})