From aa156266235cf01391ac1d4d20b47e3bbcd7e3dd Mon Sep 17 00:00:00 2001 From: Miguel Victoria Date: Thu, 5 Dec 2024 13:03:56 -0500 Subject: [PATCH] feat: add genesis packages with transaction signing --- .../internal/txs/txs_add_packages.go | 76 ++++++-- .../internal/txs/txs_add_packages_test.go | 175 ++++++++++++++++-- gno.land/pkg/gnoland/app.go | 2 +- tm2/pkg/sdk/auth/ante.go | 4 +- tm2/pkg/sdk/auth/ante_test.go | 6 +- 5 files changed, 226 insertions(+), 37 deletions(-) diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index cf863c72116..a8437bb7847 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -6,7 +6,9 @@ import ( "flag" "fmt" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" @@ -27,16 +29,32 @@ var ( ) type addPkgCfg struct { - txsCfg *txsCfg - deployerAddress string + txsCfg *txsCfg + keyName string + gnoHome string + insecurePasswordStdin bool } func (c *addPkgCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( - &c.deployerAddress, - "deployer-address", - defaultCreator.String(), - "the address that will be used to deploy the package", + &c.keyName, + "key-name", + "", + "The package deployer key name or address", + ) + + fs.StringVar( + &c.gnoHome, + "gno-home", + gnoenv.HomeDir(), + "the gno home directory", + ) + + fs.BoolVar( + &c.insecurePasswordStdin, + "insecure-password-stdin", + false, + "the gno home directory", ) } @@ -78,14 +96,21 @@ func execTxsAddPackages( var ( creator = defaultCreator - err error + pass string ) - - // Check if the deployer address is set - if cfg.deployerAddress != defaultCreator.String() { - creator, err = crypto.AddressFromString(cfg.deployerAddress) + kb, err := keys.NewKeyBaseFromDir(cfg.gnoHome) + if err != nil { + return err + } + if cfg.keyName != "" { + info, err := kb.GetByNameOrAddress(cfg.keyName) + if err != nil { + return err + } + creator = info.GetAddress() + pass, err = io.GetPassword("Enter password.", cfg.insecurePasswordStdin) if err != nil { - return fmt.Errorf("%w, %w", errInvalidDeployerAddr, err) + return fmt.Errorf("cannot read password: %w", err) } } @@ -96,7 +121,11 @@ func execTxsAddPackages( if err != nil { return fmt.Errorf("unable to load txs from directory, %w", err) } - + if creator != defaultCreator { + if err := signTxs(txs, cfg.keyName, genesis.ChainID, kb, pass); err != nil { + return fmt.Errorf("unable to sign txs, %w", err) + } + } parsedTxs = append(parsedTxs, txs...) } @@ -117,3 +146,24 @@ func execTxsAddPackages( return nil } + +func signTxs(txs []gnoland.TxWithMetadata, keyName string, chainID string, kb keys.Keybase, pass string) error { + for index, tx := range txs { + signBytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to load txs from directory, %w", err) + } + signature, publicKey, err := kb.Sign(keyName, pass, signBytes) + txs[index].Tx.Signatures = []std.Signature{ + { + PubKey: publicKey, + Signature: signature, + }, + } + if err != nil { + return fmt.Errorf("unable sign tx %w", err) + } + } + + return nil +} diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages_test.go b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go index c3405d6ff8d..f67529c449f 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages_test.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/gnolang/contribs/gnogenesis/internal/common" @@ -12,6 +13,8 @@ import ( 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/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -60,8 +63,10 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { assert.ErrorContains(t, cmdErr, errInvalidPackageDir.Error()) }) - t.Run("invalid deployer address", func(t *testing.T) { + t.Run("nonExistentKeyName", func(t *testing.T) { t.Parallel() + keybaseDir := t.TempDir() + keyname := "beep-boop" tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) @@ -77,16 +82,19 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { "--genesis-path", tempGenesis.Name(), t.TempDir(), // package dir - "--deployer-address", - "beep-boop", // invalid address + "--key-name", + keyname, // non-existent key name + "--gno-home", + keybaseDir, // temporaryDir for keybase } // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorIs(t, cmdErr, errInvalidDeployerAddr) + fmt.Println(cmdErr.Error()) + assert.ErrorContains(t, cmdErr, "Key "+keyname+" not found") }) - t.Run("valid package", func(t *testing.T) { + t.Run("existentKeyBadPassword", func(t *testing.T) { t.Parallel() tempGenesis, cleanup := testutils.NewTestFile(t) @@ -94,32 +102,139 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - // Prepare the package var ( packagePath = "gno.land/p/demo/cuttlas" dir = t.TempDir() + keybaseDir = t.TempDir() + keyname = "beep-boop" + password = "somepass" ) + createValidFile(t, dir, packagePath) + // Create key + kb, err := keys.NewKeyBaseFromDir(keybaseDir) + require.NoError(t, err) + mnemonic, err := client.GenerateMnemonic(256) + require.NoError(t, err) + _, err = kb.CreateAccount(keyname, mnemonic, "", password+"wrong", 0, 0) + require.NoError(t, err) - createFile := func(path, data string) { - file, err := os.Create(path) - require.NoError(t, err) + io := commands.NewTestIO() + io.SetIn( + strings.NewReader( + fmt.Sprintf( + "%s\n", + password, + ), + ), + ) - _, err = file.WriteString(data) - require.NoError(t, err) + // Create the command + cmd := NewTxsCmd(io) + args := []string{ + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + "--key-name", + keyname, // non-existent key name + "--gno-home", + keybaseDir, // temporaryDir for keybase + "--insecure-password-stdin", + dir, } - // Create the gno.mod file - createFile( - filepath.Join(dir, "gno.mod"), - fmt.Sprintf("module %s\n", packagePath), + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + fmt.Println(cmdErr.Error()) + assert.ErrorContains(t, cmdErr, "unable to sign txs, unable sign tx invalid account password") + }) + + t.Run("existentKeyOKPassword", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := common.GetDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + // Prepare the package + var ( + packagePath = "gno.land/p/demo/cuttlas" + dir = t.TempDir() + keybaseDir = t.TempDir() + keyname = "beep-boop" + password = "somepass" + ) + createValidFile(t, dir, packagePath) + // Create key + kb, err := keys.NewKeyBaseFromDir(keybaseDir) + require.NoError(t, err) + mnemonic, err := client.GenerateMnemonic(256) + require.NoError(t, err) + _, err = kb.CreateAccount(keyname, mnemonic, "", password, 0, 0) + require.NoError(t, err) + + io := commands.NewTestIO() + io.SetIn( + strings.NewReader( + fmt.Sprintf( + "%s\n", + password, + ), + ), ) - // Create a simple main.gno - createFile( - filepath.Join(dir, "main.gno"), - "package cuttlas\n\nfunc Example() string {\nreturn \"Manos arriba!\"\n}", + // Create the command + cmd := NewTxsCmd(io) + args := []string{ + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + "--key-name", + keyname, // non-existent key name + "--gno-home", + keybaseDir, // temporaryDir for keybase + "--insecure-password-stdin", + dir, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) + + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + require.Equal(t, 1, len(state.Txs)) + require.Equal(t, 1, len(state.Txs[0].Tx.Msgs)) + + msgAddPkg, ok := state.Txs[0].Tx.Msgs[0].(vmm.MsgAddPackage) + require.True(t, ok) + + assert.Equal(t, packagePath, msgAddPkg.Package.Path) + }) + + t.Run("valid package", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := common.GetDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + // Prepare the package + var ( + packagePath = "gno.land/p/demo/cuttlas" + dir = t.TempDir() ) + createValidFile(t, dir, packagePath) // Create the command cmd := NewTxsCmd(commands.NewTestIO()) @@ -152,3 +267,25 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { assert.Equal(t, packagePath, msgAddPkg.Package.Path) }) } + +func createValidFile(t *testing.T, dir string, packagePath string) { + createFile := func(path, data string) { + file, err := os.Create(path) + require.NoError(t, err) + + _, err = file.WriteString(data) + require.NoError(t, err) + } + + // Create the gno.mod file + createFile( + filepath.Join(dir, "gno.mod"), + fmt.Sprintf("module %s\n", packagePath), + ) + + // Create a simple main.gno + createFile( + filepath.Join(dir, "main.gno"), + "package cuttlas\n\nfunc Example() string {\nreturn \"Manos arriba!\"\n}", + ) +} diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index e0c93f6194f..c526bc11c41 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -103,7 +103,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Set AnteHandler authOptions := auth.AnteOptions{ - VerifyGenesisSignatures: false, // for development + VerifyGenesisSignatures: true, } authAnteHandler := auth.NewAnteHandler( acctKpr, bankKpr, auth.DefaultSigVerificationGasConsumer, authOptions) diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index d36b376aa8d..da7ae2575d7 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -394,15 +394,17 @@ func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit int64) sdk.Context { // and an account. func GetSignBytes(chainID string, tx std.Tx, acc std.Account, genesis bool) ([]byte, error) { var accNum uint64 + var accSequence uint64 if !genesis { accNum = acc.GetAccountNumber() + accSequence = acc.GetSequence() } return std.GetSignaturePayload( std.SignDoc{ ChainID: chainID, AccountNumber: accNum, - Sequence: acc.GetSequence(), + Sequence: accSequence, Fee: tx.Fee, Msgs: tx.Msgs, Memo: tx.Memo, diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index 86e34391770..597d4408efe 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -208,8 +208,8 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { tx = tu.NewTestTx(t, ctx.ChainID(), msgs, privs, []uint64{1}, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, std.UnauthorizedError{}) - // from correct account number - seqs = []uint64{1} + // At genesis account number is zero + seqs = []uint64{0} tx = tu.NewTestTx(t, ctx.ChainID(), msgs, privs, []uint64{0}, seqs, fee) checkValidTx(t, anteHandler, ctx, tx, false) @@ -222,7 +222,7 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { checkInvalidTx(t, anteHandler, ctx, tx, false, std.UnauthorizedError{}) // correct account numbers - privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []uint64{0, 0}, []uint64{2, 0} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []uint64{0, 0}, []uint64{0, 0} tx = tu.NewTestTx(t, ctx.ChainID(), msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx, false) }