In golang the convention is to place files that compile to a binary in the ./cmd folder of a project. For our application we have 2 binaries that we want to create:
nameserviced: This binary is similar tobitcoindor other cryptocurrency daemons in that it maintains peer connections a propagates transactions through the network. In our case, Tendermint is used for networking and transaction ordering.nameservicecli: This binary provides commands that allow users to interact with your application.
To get started create two files in the root of the project directory that will instantiate these binaries:
./cmd/nameserviced/main.go./cmd/nameservicecli/main.go
Start by adding the following code to nameserviced/main.go:
NOTE: Your application needs to import the code you just wrote. Here the import path is set to this repository (
github.com/cosmos/sdk-module-tutorial). If you are following along in your own repo you will need to change the import path to reflect that (github.com/{{ .Username }}/{{ .Project.Repo }}).
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/p2p"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
app "github.com/cosmos/sdk-module-tutorial"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
tmtypes "github.com/tendermint/tendermint/types"
)
// DefaultNodeHome sets the folder where the applcation data and configuration will be stored
var DefaultNodeHome = os.ExpandEnv("$HOME/.nameserviced")
func main() {
cobra.EnableCommandSorting = false
cdc := app.MakeCodec()
ctx := server.NewDefaultContext()
appInit := server.AppInit{
AppGenState: server.SimpleAppGenState,
}
rootCmd := &cobra.Command{
Use: "nameserviced",
Short: "nameservice App Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
rootCmd.AddCommand(InitCmd(ctx, cdc, appInit))
server.AddCommands(ctx, cdc, rootCmd, appInit, newApp, exportAppStateAndTMValidators)
// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "NS", DefaultNodeHome)
err := executor.Execute()
if err != nil {
// handle with #870
panic(err)
}
}
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewnameserviceApp(logger, db)
}
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, traceStore io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error) {
return nil, nil, nil
}
// get cmd to initialize all files for tendermint and application
// nolint: errcheck
func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
return &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))
chainID := viper.GetString(client.FlagChainID)
if chainID == "" {
chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
}
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return err
}
nodeID := string(nodeKey.ID())
pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile())
genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk)
if err != nil {
return err
}
appState, err := appInit.AppGenState(cdc, []json.RawMessage{genTx})
if err != nil {
return err
}
appStateJSON, err := cdc.MarshalJSON(appState)
if err != nil {
return err
}
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.Fprintf(os.Stderr, "%s\n", string(out))
return gaiaInit.WriteGenesisFile(config.GenesisFile(), chainID, []tmtypes.GenesisValidator{validator}, appStateJSON)
},
}
}Notes on the above code:
- Most of the code above combines the CLI commands from
- Tendermint
- Cosmos-SDK
- Your Nameservice module
- The rest of the code helps the application generate genesis state from the configuration.
Finish up by building the nameservicecli command:
NOTE: Your application needs to import the code you just wrote. Here the import path is set to this repository (
github.com/cosmos/sdk-module-tutorial). If you are following along in your own repo you will need to change the import path to reflect that (github.com/{{ .Username }}/{{ .Project.Repo }}).
package main
import (
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
app "github.com/cosmos/sdk-module-tutorial"
nameservicecmd "github.com/cosmos/sdk-module-tutorial/x/nameservice/client/cli"
)
const storeAcc = "acc"
var (
rootCmd = &cobra.Command{
Use: "nameservicecli",
Short: "nameservice Client",
}
DefaultCLIHome = os.ExpandEnv("$HOME/.nameservicecli")
)
func main() {
cobra.EnableCommandSorting = false
cdc := app.MakeCodec()
rootCmd.AddCommand(client.ConfigCmd())
rpc.AddCommands(rootCmd)
queryCmd := &cobra.Command{
Use: "query",
Aliases: []string{"q"},
Short: "Querying subcommands",
}
queryCmd.AddCommand(
rpc.BlockCommand(),
rpc.ValidatorCommand(),
)
tx.AddCommands(queryCmd, cdc)
queryCmd.AddCommand(client.LineBreak)
queryCmd.AddCommand(client.GetCommands(
authcmd.GetAccountCmd(storeAcc, cdc, authcmd.GetAccountDecoder(cdc)),
nameservicecmd.GetCmdResolveName("nameservice", cdc),
nameservicecmd.GetCmdWhois("nameservice", cdc),
)...)
txCmd := &cobra.Command{
Use: "tx",
Short: "Transactions subcommands",
}
txCmd.AddCommand(client.PostCommands(
nameservicecmd.GetCmdBuyName(cdc),
nameservicecmd.GetCmdSetName(cdc),
)...)
rootCmd.AddCommand(
queryCmd,
txCmd,
client.LineBreak,
)
rootCmd.AddCommand(
keys.Commands(),
)
executor := cli.PrepareMainCmd(rootCmd, "NS", DefaultCLIHome)
err := executor.Execute()
if err != nil {
panic(err)
}
}Notes on the above code:
- Most of the code above combines the CLI commands from
- Tendermint
- Cosmos-SDK
- Your Nameservice module