diff --git a/gno.land/cmd/gnoland/genesis.go b/gno.land/cmd/gnoland/genesis.go index 37c0f8f2926..1e10b32c660 100644 --- a/gno.land/cmd/gnoland/genesis.go +++ b/gno.land/cmd/gnoland/genesis.go @@ -1,11 +1,14 @@ package main import ( + "errors" "flag" "github.com/gnolang/gno/tm2/pkg/commands" ) +var errUnableToLoadGenesis = errors.New("unable to load genesis") + func newGenesisCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ diff --git a/gno.land/cmd/gnoland/genesis_balances.go b/gno.land/cmd/gnoland/genesis_balances.go index c8cd1c539f5..003572c5e0c 100644 --- a/gno.land/cmd/gnoland/genesis_balances.go +++ b/gno.land/cmd/gnoland/genesis_balances.go @@ -1,11 +1,14 @@ package main import ( + "errors" "flag" "github.com/gnolang/gno/tm2/pkg/commands" ) +var errBalanceNotFound = errors.New("genesis balances entry does not exist") + type balancesCfg struct { commonCfg } diff --git a/gno.land/cmd/gnoland/genesis_balances_remove.go b/gno.land/cmd/gnoland/genesis_balances_remove.go index 58a02319c8d..54d7f69bd45 100644 --- a/gno.land/cmd/gnoland/genesis_balances_remove.go +++ b/gno.land/cmd/gnoland/genesis_balances_remove.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "flag" "fmt" @@ -12,11 +11,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" ) -var ( - errUnableToLoadGenesis = errors.New("unable to load genesis") - errBalanceNotFound = errors.New("genesis balances entry does not exist") -) - type balancesRemoveCfg struct { rootCfg *balancesCfg diff --git a/gno.land/cmd/gnoland/genesis_txs.go b/gno.land/cmd/gnoland/genesis_txs.go index 4d9ad8c11db..70dc2436093 100644 --- a/gno.land/cmd/gnoland/genesis_txs.go +++ b/gno.land/cmd/gnoland/genesis_txs.go @@ -27,7 +27,9 @@ func newTxsCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newTxsAddCmd(cfg, io), + newTxsGenerateCmd(cfg, io), newTxsRemoveCmd(cfg, io), + newTxsClearCmd(cfg, io), newTxsExportCmd(cfg, io), ) diff --git a/gno.land/cmd/gnoland/genesis_txs_add.go b/gno.land/cmd/gnoland/genesis_txs_add.go index 06019f7ef59..9bc4b745ece 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add.go +++ b/gno.land/cmd/gnoland/genesis_txs_add.go @@ -69,6 +69,26 @@ func execTxsAdd( parsedTxs = append(parsedTxs, txs...) } + // append file txs to genesis + if err := appendTxs(genesis, parsedTxs); err != nil { + return err + } + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Saved %d transactions to genesis.json", + len(parsedTxs), + ) + + return nil +} + +// appendTxs appends the txs to GenesisDoc +func appendTxs(genesis *types.GenesisDoc, txs []std.Tx) error { // Initialize the app state if it's not present if genesis.AppState == nil { genesis.AppState = gnoland.GnoGenesisState{} @@ -77,12 +97,12 @@ func execTxsAdd( state := genesis.AppState.(gnoland.GnoGenesisState) // Left merge the transactions - fileTxStore := txStore(parsedTxs) + inTxStore := txStore(txs) genesisTxStore := txStore(state.Txs) // The genesis transactions have preference with the order // in the genesis.json - if err := genesisTxStore.leftMerge(fileTxStore); err != nil { + if err := genesisTxStore.leftMerge(inTxStore); err != nil { return err } @@ -90,16 +110,6 @@ func execTxsAdd( state.Txs = genesisTxStore genesis.AppState = state - // Save the updated genesis - if err := genesis.SaveAs(cfg.genesisPath); err != nil { - return fmt.Errorf("unable to save genesis.json, %w", err) - } - - io.Printfln( - "Saved %d transactions to genesis.json", - len(parsedTxs), - ) - return nil } diff --git a/gno.land/cmd/gnoland/genesis_txs_clear.go b/gno.land/cmd/gnoland/genesis_txs_clear.go new file mode 100644 index 00000000000..47c7ee3ddc1 --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_clear.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "flag" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +// newTxsClearCmd creates the genesis txs clear subcommand +func newTxsClearCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "clear", + ShortUsage: "txs clear", + ShortHelp: "clears all the transactions from the genesis.json", + }, + commands.NewEmptyConfig(), + func(ctx context.Context, args []string) error { + return execTxsClear(txsCfg, args, io) + }, + ) +} + +func execTxsClear(cfg *txsCfg, args []string, io commands.IO) error { + if len(args) > 0 { + return flag.ErrHelp + } + + // Load the genesis + genesis, err := types.GenesisDocFromFile(cfg.genesisPath) + if err != nil { + return fmt.Errorf("unable to load genesis, %w", err) + } + + state := genesis.AppState.(gnoland.GnoGenesisState) + + totalTxs := len(state.Txs) + + // Remove all txs + state.Txs = nil + genesis.AppState = state + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "%d txs removed!", + totalTxs, + ) + + return nil +} diff --git a/gno.land/cmd/gnoland/genesis_txs_clear_test.go b/gno.land/cmd/gnoland/genesis_txs_clear_test.go new file mode 100644 index 00000000000..35978272978 --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_clear_test.go @@ -0,0 +1,109 @@ +package main + +import ( + "context" + "flag" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Txs_Clear(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "clear", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("too many arg", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "clear", + "arg", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, flag.ErrHelp.Error()) + }) + + t.Run("clear genesis packages", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + state := gnoland.GnoGenesisState{ + // Set an initial addpkg tx + Txs: []std.Tx{ + { + Msgs: []std.Msg{ + vmm.MsgAddPackage{ + Creator: getDummyKey(t).Address(), + Package: &std.MemPackage{ + Name: "dummy", + Path: "gno.land/r/demo/dummy", + }, + }, + }, + }, + }, + } + genesis.AppState = state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + require.Equal(t, 1, len(state.Txs)) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "clear", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + require.Equal(t, 0, len(state.Txs)) + }) +} diff --git a/gno.land/cmd/gnoland/genesis_txs_generate.go b/gno.land/cmd/gnoland/genesis_txs_generate.go new file mode 100644 index 00000000000..a6ab808d0dc --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_generate.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ( + test1 = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + defaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) +) + +var errUnableToLoadPackages = errors.New("unable to load packages") + +// newTxsGenerateCmd creates the genesis txs generate subcommand +func newTxsGenerateCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "generate", + ShortUsage: "txs generate [...]", + ShortHelp: "generates addpkg txs from dir and add them to genesis.json", + }, + commands.NewEmptyConfig(), + func(ctx context.Context, args []string) error { + return execTxsGenerate(txsCfg, args, io) + }, + ) +} + +func execTxsGenerate(cfg *txsCfg, args []string, io commands.IO) error { + if len(args) < 1 { + return flag.ErrHelp + } + + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + txs, err := gnoland.LoadPackagesFromDirs(args, test1, defaultFee, nil) + if err != nil { + return fmt.Errorf("%w: %w", errUnableToLoadPackages, err) + } + + // append generated addpkg txs to genesis + if err := appendTxs(genesis, txs); err != nil { + return err + } + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Saved %d transactions to genesis.json", + len(txs), + ) + + return nil +} diff --git a/gno.land/cmd/gnoland/genesis_txs_generate_test.go b/gno.land/cmd/gnoland/genesis_txs_generate_test.go new file mode 100644 index 00000000000..21ec5a6da17 --- /dev/null +++ b/gno.land/cmd/gnoland/genesis_txs_generate_test.go @@ -0,0 +1,123 @@ +package main + +import ( + "context" + "flag" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Txs_Generate(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "generate", + "./", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("missing args", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "generate", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, flag.ErrHelp.Error()) + }) + + t.Run("invalid dir", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "generate", + "invalid", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errUnableToLoadPackages.Error()) + }) + + t.Run("packages from dir", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "genesis", + "txs", + "generate", + "../../../examples/gno.land/p/demo/avl", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + require.Equal(t, 1, len(state.Txs)) + require.Equal(t, 1, len(state.Txs[0].Msgs)) + + msgAddPkg, ok := state.Txs[0].Msgs[0].(vmm.MsgAddPackage) + require.True(t, ok) + + assert.Equal(t, "gno.land/p/demo/avl", msgAddPkg.Package.Path) + }) +} diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index d45236563ad..04d38ab7206 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -403,7 +403,7 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro examplesDir := filepath.Join(c.gnoRootDir, "examples") test1 := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, test1, defaultFee, nil) + pkgsTxs, err := gnoland.LoadPackagesFromDirs([]string{examplesDir}, test1, defaultFee, nil) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) } diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index d99dfe7e5ed..b7945bbc5c0 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -86,11 +86,15 @@ func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]st // LoadPackagesFromDir loads gno packages from a directory. // It creates and returns a list of transactions based on these packages. -func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { - // list all packages from target path - pkgs, err := gnomod.ListPkgs(dir) - if err != nil { - return nil, fmt.Errorf("listing gno packages: %w", err) +func LoadPackagesFromDirs(dirs []string, creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { + // list all packages from target paths + var pkgs gnomod.PkgList + for _, dir := range dirs { + var err error + pkgs, err = gnomod.ListPkgs(dir) + if err != nil { + return nil, fmt.Errorf("listing gno packages: %w", err) + } } // Sort packages by dependencies. diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go index 66fc50e7953..071cdf3673d 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -115,7 +115,7 @@ func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std examplesDir := filepath.Join(gnoroot, "examples") defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee, nil) + txs, err := gnoland.LoadPackagesFromDirs([]string{examplesDir}, creator, defaultFee, nil) require.NoError(t, err) return txs