Skip to content

feat: new txtar command adduser #1471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 10, 2024
Merged
5 changes: 5 additions & 0 deletions gno.land/cmd/gnoland/testdata/adduser-err.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
gnoland start

# should fail if user is added after node is started
! adduser test5
stderr '"adduser" error: adduser must be used before starting node'
30 changes: 30 additions & 0 deletions gno.land/cmd/gnoland/testdata/adduser.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
adduser test8

## start a new node
gnoland start

## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar
gnokey maketx addpkg -pkgdir $WORK/bar -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test8

## execute Render
gnokey maketx run -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test8 $WORK/script/script.gno

## compare render
stdout 'main: --- hello from foo ---'
stdout 'OK!'
stdout 'GAS WANTED: 200000'
stdout 'GAS USED: '

-- bar/bar.gno --
package bar

func Render(path string) string {
return "hello from foo"
}

-- script/script.gno --
package main
import "gno.land/r/foobar/bar"
func main() {
println("main: ---", bar.Render(""), "---")
}
4 changes: 4 additions & 0 deletions gno.land/pkg/integration/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
// - `--remote`, `--insecure-password-stdin`, and `--home` flags are set automatically to
// communicate with the gnoland node.
//
// 3. `adduser`:
// - Creates a new user in the default keybase directory
// - Must be run before `gnoland start`.
//
// Logging:
//
// Gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much
Expand Down
104 changes: 100 additions & 4 deletions gno.land/pkg/integration/testing_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"context"
"errors"
"fmt"
"hash/crc32"
"os"
Expand All @@ -10,15 +11,20 @@ import (
"strings"
"testing"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
"github.com/gnolang/gno/tm2/pkg/bft/node"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto/bip39"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/crypto/keys/client"
"github.com/gnolang/gno/tm2/pkg/log"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/rogpeppe/go-internal/testscript"
)

const numTestAccounts int = 4

type tSeqShim struct{ *testing.T }

// noop Parallel method allow us to run test sequentially
Expand Down Expand Up @@ -71,6 +77,10 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
// Testscripts run concurrently by default, so we need to be prepared for that.
nodes := map[string]*testNode{}

// Track new user balances added via the `adduser` command. These are added to the genesis
// state when the node is started.
var newUserBalances []gnoland.Balance

updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS"))
persistWorkDir, _ := strconv.ParseBool(os.Getenv("TESTWORK"))
return testscript.Params{
Expand Down Expand Up @@ -107,12 +117,25 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
env.Values["_logger"] = logger
}

// Setup "test1" default account
// test1 must be created outside of the loop below because it is already included in genesis so
// attempting to recreate results in it getting overwritten and breaking existing tests that
// rely on its address being static.
kb.CreateAccount(DefaultAccount_Name, DefaultAccount_Seed, "", "", 0, 0)

env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed)
env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address)

// Create test accounts starting from test2.
for i := 1; i < numTestAccounts; i++ {
accountName := "test" + strconv.Itoa(i+1)

balance, err := createAccount(env, kb, accountName)
if err != nil {
return fmt.Errorf("error creating account %s: %w", accountName, err)
}

newUserBalances = append(newUserBalances, balance)
}

env.Setenv("GNOROOT", gnoRootDir)
env.Setenv("GNOHOME", gnoHomeDir)

Expand All @@ -126,15 +149,15 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
}

logger := ts.Value("_logger").(log.Logger) // grab logger
sid := ts.Getenv("SID") // grab session id
sid := getNodeSID(ts) // grab session id

var cmd string
cmd, args = args[0], args[1:]

var err error
switch cmd {
case "start":
if _, ok := nodes[sid]; ok {
if nodeIsRunning(nodes, sid) {
err = fmt.Errorf("node already started")
break
}
Expand All @@ -144,6 +167,16 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {

// Generate config and node
cfg, _ := TestingNodeConfig(t, gnoRootDir)

// Add balances for users added via the `adduser` command.
genesis := cfg.Genesis
genesisState := gnoland.GnoGenesisState{
Balances: genesis.AppState.(gnoland.GnoGenesisState).Balances,
Txs: genesis.AppState.(gnoland.GnoGenesisState).Txs,
}
genesisState.Balances = append(genesisState.Balances, newUserBalances...)
cfg.Genesis.AppState = genesisState

n, remoteAddr := TestingInMemoryNode(t, logger, cfg)

// Register cleanup
Expand Down Expand Up @@ -211,10 +244,42 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {

tsValidateError(ts, "gnokey", neg, err)
},
// adduser commands must be executed before starting the node; it errors out otherwise.
"adduser": func(ts *testscript.TestScript, neg bool, args []string) {
if nodeIsRunning(nodes, getNodeSID(ts)) {
tsValidateError(ts, "adduser", neg, errors.New("adduser must be used before starting node"))
return
}

if len(args) == 0 {
ts.Fatalf("new user name required")
}

kb, err := keys.NewKeyBaseFromDir(gnoHomeDir)
if err != nil {
ts.Fatalf("unable to get keybase")
}

balance, err := createAccount(ts, kb, args[0])
if err != nil {
ts.Fatalf("error creating account %s: %s", args[0], err)
}

newUserBalances = append(newUserBalances, balance)
},
},
}
}

func getNodeSID(ts *testscript.TestScript) string {
return ts.Getenv("SID")
}

func nodeIsRunning(nodes map[string]*testNode, sid string) bool {
_, ok := nodes[sid]
return ok
}

func getTestingLogger(env *testscript.Env, logname string) (log.Logger, error) {
var path string

Expand Down Expand Up @@ -273,3 +338,34 @@ func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error)
}
}
}

type envSetter interface {
Setenv(key, value string)
}

// createAccount creates a new account with the given name and adds it to the keybase.
func createAccount(env envSetter, kb keys.Keybase, accountName string) (balance gnoland.Balance, err error) {
entropy, err := bip39.NewEntropy(256)
if err != nil {
return balance, fmt.Errorf("error creating entropy: %w", err)
}

mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
return balance, fmt.Errorf("error generating mnemonic: %w", err)
}

var keyInfo keys.Info
if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil {
return balance, fmt.Errorf("unable to create account: %w", err)
}

address := keyInfo.GetAddress()
env.Setenv("USER_SEED_"+accountName, mnemonic)
env.Setenv("USER_ADDR_"+accountName, address.String())

return gnoland.Balance{
Address: address,
Amount: std.Coins{std.NewCoin("ugnot", 1000000000000000000)},
}, nil
}