Integrate go-nitro

- Add nitro server and x/nitro module
    - wire go-nitro p2p through cometbft

- Add distsig server, currently WIP
    - integrate DKG and DSS schemes into ABCI methods

- Remove deprecated features
    - crisis module
    - module invariants

- Update to use newer SDK patterns
    - upgrade sdk to v0.53.x
    - custom address codec
    - expand use of depinject
    - migrate e2e tests to system tests
    - use depinject to set up integration tests
    - change reserved protobuf field name `cerc.registry.v1.Record.type`

- Revise & add documentation
    - TransferCoinsToModuleAccount: clarify function

- Update init.sh script
This commit is contained in:
roysc committed 2025-09-21 11:44:44 +08:00
1 parent a8ebc0aceb
commit 5aa2594073
179 files changed
+15631 -3058

No files matched your search

+89 -44
View File
@@ -1,21 +1,18 @@
package cmd
import (
"context"
"errors"
"io"
"github.com/cometbft/cometbft/node"
dbm "github.com/cosmos/cosmos-db"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
"cosmossdk.io/log"
confixcmd "cosmossdk.io/tools/confix/cmd"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/debug"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/pruning"
"github.com/cosmos/cosmos-sdk/client/rpc"
@@ -25,37 +22,41 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
"git.vdb.to/cerc-io/laconicd/app"
"git.vdb.to/cerc-io/laconicd/gql"
laconicserver "git.vdb.to/cerc-io/laconicd/server"
"git.vdb.to/cerc-io/laconicd/server/nitro"
"git.vdb.to/cerc-io/laconicd/server/relay"
)
func initRootCmd(rootCmd *cobra.Command, txConfig client.TxConfig, basicManager module.BasicManager) {
func initRootCmd(
rootCmd *cobra.Command,
txConfig client.TxConfig,
basicManager module.BasicManager,
) *laconicserver.ServerAux {
cfg := sdk.GetConfig()
cfg.Seal()
rootCmd.AddCommand(
genutilcli.InitCmd(basicManager, app.DefaultNodeHome),
NewTestnetCmd(basicManager, banktypes.GenesisBalancesIterator{}),
debug.Cmd(),
confixcmd.ConfigCommand(),
pruning.Cmd(newApp, app.DefaultNodeHome),
snapshot.Cmd(newApp),
)
server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, func(startCmd *cobra.Command) {
// Override start command to run the GQL server
newStartCmd := server.StartCmdWithOptions(newApp, app.DefaultNodeHome, server.StartCmdOptions{
PostSetup: func(svrCtx *server.Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error {
g.Go(func() error {
return gql.Server(ctx, clientCtx, svrCtx.Logger.With("module", "gql-server"))
})
return nil
},
})
startCmd.RunE = newStartCmd.RunE
addStartComponents := func(startCmd *cobra.Command) {
laconicserver.SetRequiredComponents(startCmd, &gql.Server{}, &nitro.Server{}, &relay.Server{})
}
var srv laconicserver.ServerAux
server.AddCommandsWithStartCmdOptions(rootCmd, app.DefaultNodeHome, newApp, appExport, server.StartCmdOptions{
AddFlags: addStartComponents,
// reactors will be configured at start, after the command is fully initialized
CometNodeOptions: []node.Option{srv.AddReactors},
})
// Capture the genesis command from genutilcli and add new commands
@@ -65,11 +66,60 @@ func initRootCmd(rootCmd *cobra.Command, txConfig client.TxConfig, basicManager
// add keybase, auxiliary RPC, query, genesis, and tx child commands
rootCmd.AddCommand(
server.StatusCommand(),
genesisCmd,
genesisCommand(txConfig, basicManager),
queryCommand(),
txCommand(),
keys.Commands(),
)
return &srv
}
// initializes all server components needed by a command
func initComponents(
configMap laconicserver.ConfigMap,
clientCtx client.Context,
logger log.Logger,
needComponent func(string) bool,
) ([]laconicserver.ServerComponent, error) {
var (
components []laconicserver.ServerComponent
gqlServer = &gql.Server{}
nitroServer = &nitro.Server{}
relayServer = &relay.Server{}
err error
)
if needComponent(gqlServer.Name()) {
gqlServer, err = gql.New(clientCtx, configMap, logger.With("module", "gql-server"))
if err != nil {
return nil, err
}
components = append(components, gqlServer)
}
if needComponent(nitroServer.Name()) {
nitroServer, err = nitro.New(configMap, logger, clientCtx.Keyring)
if err != nil {
return nil, err
}
components = append(components, nitroServer)
}
if needComponent(relayServer.Name()) {
relayServer, err = relay.New(configMap, logger)
if err != nil {
return nil, err
}
components = append(components, relayServer)
}
return components, nil
}
// genesisCommand builds genesis-related `laconicd genesis` command. Users may provide application specific commands as a parameter
func genesisCommand(txConfig client.TxConfig, basicManager module.BasicManager, cmds ...*cobra.Command) *cobra.Command {
cmd := genutilcli.Commands(txConfig, basicManager, app.DefaultNodeHome)
for _, subCmd := range cmds {
cmd.AddCommand(subCmd)
}
return cmd
}
func queryCommand() *cobra.Command {
@@ -83,7 +133,7 @@ func queryCommand() *cobra.Command {
}
cmd.AddCommand(
rpc.ValidatorCommand(),
rpc.WaitTxCmd(),
server.QueryBlockCmd(),
authcmd.QueryTxsByEventsCmd(),
server.QueryBlocksCmd(),
@@ -118,18 +168,26 @@ func txCommand() *cobra.Command {
return cmd
}
// newApp is an appCreator
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application {
// newApp creates the application
func newApp(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
appOpts servertypes.AppOptions,
) servertypes.Application {
baseappOptions := server.DefaultBaseappOptions(appOpts)
app, err := app.NewLaconicApp(logger, db, traceStore, true, appOpts, baseappOptions...)
ret, err := app.NewLaconicApp(
logger, db, traceStore, true,
appOpts,
baseappOptions...,
)
if err != nil {
panic(err)
}
return app
return ret
}
// appExport creates a new app (optionally at a given height) and exports state.
// appExport creates a new laconicd (optionally at a given height) and exports state.
func appExport(
logger log.Logger,
db dbm.DB,
@@ -140,18 +198,6 @@ func appExport(
appOpts servertypes.AppOptions,
modulesToExport []string,
) (servertypes.ExportedApp, error) {
var (
laconicApp *app.LaconicApp
err error
)
// this check is necessary as we use the flag in x/upgrade.
// we can exit more gracefully by checking the flag here.
homePath, ok := appOpts.Get(flags.FlagHome).(string)
if !ok || homePath == "" {
return servertypes.ExportedApp{}, errors.New("application home not set")
}
viperAppOpts, ok := appOpts.(*viper.Viper)
if !ok {
return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper")
@@ -161,18 +207,17 @@ func appExport(
viperAppOpts.Set(server.FlagInvCheckPeriod, 1)
appOpts = viperAppOpts
var laconicApp *app.LaconicApp
var err error
if height != -1 {
laconicApp, err = app.NewLaconicApp(logger, db, traceStore, false, appOpts)
if err != nil {
if laconicApp, err = app.NewLaconicApp(logger, db, traceStore, false, appOpts); err != nil {
return servertypes.ExportedApp{}, err
}
if err := laconicApp.LoadHeight(height); err != nil {
if err = laconicApp.LoadHeight(height); err != nil {
return servertypes.ExportedApp{}, err
}
} else {
laconicApp, err = app.NewLaconicApp(logger, db, traceStore, true, appOpts)
if err != nil {
if laconicApp, err = app.NewLaconicApp(logger, db, traceStore, true, appOpts); err != nil {
return servertypes.ExportedApp{}, err
}
}
+36
View File
@@ -0,0 +1,36 @@
package cmd
import (
"time"
cmtcfg "github.com/cometbft/cometbft/config"
)
// initCometBFTConfig helps to override default CometBFT config values.
func initCometBFTConfig() *cmtcfg.Config {
cfg := cmtcfg.DefaultConfig()
// display only warn logs by default for builtin modules except server, p2p, state
cfg.LogLevel = "*:warn,server:info,p2p:info,state:info"
cfg.LogLevel += ",auction:info,bond:info,registry:info,gql-server:info"
// // TODO: understand full meaning of these settings
cfg.Consensus.TimeoutPropose = 5000 * time.Millisecond
// cfg.Consensus.TimeoutProposeDelta = 500 * time.Millisecond
// cfg.Consensus.TimeoutPrevote = 1000 * time.Millisecond
// cfg.Consensus.TimeoutPrevoteDelta = 500 * time.Millisecond
// cfg.Consensus.TimeoutPrecommit = 1000 * time.Millisecond
// cfg.Consensus.TimeoutPrecommitDelta = 500 * time.Millisecond
// // start new block as soon as 2/3 precommits are received
// cfg.Consensus.TimeoutCommit = 0 * time.Second
cfg.Consensus.CreateEmptyBlocks = false
// overwrite default pprof listen address
cfg.RPC.PprofListenAddress = "localhost:6060"
// use previous db backend
cfg.DBBackend = "goleveldb"
return cfg
}
+87
View File
@@ -0,0 +1,87 @@
package cmd
import (
"fmt"
"io"
"log/slog"
"math"
"time"
"cosmossdk.io/log"
sdk_slog "cosmossdk.io/log/slog"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/lmittmann/tint"
"git.vdb.to/cerc-io/laconicd/server"
)
func createSlogLogger(cfg server.ConfigMap, out io.Writer) (log.Logger, error) {
handler, err := createSlogHandler(cfg, out)
if err != nil {
return nil, err
}
return sdk_slog.NewCustomLogger(slog.New(handler)), nil
}
func createSlogHandler(cfg server.ConfigMap, out io.Writer) (slog.Handler, error) {
var (
format string
noColor bool
level string
)
if v, ok := cfg[flags.FlagLogFormat]; ok {
format = v.(string)
}
if v, ok := cfg[flags.FlagLogNoColor]; ok {
noColor = v.(bool)
}
if v, ok := cfg[flags.FlagLogLevel]; ok {
level = v.(string)
}
logLvl, err := parseSlogLevel(level)
if err != nil {
// If the log level is not a valid zerolog level, then we try to parse it as a key filter.
filterFunc, err := log.ParseLogLevelWithParser(level, parseSlogLevel)
if err != nil {
return nil, err
}
out = log.NewFilterWriter(out, filterFunc)
}
var handler slog.Handler
// if json format is requested, ignore no_color
if format == flags.OutputFormatJSON {
handler = slog.NewJSONHandler(out, &slog.HandlerOptions{
Level: logLvl,
})
} else {
handler = tint.NewHandler(out, &tint.Options{
Level: logLvl,
TimeFormat: time.RFC3339Nano,
NoColor: noColor,
})
}
return handler, nil
}
func parseSlogLevel(l string) (slog.Level, error) {
// Legal log level values are hardcoded into server/v2 command factory:
// (trace|debug|info|warn|error|fatal|panic|disabled or '*:<level>,<key>:<level>')
// however, slog only has debug, info, warn, error
switch l {
case "debug", "trace":
return slog.LevelDebug, nil
case "info":
return slog.LevelInfo, nil
case "warn":
return slog.LevelWarn, nil
case "error":
return slog.LevelError, nil
case "fatal", "panic":
return slog.LevelError, nil
case "disabled":
return slog.Level(math.MaxInt), nil
}
return 0, fmt.Errorf("invalid log level: %s", l)
}
+95 -71
View File
@@ -1,26 +1,23 @@
package cmd
import (
"fmt"
"os"
"time"
cmtcfg "github.com/cometbft/cometbft/config"
"github.com/spf13/cobra"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
"cosmossdk.io/client/v2/autocli"
clientv2keyring "cosmossdk.io/client/v2/autocli/keyring"
"cosmossdk.io/core/address"
"cosmossdk.io/depinject"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/config"
clientconfig "github.com/cosmos/cosmos-sdk/client/config"
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
serverconfig "github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
@@ -28,16 +25,19 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"git.vdb.to/cerc-io/laconicd/app"
"git.vdb.to/cerc-io/laconicd/app/params"
"git.vdb.to/cerc-io/laconicd/gql"
types "git.vdb.to/cerc-io/laconicd/x/types/v1"
appconfig "git.vdb.to/cerc-io/laconicd/app/config"
)
const EnvPrefix = "LACONIC"
const (
// EnvPrefix is the environment variable prefix for the application
EnvPrefix = "LACONIC"
// NewRootCmd creates a new root command for laconicd. It is called once in the
// main function.
func NewRootCmd() *cobra.Command {
// DefaultNodeHome is the default data directory name for the application
DefaultNodeHome = ".laconicd"
)
// NewRootCmd creates a new root command for laconicd. It is called once in the main function.
func NewRootCmd(args ...string) (*cobra.Command, error) {
var (
txConfigOpts tx.ConfigOptions
autoCliOpts autocli.AppOptions
@@ -46,13 +46,12 @@ func NewRootCmd() *cobra.Command {
)
if err := depinject.Inject(
depinject.Configs(app.AppConfig(),
depinject.Configs(app.AppModuleConfig,
depinject.Supply(
log.NewNopLogger(),
),
depinject.Provide(
ProvideClientContext,
ProvideKeyring,
),
),
&txConfigOpts,
@@ -60,68 +59,34 @@ func NewRootCmd() *cobra.Command {
&moduleBasicManager,
&clientCtx,
); err != nil {
panic(err)
return nil, err
}
rootCmd := &cobra.Command{
Use: "laconicd",
Short: "Laconic Daemon",
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
// set the default command outputs
cmd.SetOut(cmd.OutOrStdout())
cmd.SetErr(cmd.ErrOrStderr())
clientCtx = clientCtx.WithCmdContext(cmd.Context()).WithViper(EnvPrefix)
clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
clientCtx, err = config.ReadFromClientConfig(clientCtx)
if err != nil {
return err
}
// sign mode textual is only available in online mode
if !clientCtx.Offline {
// This needs to go after ReadFromClientConfig, as that function ets the RPC client needed for SIGN_MODE_TEXTUAL.
txConfigOpts.EnabledSignModes = append(txConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL)
txConfigOpts.TextualCoinMetadataQueryFn = txmodule.NewGRPCCoinMetadataQueryFn(clientCtx)
txConfigWithTextual, err := tx.NewTxConfigWithOptions(codec.NewProtoCodec(clientCtx.InterfaceRegistry), txConfigOpts)
if err != nil {
return err
}
clientCtx = clientCtx.WithTxConfig(txConfigWithTextual)
}
if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil {
return err
}
// overwrite the minimum gas price from the app configuration
srvCfg := serverconfig.DefaultConfig()
srvCfg.MinGasPrices = fmt.Sprintf("0%s", params.CoinUnit)
// overwrite the block timeout
cmtCfg := cmtcfg.DefaultConfig()
cmtCfg.Consensus.TimeoutCommit = 3 * time.Second
cmtCfg.LogLevel = "*:error,p2p:info,state:info,auction:info,bond:info,registry:info,gql-server:info" // better default logging
return server.InterceptConfigsPreRunHandler(cmd, serverconfig.DefaultConfigTemplate, srvCfg, cmtCfg)
},
Use: "laconicd",
SilenceErrors: true,
SilenceUsage: true, // prevent usage printing on every error
PersistentPreRunE: RootCommandPersistentPreRun(clientCtx, txConfigOpts),
}
initRootCmd(rootCmd, clientCtx.TxConfig, moduleBasicManager)
initCtx := initRootCmd(rootCmd, clientCtx.TxConfig, moduleBasicManager)
nodeCmds := nodeservice.NewNodeCommands()
autoCliOpts.ModuleOptions = make(map[string]*autocliv1.ModuleOptions)
autoCliOpts.ModuleOptions[nodeCmds.Name()] = nodeCmds.AutoCLIOptions()
if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil {
panic(err)
return nil, err
}
rootCmd.SetArgs(args)
// Add flags for GQL server.
rootCmd = gql.AddGQLFlags(rootCmd)
return rootCmd
// now enhance the subcommand
subCmd, _, err := rootCmd.Find(args)
if err != nil {
return nil, err
}
initCtx.AddComponents(subCmd, initComponents)
return rootCmd, nil
}
func ProvideClientContext(
@@ -141,7 +106,7 @@ func ProvideClientContext(
WithViper(EnvPrefix) // env variable prefix
// Read the config again to overwrite the default values with the values from the config file
clientCtx, _ = config.ReadFromClientConfig(clientCtx)
clientCtx, _ = clientconfig.ReadFromClientConfig(clientCtx)
// Workaround: Unset clientCtx.HomeDir and clientCtx.KeyringDir from depinject clientCtx as they are given precedence over
// the CLI args (--home flag) in some commands
@@ -149,9 +114,10 @@ func ProvideClientContext(
clientCtx.HomeDir = ""
clientCtx.KeyringDir = ""
// XXX TODO fix after rebase
// Custom LockupAccount type needs to be registered
interfaceRegistry.RegisterImplementations((*types.LockupAccountI)(nil), &types.LockupAccount{})
interfaceRegistry.RegisterImplementations((*authtypes.GenesisAccount)(nil), &types.LockupAccount{})
// interfaceRegistry.RegisterImplementations((*types.LockupAccountI)(nil), &types.LockupAccount{})
// interfaceRegistry.RegisterImplementations((*authtypes.GenesisAccount)(nil), &types.LockupAccount{})
return clientCtx
}
@@ -164,3 +130,61 @@ func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clien
return keyring.NewAutoCLIKeyring(kb)
}
func RootCommandPersistentPreRun(
clientCtx client.Context,
txConfigOpts tx.ConfigOptions,
) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error {
// set the default command outputs
cmd.SetOut(cmd.OutOrStdout())
cmd.SetErr(cmd.ErrOrStderr())
clientCtx = clientCtx.WithCmdContext(cmd.Context()).WithViper("")
clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
clientCtx, err = clientconfig.ReadFromClientConfig(clientCtx)
if err != nil {
return err
}
// This needs to go after ReadFromClientConfig, as that function
// sets the RPC client needed for SIGN_MODE_TEXTUAL. This sign mode
// is only available if the client is online.
if !clientCtx.Offline {
txConfigOpts.EnabledSignModes = append(txConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL)
txConfigOpts.TextualCoinMetadataQueryFn = txmodule.NewGRPCCoinMetadataQueryFn(clientCtx)
txConfigWithTextual, err := tx.NewTxConfigWithOptions(codec.NewProtoCodec(clientCtx.InterfaceRegistry), txConfigOpts)
if err != nil {
return err
}
clientCtx = clientCtx.WithTxConfig(txConfigWithTextual)
}
if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil {
return err
}
appConfig := appconfig.DefaultConfig()
cmtConfig := initCometBFTConfig()
serverCtx, err := server.InterceptConfigsAndCreateContext(
cmd, appconfig.DefaultConfigTemplate, appConfig, cmtConfig,
)
if err != nil {
return err
}
// use slog-based logger to combine with nitro logs
logger, err := createSlogLogger(serverCtx.Viper.AllSettings(), cmd.OutOrStdout())
if err != nil {
return err
}
serverCtx.Logger = logger.With(log.ModuleKey, "server")
return server.SetCmdServerContext(cmd, serverCtx)
}
}
+67
View File
@@ -0,0 +1,67 @@
package cmd_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
"git.vdb.to/cerc-io/laconicd/app"
"git.vdb.to/cerc-io/laconicd/cmd/laconicd/cmd"
)
func TestInitCmd(t *testing.T) {
rootCmd, err := cmd.NewRootCmd()
require.NoError(t, err)
rootCmd.SetArgs([]string{
"init", // Test the init cmd
"simapp-test", // Moniker
fmt.Sprintf("--%s=%s", cli.FlagOverwrite, "true"), // Overwrite genesis.json, in case it already exists
})
require.NoError(t, svrcmd.Execute(rootCmd, "", app.DefaultNodeHome))
}
func TestHomeFlagRegistration(t *testing.T) {
homeDir := "/tmp/foo"
rootCmd, err := cmd.NewRootCmd()
require.NoError(t, err)
rootCmd.SetArgs([]string{
"query",
fmt.Sprintf("--%s", flags.FlagHome),
homeDir,
})
require.NoError(t, svrcmd.Execute(rootCmd, "", app.DefaultNodeHome))
result, err := rootCmd.Flags().GetString(flags.FlagHome)
require.NoError(t, err)
require.Equal(t, result, homeDir)
}
func TestHelpRequested(t *testing.T) {
argz := [][]string{
{"query", "--help"},
{"query", "tx", "-h"},
{"--help"},
{"start", "-h"},
}
for _, args := range argz {
rootCmd, err := cmd.NewRootCmd(args...)
require.NoError(t, err)
var out bytes.Buffer
rootCmd.SetArgs(args)
rootCmd.SetOut(&out)
require.NoError(t, rootCmd.Execute())
require.Contains(t, out.String(), args[0])
require.Contains(t, out.String(), "--help")
require.Contains(t, out.String(), "Usage:")
}
}
+598
View File
@@ -0,0 +1,598 @@
package cmd
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"time"
cmtcfg "github.com/cometbft/cometbft/config"
cmtconfig "github.com/cometbft/cometbft/config"
cmttime "github.com/cometbft/cometbft/types/time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"cosmossdk.io/math"
"cosmossdk.io/math/unsafe"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/server"
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/version"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"git.vdb.to/cerc-io/laconicd/app"
appconfig "git.vdb.to/cerc-io/laconicd/app/config"
)
var (
flagNodeDirPrefix = "node-dir-prefix"
flagNumValidators = "validator-count"
flagOutputDir = "output-dir"
flagNodeDaemonHome = "node-daemon-home"
flagStartingIPAddress = "starting-ip-address"
flagListenIPAddress = "listen-ip-address"
flagEnableLogging = "enable-logging"
flagGRPCAddress = "grpc.address"
flagRPCAddress = "rpc.address"
flagAPIAddress = "api.address"
flagPrintMnemonic = "print-mnemonic"
flagStakingDenom = "staking-denom"
flagCommitTimeout = "commit-timeout"
flagSingleHost = "single-host"
)
type initArgs struct {
algo string
chainID string
keyringBackend string
minGasPrices string
nodeDaemonHome string
nodeDirPrefix string
numValidators int
outputDir string
startingIPAddress string
listenIPAddress string
singleMachine bool
bondTokenDenom string
}
type startArgs struct {
algo string
apiAddress string
chainID string
enableLogging bool
grpcAddress string
minGasPrices string
numValidators int
outputDir string
printMnemonic bool
rpcAddress string
timeoutCommit time.Duration
}
func addTestnetFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().IntP(flagNumValidators, "v", 4, "Number of validators to initialize the testnet with")
cmd.Flags().StringP(flagOutputDir, "o", "./.testnets", "Directory to store initialization data for the testnet")
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)")
cmd.Flags().String(flags.FlagKeyType, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for")
// support old flags name for backwards compatibility
cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
if name == flags.FlagKeyAlgorithm {
name = flags.FlagKeyType
}
return pflag.NormalizedName(name)
})
}
// NewTestnetCmd creates a root testnet command with subcommands to run an in-process testnet or initialize
// validator configuration files for running a multi-validator testnet in a separate process
func NewTestnetCmd(mm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command {
testnetCmd := &cobra.Command{
Use: "testnet",
Short: "subcommands for starting or configuring local testnets",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
testnetCmd.AddCommand(testnetStartCmd())
testnetCmd.AddCommand(testnetInitFilesCmd(mm, genBalIterator))
return testnetCmd
}
// testnetInitFilesCmd returns a cmd to initialize all files for CometBFT testnet and application
func testnetInitFilesCmd(mm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command {
cmd := &cobra.Command{
Use: "init-files",
Short: "Initialize config directories & files for a multi-validator testnet running locally via separate processes (e.g. Docker Compose or similar)",
Long: fmt.Sprintf(`init-files will setup one directory per validator and populate each with
necessary files (private validator, genesis, config, etc.) for running validator nodes.
Booting up a network with these validator folders is intended to be used with Docker Compose,
or a similar setup where each node has a manually configurable IP address.
Note, strict routability for addresses is turned off in the config file.
Example:
%s testnet init-files --validator-count 4 --output-dir ./.testnets --starting-ip-address 192.168.10.2
`, version.AppName),
RunE: func(cmd *cobra.Command, _ []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
config := cmtcfg.DefaultConfig()
args := initArgs{}
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir)
args.keyringBackend, _ = cmd.Flags().GetString(flags.FlagKeyringBackend)
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID)
args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices)
args.nodeDirPrefix, _ = cmd.Flags().GetString(flagNodeDirPrefix)
args.nodeDaemonHome, _ = cmd.Flags().GetString(flagNodeDaemonHome)
args.startingIPAddress, _ = cmd.Flags().GetString(flagStartingIPAddress)
args.listenIPAddress, _ = cmd.Flags().GetString(flagListenIPAddress)
args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators)
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyType)
args.bondTokenDenom, _ = cmd.Flags().GetString(flagStakingDenom)
args.singleMachine, _ = cmd.Flags().GetBool(flagSingleHost)
config.Consensus.TimeoutCommit, err = cmd.Flags().GetDuration(flagCommitTimeout)
if err != nil {
return err
}
return initTestnetFiles(clientCtx, cmd, config, mm, genBalIterator, args)
},
}
addTestnetFlagsToCmd(cmd)
cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix for the name of per-validator subdirectories (to be number-suffixed like node0, node1, ...)")
cmd.Flags().String(flagNodeDaemonHome, "laconicd", "Home directory of the node's daemon configuration")
cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)")
cmd.Flags().String(flagListenIPAddress, "127.0.0.1", "TCP or UNIX socket IP address for the RPC server to listen on")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
cmd.Flags().Duration(flagCommitTimeout, 5*time.Second, "Time to wait after a block commit before starting on the new height")
cmd.Flags().Bool(flagSingleHost, false, "Cluster runs on a single host machine with different ports")
cmd.Flags().String(flagStakingDenom, sdk.DefaultBondDenom, "Default staking token denominator")
return cmd
}
// testnetStartCmd returns a cmd to start multi validator in-process testnet
func testnetStartCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Launch an in-process multi-validator testnet",
Long: fmt.Sprintf(`testnet will launch an in-process multi-validator testnet,
and generate a directory for each validator populated with necessary
configuration files (private validator, genesis, config, etc.).
Example:
%s testnet --validator-count 4 --output-dir ./.testnets
`, version.AppName),
RunE: func(cmd *cobra.Command, _ []string) (err error) {
args := startArgs{}
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir)
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID)
args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices)
args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators)
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyType)
args.enableLogging, _ = cmd.Flags().GetBool(flagEnableLogging)
args.rpcAddress, _ = cmd.Flags().GetString(flagRPCAddress)
args.apiAddress, _ = cmd.Flags().GetString(flagAPIAddress)
args.grpcAddress, _ = cmd.Flags().GetString(flagGRPCAddress)
args.printMnemonic, _ = cmd.Flags().GetBool(flagPrintMnemonic)
return startTestnet(cmd, args)
},
}
addTestnetFlagsToCmd(cmd)
cmd.Flags().Bool(flagEnableLogging, false, "Enable INFO logging of CometBFT validator nodes")
cmd.Flags().String(flagRPCAddress, "tcp://0.0.0.0:26657", "the RPC address to listen on")
cmd.Flags().String(flagAPIAddress, "tcp://0.0.0.0:1317", "the address to listen on for REST API")
cmd.Flags().String(flagGRPCAddress, "0.0.0.0:9090", "the gRPC server address to listen on")
cmd.Flags().Bool(flagPrintMnemonic, true, "print mnemonic of first validator to stdout for manual testing")
return cmd
}
const nodeDirPerm = 0o755
// initTestnetFiles initializes testnet files for a testnet to be run in a separate process
func initTestnetFiles(
clientCtx client.Context,
cmd *cobra.Command,
nodeConfig *cmtconfig.Config,
mm module.BasicManager,
genBalIterator banktypes.GenesisBalancesIterator,
args initArgs,
) error {
if args.chainID == "" {
args.chainID = "chain-" + unsafe.Str(6)
}
nodeIDs := make([]string, args.numValidators)
valPubKeys := make([]cryptotypes.PubKey, args.numValidators)
appConfig := appconfig.DefaultConfig()
appConfig.MinGasPrices = args.minGasPrices
appConfig.API.Enable = true
appConfig.Telemetry.Enabled = true
appConfig.Telemetry.PrometheusRetentionTime = 60
appConfig.Telemetry.EnableHostnameLabel = false
appConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", args.chainID}}
var (
genAccounts []authtypes.GenesisAccount
genBalances []banktypes.Balance
genFiles []string
)
const (
rpcPort = 26657
apiPort = 1317
grpcPort = 9090
)
p2pPortStart := 26656
inBuf := bufio.NewReader(cmd.InOrStdin())
// generate private keys, node IDs, and initial transactions
for i := 0; i < args.numValidators; i++ {
var portOffset int
if args.singleMachine {
portOffset = i
p2pPortStart = 16656 // use different start point to not conflict with rpc port
nodeConfig.P2P.AddrBookStrict = false
nodeConfig.P2P.PexReactor = false
nodeConfig.P2P.AllowDuplicateIP = true
appConfig.API.Address = fmt.Sprintf("tcp://0.0.0.0:%d", apiPort+portOffset)
appConfig.GRPC.Address = fmt.Sprintf("0.0.0.0:%d", grpcPort+portOffset)
}
nodeDirName := fmt.Sprintf("%s%d", args.nodeDirPrefix, i)
nodeDir := filepath.Join(args.outputDir, nodeDirName, args.nodeDaemonHome)
gentxsDir := filepath.Join(args.outputDir, "gentxs")
nodeConfig.SetRoot(nodeDir)
nodeConfig.Moniker = nodeDirName
nodeConfig.RPC.ListenAddress = fmt.Sprintf("tcp://%s:%d", args.listenIPAddress, rpcPort+portOffset)
if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
_ = os.RemoveAll(args.outputDir)
return err
}
var (
err error
ip string
)
if args.singleMachine {
ip = "127.0.0.1"
} else {
ip, err = getIP(i, args.startingIPAddress)
if err != nil {
_ = os.RemoveAll(args.outputDir)
return err
}
}
nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig)
if err != nil {
_ = os.RemoveAll(args.outputDir)
return err
}
memo := fmt.Sprintf("%s@%s:%d", nodeIDs[i], ip, p2pPortStart+portOffset)
genFiles = append(genFiles, nodeConfig.GenesisFile())
kb, err := keyring.New(sdk.KeyringServiceName(), args.keyringBackend, nodeDir, inBuf, clientCtx.Codec)
if err != nil {
return err
}
keyringAlgos, _ := kb.SupportedAlgorithms()
algo, err := keyring.NewSigningAlgoFromString(args.algo, keyringAlgos)
if err != nil {
return err
}
addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo)
if err != nil {
_ = os.RemoveAll(args.outputDir)
return err
}
info := map[string]string{"secret": secret}
cliPrint, err := json.Marshal(info)
if err != nil {
return err
}
// save private key seed words
if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), nodeDir, cliPrint); err != nil {
return err
}
accTokens := sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction)
accStakingTokens := sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction)
coins := sdk.Coins{
sdk.NewCoin("testtoken", accTokens),
sdk.NewCoin(args.bondTokenDenom, accStakingTokens),
}
genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins.Sort()})
genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0))
valAddr := sdk.ValAddress(addr)
valStr := valAddr.String()
valTokens := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction)
createValMsg, err := stakingtypes.NewMsgCreateValidator(
valStr,
valPubKeys[i],
sdk.NewCoin(args.bondTokenDenom, valTokens),
stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
stakingtypes.NewCommissionRates(math.LegacyOneDec(), math.LegacyOneDec(), math.LegacyOneDec()),
math.OneInt(),
)
if err != nil {
return err
}
txBuilder := clientCtx.TxConfig.NewTxBuilder()
if err := txBuilder.SetMsgs(createValMsg); err != nil {
return err
}
txBuilder.SetMemo(memo)
txFactory := tx.Factory{}
txFactory = txFactory.
WithChainID(args.chainID).
WithMemo(memo).
WithKeybase(kb).
WithTxConfig(clientCtx.TxConfig)
if err := tx.Sign(cmd.Context(), txFactory, nodeDirName, txBuilder, true); err != nil {
return err
}
txBz, err := clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
if err != nil {
return err
}
if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil {
return err
}
srvconfig.SetConfigTemplate(appconfig.DefaultConfigTemplate)
srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config", "app.toml"), appConfig)
}
if err := initGenFiles(clientCtx, mm, args.chainID, genAccounts, genBalances, genFiles, args.numValidators); err != nil {
return err
}
err := collectGenFiles(
clientCtx, nodeConfig, args.chainID, nodeIDs, valPubKeys, args.numValidators,
args.outputDir, args.nodeDirPrefix, args.nodeDaemonHome, genBalIterator,
rpcPort, p2pPortStart, args.singleMachine,
)
if err != nil {
return err
}
server.GetServerContextFromCmd(cmd).Viper.Set(flags.FlagHome, nodeConfig.RootDir)
cmd.PrintErrf("Successfully initialized %d node directories\n", args.numValidators)
return nil
}
func initGenFiles(
clientCtx client.Context, mm module.BasicManager, chainID string,
genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance,
genFiles []string, numValidators int,
) error {
appGenState := mm.DefaultGenesis(clientCtx.Codec)
// set the accounts in the genesis state
var authGenState authtypes.GenesisState
clientCtx.Codec.MustUnmarshalJSON(appGenState[authtypes.ModuleName], &authGenState)
accounts, err := authtypes.PackAccounts(genAccounts)
if err != nil {
return err
}
authGenState.Accounts = accounts
appGenState[authtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&authGenState)
// set the balances in the genesis state
var bankGenState banktypes.GenesisState
clientCtx.Codec.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState)
bankGenState.Balances = banktypes.SanitizeGenesisBalances(genBalances)
for _, bal := range bankGenState.Balances {
bankGenState.Supply = bankGenState.Supply.Add(bal.Coins...)
}
appGenState[banktypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&bankGenState)
appGenStateJSON, err := json.MarshalIndent(appGenState, "", " ")
if err != nil {
return err
}
appGenesis := genutiltypes.NewAppGenesisWithVersion(chainID, appGenStateJSON)
// generate empty genesis files for each validator and save
for i := 0; i < numValidators; i++ {
if err := appGenesis.SaveAs(genFiles[i]); err != nil {
return err
}
}
return nil
}
func collectGenFiles(
clientCtx client.Context, nodeConfig *cmtconfig.Config, chainID string,
nodeIDs []string, valPubKeys []cryptotypes.PubKey, numValidators int,
outputDir, nodeDirPrefix, nodeDaemonHome string, genBalIterator banktypes.GenesisBalancesIterator,
rpcPortStart, p2pPortStart int,
singleMachine bool,
) error {
var appState json.RawMessage
genTime := cmttime.Now()
for i := 0; i < numValidators; i++ {
if singleMachine {
portOffset := i
nodeConfig.RPC.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", rpcPortStart+portOffset)
nodeConfig.P2P.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", p2pPortStart+portOffset)
}
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
gentxsDir := filepath.Join(outputDir, "gentxs")
nodeConfig.Moniker = nodeDirName
nodeConfig.SetRoot(nodeDir)
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey)
appGenesis, err := genutiltypes.AppGenesisFromFile(nodeConfig.GenesisFile())
if err != nil {
return err
}
nodeAppState, err := genutil.GenAppStateFromConfig(
clientCtx.Codec,
clientCtx.TxConfig,
nodeConfig,
initCfg,
appGenesis,
genBalIterator,
genutiltypes.DefaultMessageValidator,
clientCtx.TxConfig.SigningContext().ValidatorAddressCodec(),
)
if err != nil {
return err
}
if appState == nil {
// set the canonical application state (they should not differ)
appState = nodeAppState
}
genFile := nodeConfig.GenesisFile()
// overwrite each validator's genesis file to have a canonical genesis time
if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil {
return err
}
}
return nil
}
func getIP(i int, startingIPAddr string) (ip string, err error) {
if len(startingIPAddr) == 0 {
ip, err = server.ExternalIP()
if err != nil {
return "", err
}
return ip, nil
}
return calculateIP(startingIPAddr, i)
}
func calculateIP(ip string, i int) (string, error) {
ipv4 := net.ParseIP(ip).To4()
if ipv4 == nil {
return "", fmt.Errorf("%v: non ipv4 address", ip)
}
for j := 0; j < i; j++ {
ipv4[3]++
}
return ipv4.String(), nil
}
func writeFile(name, dir string, contents []byte) error {
file := filepath.Join(dir, name)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("could not create directory %q: %w", dir, err)
}
if err := os.WriteFile(file, contents, 0o600); err != nil {
return err
}
return nil
}
// startTestnet starts an in-process testnet
func startTestnet(cmd *cobra.Command, args startArgs) error {
networkConfig := network.DefaultConfig(app.NewTestNetworkFixture)
// Default networkConfig.ChainID is random, and we should only override it if chainID provided
// is non-empty
if args.chainID != "" {
networkConfig.ChainID = args.chainID
}
networkConfig.SigningAlgo = args.algo
networkConfig.MinGasPrices = args.minGasPrices
networkConfig.NumValidators = args.numValidators
networkConfig.EnableLogging = args.enableLogging
networkConfig.RPCAddress = args.rpcAddress
networkConfig.APIAddress = args.apiAddress
networkConfig.GRPCAddress = args.grpcAddress
networkConfig.PrintMnemonic = args.printMnemonic
networkConfig.TimeoutCommit = args.timeoutCommit
networkLogger := network.NewCLILogger(cmd)
baseDir := fmt.Sprintf("%s/%s", args.outputDir, networkConfig.ChainID)
if _, err := os.Stat(baseDir); !os.IsNotExist(err) {
return fmt.Errorf(
"testnests directory already exists for chain-id '%s': %s, please remove or select a new --chain-id",
networkConfig.ChainID, baseDir)
}
testnet, err := network.New(networkLogger, baseDir, networkConfig)
if err != nil {
return err
}
if _, err := testnet.WaitForHeight(1); err != nil {
return err
}
cmd.Println("press the Enter Key to terminate")
if _, err := fmt.Scanln(); err != nil { // wait for Enter Key
return err
}
testnet.Cleanup()
return nil
}
+72
View File
@@ -0,0 +1,72 @@
package cmd
import (
"context"
"fmt"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/types/module"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/consensus"
"github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/cosmos/cosmos-sdk/x/staking"
)
func Test_TestnetCmd(t *testing.T) {
moduleBasic := module.NewBasicManager(
auth.AppModuleBasic{},
genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator),
bank.AppModuleBasic{},
staking.AppModuleBasic{},
distribution.AppModuleBasic{},
consensus.AppModuleBasic{},
)
home := t.TempDir()
encodingConfig := moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}, staking.AppModuleBasic{})
logger := log.NewNopLogger()
cfg, err := genutiltest.CreateDefaultCometConfig(home)
require.NoError(t, err)
err = genutiltest.ExecInitCmd(moduleBasic, home, encodingConfig.Codec)
require.NoError(t, err)
serverCtx := server.NewContext(viper.New(), cfg, logger)
clientCtx := client.Context{}.
WithCodec(encodingConfig.Codec).
WithHomeDir(home).
WithTxConfig(encodingConfig.TxConfig)
ctx := context.Background()
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
cmd := testnetInitFilesCmd(moduleBasic, banktypes.GenesisBalancesIterator{})
cmd.SetArgs([]string{
fmt.Sprintf("--%s=test", flags.FlagKeyringBackend, keyring.BackendTest),
fmt.Sprintf("--output-dir=%s", home),
})
err = cmd.ExecuteContext(ctx)
require.NoError(t, err)
genFile := cfg.GenesisFile()
appState, _, err := genutiltypes.GenesisStateFromGenFile(genFile)
require.NoError(t, err)
bankGenState := banktypes.GetGenesisStateFromAppState(encodingConfig.Codec, appState)
require.NotEmpty(t, bankGenState.Supply.String())
}
-28
View File
@@ -1,28 +0,0 @@
package main_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
"git.vdb.to/cerc-io/laconicd/app"
"git.vdb.to/cerc-io/laconicd/cmd/laconicd/cmd"
)
func TestInitCmd(t *testing.T) {
rootCmd := cmd.NewRootCmd()
rootCmd.SetArgs([]string{
"init", // Test the init cmd
"localtestnet", // Moniker
fmt.Sprintf("--%s=%s", cli.FlagOverwrite, "true"), // Overwrite genesis.json, in case it already exists
fmt.Sprintf("--%s=%s", flags.FlagChainID, "laconic_9000-1"),
})
err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome)
require.NoError(t, err)
}
+16 -6
View File
@@ -1,22 +1,32 @@
package main
import (
"errors"
"fmt"
"os"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"git.vdb.to/cerc-io/laconicd/app"
"git.vdb.to/cerc-io/laconicd/app/params"
_ "git.vdb.to/cerc-io/laconicd/app/params" // import for side-effects
"git.vdb.to/cerc-io/laconicd/cmd/laconicd/cmd"
)
func main() {
params.SetAddressPrefixes()
rootCmd := cmd.NewRootCmd()
if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil {
fmt.Fprintln(rootCmd.OutOrStderr(), err)
// reproduce default cobra behavior so that eager parsing of flags is possible.
// see: https://github.com/spf13/cobra/blob/e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec/command.go#L1082
args := os.Args[1:]
rootCmd, err := cmd.NewRootCmd(args...)
if err != nil {
if _, pErr := fmt.Fprintln(os.Stderr, err); pErr != nil {
panic(errors.Join(err, pErr))
}
os.Exit(1)
}
if err := svrcmd.Execute(rootCmd, cmd.EnvPrefix, app.DefaultNodeHome); err != nil {
if _, pErr := fmt.Fprintln(rootCmd.OutOrStderr(), err); pErr != nil {
panic(errors.Join(err, pErr))
}
os.Exit(1)
}
}