From 8ace17c72eb8b4aae570a4969e765c457d5987e3 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Sat, 13 Apr 2024 21:29:03 +0200
Subject: [PATCH 01/23] feat(gnodev): add balances handler
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/main.go | 279 ++++++++++++++++++----
contribs/gnodev/pkg/dev/keybase.go | 102 ++++++++
contribs/gnodev/pkg/dev/node.go | 85 ++++---
contribs/gnodev/pkg/dev/packages.go | 169 +++++++++++++
contribs/gnodev/pkg/dev/packages_test.go | 71 ++++++
contribs/gnodev/pkg/dev/pkgs.go | 82 -------
gno.land/cmd/genesis/balances_add.go | 76 +-----
gno.land/cmd/genesis/balances_add_test.go | 131 ----------
gno.land/cmd/genesis/types.go | 24 --
gno.land/pkg/balances/balances.go | 100 ++++++++
gno.land/pkg/balances/balances_test.go | 190 +++++++++++++++
11 files changed, 937 insertions(+), 372 deletions(-)
create mode 100644 contribs/gnodev/pkg/dev/keybase.go
create mode 100644 contribs/gnodev/pkg/dev/packages.go
create mode 100644 contribs/gnodev/pkg/dev/packages_test.go
delete mode 100644 contribs/gnodev/pkg/dev/pkgs.go
create mode 100644 gno.land/pkg/balances/balances.go
create mode 100644 gno.land/pkg/balances/balances_test.go
diff --git a/contribs/gnodev/main.go b/contribs/gnodev/main.go
index 6e6d12fcbdc..9bd52d89ec7 100644
--- a/contribs/gnodev/main.go
+++ b/contribs/gnodev/main.go
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"log/slog"
+ "math/rand"
"net"
"net/http"
"os"
@@ -21,12 +22,19 @@ import (
"github.com/gnolang/gno/contribs/gnodev/pkg/logger"
"github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
"github.com/gnolang/gno/contribs/gnodev/pkg/watcher"
+ "github.com/gnolang/gno/gno.land/pkg/balances"
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/gno.land/pkg/gnoweb"
- zaplog "github.com/gnolang/gno/gno.land/pkg/log"
+ "github.com/gnolang/gno/gno.land/pkg/integration"
+ gnolog "github.com/gnolang/gno/gno.land/pkg/log"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/tm2/pkg/commands"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
osm "github.com/gnolang/gno/tm2/pkg/os"
+ "github.com/gnolang/gno/tm2/pkg/std"
"github.com/muesli/termenv"
)
@@ -37,20 +45,36 @@ const (
EventServerLogName = "Event"
)
+var (
+ DefaultCreatorName = integration.DefaultAccount_Name
+ DefaultCreatorAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address)
+ DefaultCreatorSeed = integration.DefaultAccount_Seed
+ DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
+)
+
type devCfg struct {
+ // Listeners
webListenerAddr string
nodeRPCListenerAddr string
nodeP2PListenerAddr string
nodeProxyAppListenerAddr string
- minimal bool
- verbose bool
- hotreload bool
- noWatch bool
- noReplay bool
- maxGas int64
- chainId string
- serverMode bool
+ // Users default
+ genesisCreator string
+ home string
+ root string
+
+ // Node Configuration
+ minimal bool
+ verbose bool
+ hotreload bool
+ noWatch bool
+ noReplay bool
+ maxGas int64
+ chainId string
+ serverMode bool
+ balancesFile string
+ additionalUsers string
}
var defaultDevOptions = &devCfg{
@@ -58,6 +82,9 @@ var defaultDevOptions = &devCfg{
maxGas: 10_000_000_000,
webListenerAddr: "127.0.0.1:8888",
nodeRPCListenerAddr: "127.0.0.1:36657",
+ genesisCreator: DefaultCreatorAddress.String(),
+ home: gnoenv.HomeDir(),
+ root: gnoenv.RootDir(),
// As we have no reason to configure this yet, set this to random port
// to avoid potential conflict with other app
@@ -87,6 +114,20 @@ additional specified paths.`,
}
func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
+ fs.StringVar(
+ &c.home,
+ "home",
+ defaultDevOptions.home,
+ "user's local directory for keys",
+ )
+
+ fs.StringVar(
+ &c.root,
+ "root",
+ defaultDevOptions.root,
+ "gno root directory",
+ )
+
fs.StringVar(
&c.webListenerAddr,
"web-listener",
@@ -98,14 +139,28 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
&c.nodeRPCListenerAddr,
"node-rpc-listener",
defaultDevOptions.nodeRPCListenerAddr,
- "gnoland rpc node listening address",
+ "listening address for GnoLand RPC node",
+ )
+
+ fs.StringVar(
+ &c.nodeRPCListenerAddr,
+ "add-users",
+ defaultDevOptions.nodeRPCListenerAddr,
+ "pre-add users, separated by commas",
+ )
+
+ fs.StringVar(
+ &c.genesisCreator,
+ "creator",
+ defaultDevOptions.genesisCreator,
+ "name of the genesis creator (from Keybase) or address",
)
fs.BoolVar(
&c.minimal,
"minimal",
defaultDevOptions.minimal,
- "do not load packages from examples directory",
+ "do not load packages from the examples directory",
)
fs.BoolVar(
@@ -119,7 +174,7 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
&c.verbose,
"verbose",
defaultDevOptions.verbose,
- "verbose output when deving",
+ "enable verbose output for development",
)
fs.StringVar(
@@ -133,21 +188,21 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
&c.noWatch,
"no-watch",
defaultDevOptions.noWatch,
- "do not watch for files change",
+ "do not watch for file changes",
)
fs.BoolVar(
&c.noReplay,
"no-replay",
defaultDevOptions.noReplay,
- "do not replay previous transactions on reload",
+ "do not replay previous transactions upon reload",
)
fs.Int64Var(
&c.maxGas,
"max-gas",
defaultDevOptions.maxGas,
- "set the maximum gas by block",
+ "set the maximum gas per block",
)
}
@@ -155,20 +210,6 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
ctx, cancel := context.WithCancelCause(context.Background())
defer cancel(nil)
- // Guess root dir
- gnoroot := gnoenv.RootDir()
-
- // Check and Parse packages
- pkgpaths, err := parseArgsPackages(args)
- if err != nil {
- return fmt.Errorf("unable to parse package paths: %w", err)
- }
-
- if !cfg.minimal {
- examplesDir := filepath.Join(gnoroot, "examples")
- pkgpaths = append(pkgpaths, examplesDir)
- }
-
// Setup Raw Terminal
rt, restore, err := setupRawTerm(cfg, io)
if err != nil {
@@ -186,9 +227,21 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
loggerEvents := logger.WithGroup(EventServerLogName)
emitterServer := emitter.NewServer(loggerEvents)
+ // load keybase
+ kb, err := setupKeybase(cfg)
+ if err != nil {
+ return fmt.Errorf("unable to load keybase: %w", err)
+ }
+
+ // Check and Parse packages
+ pkgpaths, err := resolvePackagesPathFromArgs(cfg, kb, args)
+ if err != nil {
+ return fmt.Errorf("unable to parse package paths: %w", err)
+ }
+
// Setup Dev Node
// XXX: find a good way to export or display node logs
- devNode, err := setupDevNode(ctx, logger, cfg, emitterServer, pkgpaths)
+ devNode, err := setupDevNode(ctx, logger, cfg, emitterServer, kb, pkgpaths)
if err != nil {
return err
}
@@ -393,14 +446,19 @@ func setupDevNode(
logger *slog.Logger,
cfg *devCfg,
remitter emitter.Emitter,
- pkgspath []string,
+ kb keys.Keybase,
+ pkgspath []dev.PackagePath,
) (*gnodev.Node, error) {
nodeLogger := logger.WithGroup(NodeLogName)
- gnoroot := gnoenv.RootDir()
+ balances, err := generateBalances(kb, cfg)
+ if err != nil {
+ return nil, fmt.Errorf("unable to generate balances: %w", err)
+ }
// configure gnoland node
- config := gnodev.DefaultNodeConfig(gnoroot)
+ config := gnodev.DefaultNodeConfig(cfg.root)
+ config.BalancesList = balances.List()
config.PackagesPathList = pkgspath
config.TMConfig.RPC.ListenAddress = resolveUnixOrTCPAddr(cfg.nodeRPCListenerAddr)
config.NoReplay = cfg.noReplay
@@ -425,26 +483,79 @@ func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) htt
return app.Router
}
-func parseArgsPackages(args []string) (paths []string, err error) {
- paths = make([]string, len(args))
+func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]dev.PackagePath, error) {
+ paths := make([]dev.PackagePath, len(args))
+
+ if cfg.genesisCreator == "" {
+ return nil, fmt.Errorf("default genesis creator cannot be empty")
+ }
+
+ defaultKey, err := kb.GetByNameOrAddress(cfg.genesisCreator)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get genesis creator %q: %w", cfg.genesisCreator, err)
+ }
+
for i, arg := range args {
- abspath, err := filepath.Abs(arg)
+ path, err := dev.ResolvePackagePathQuery(kb, arg)
if err != nil {
- return nil, fmt.Errorf("invalid path %q: %w", arg, err)
+ return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err)
}
- ppath, err := gnomod.FindRootDir(abspath)
- if err != nil {
- return nil, fmt.Errorf("unable to find root dir of %q: %w", abspath, err)
+ if path.Creator.IsZero() {
+ path.Creator = defaultKey.GetAddress()
}
- paths[i] = ppath
+ paths[i] = path
+ }
+
+ // Add examples folder if minimal specified
+ if !cfg.minimal {
+ fmt.Println("adding example folder: ", filepath.Join(cfg.root, "examples"))
+ paths = append(paths, gnodev.PackagePath{
+ Path: filepath.Join(cfg.root, "examples"),
+ Creator: defaultKey.GetAddress(),
+ Deposit: nil,
+ })
}
return paths, nil
}
-func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
+func generateBalances(kb keys.Keybase, cfg *devCfg) (balances.Balances, error) {
+ bls := balances.New()
+ amount := std.Coins{std.NewCoin("ugnot", 10e6)}
+
+ keys, err := kb.List()
+ if err != nil {
+ return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
+ }
+
+ // Automatically set every key from keybase to unlimited found
+ for _, key := range keys {
+ address := key.GetAddress()
+ bls[address] = gnoland.Balance{Amount: amount, Address: address}
+ }
+
+ if cfg.balancesFile == "" {
+ return bls, nil
+ }
+
+ file, err := os.Open(cfg.balancesFile)
+ if err != nil {
+ return nil, fmt.Errorf("unable to open balance file %q: %w", cfg.balancesFile, err)
+ }
+
+ blsFile, err := balances.GetBalancesFromSheet(file)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
+ }
+
+ // Left merge keybase balance into file balance
+ blsFile.LeftMerge(bls)
+ return blsFile, nil
+}
+
+func listenForKeyPress(w io.Writer, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
cc := make(chan rawterm.KeyPress, 1)
go func() {
defer close(cc)
@@ -460,6 +571,30 @@ func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.
return cc
}
+// createAccount creates a new account with the given name and adds it to the keybase.
+func createAccount(kb keys.Keybase, accountName string) (keys.Info, error) {
+ entropy, err := bip39.NewEntropy(256)
+ if err != nil {
+ return nil, fmt.Errorf("error creating entropy: %w", err)
+ }
+
+ mnemonic, err := bip39.NewMnemonic(entropy)
+ if err != nil {
+ return nil, fmt.Errorf("error generating mnemonic: %w", err)
+ }
+
+ return kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
+}
+
+func checkForError(w io.Writer, err error) {
+ if err != nil {
+ fmt.Fprintf(w, "[ERROR] - %s\n", err.Error())
+ return
+ }
+
+ fmt.Fprintln(w, "[DONE]")
+}
+
func resolveUnixOrTCPAddr(in string) (out string) {
var err error
var addr net.Addr
@@ -483,6 +618,64 @@ func resolveUnixOrTCPAddr(in string) (out string) {
panic(err)
}
+func setupKeybase(cfg *devCfg) (keys.Keybase, error) {
+ kb := keys.NewInMemory()
+ if cfg.home != "" {
+ // load home keybase into our inMemory keybase
+ kbHome, err := keys.NewKeyBaseFromDir(cfg.home)
+ if err != nil {
+ return nil, fmt.Errorf("unable to load keybae from dir %q: %w", cfg.home, err)
+ }
+
+ keys, err := kbHome.List()
+ if err != nil {
+ return nil, fmt.Errorf("unable to list keys from keybase %q: %w", cfg.home, err)
+ }
+
+ for _, key := range keys {
+ name := key.GetName()
+ armor, err := kbHome.Export(key.GetName())
+ if err != nil {
+ return nil, fmt.Errorf("unable to export key %q: %w", name, err)
+ }
+
+ if err := kb.Import(name, armor); err != nil {
+ return nil, fmt.Errorf("unable to import key %q: %w", name, err)
+ }
+ }
+ }
+
+ // Add additional users to our keybase
+ addUsers := strings.Split(cfg.additionalUsers, ",")
+ for _, user := range addUsers {
+ if _, err := createAccount(kb, user); err != nil {
+ return nil, fmt.Errorf("unable to create user %q: %w", user, err)
+ }
+ }
+
+ // Add default creator it doesn't exist
+ ok, _ := kb.HasByAddress(DefaultCreatorAddress)
+ if ok {
+ return kb, nil
+ }
+
+ for i := 0; i < 5; i++ {
+ creatorName := fmt.Sprintf("_testUser%05d\n", rand.Intn(100000))
+ ok, _ := kb.HasByName(creatorName)
+ if ok {
+ continue
+ }
+
+ if _, err := kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0); err != nil {
+ return nil, fmt.Errorf("unable to create default %q account: %w", DefaultCreatorName, err)
+ }
+
+ return kb, nil
+ }
+
+ panic("unable to generate ranmdom test user name")
+}
+
func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger {
level := slog.LevelInfo
if cfg.verbose {
@@ -491,7 +684,7 @@ func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger {
if cfg.serverMode {
zaplogger := logger.NewZapLogger(out, level)
- return zaplog.ZapLoggerToSlog(zaplogger)
+ return gnolog.ZapLoggerToSlog(zaplogger)
}
// Detect term color profile
diff --git a/contribs/gnodev/pkg/dev/keybase.go b/contribs/gnodev/pkg/dev/keybase.go
new file mode 100644
index 00000000000..7f69a36c31b
--- /dev/null
+++ b/contribs/gnodev/pkg/dev/keybase.go
@@ -0,0 +1,102 @@
+package dev
+
+import (
+ "fmt"
+
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+// type Keybase struct {
+// keys.Keybase
+// }
+
+// func NewKeybase() *Keybase {
+// return &Keybase{
+// Keybase: keys.NewInMemory(),
+// }
+// }
+
+// func (to *Keybase) ImportKeybaseFromPath(path string) error {
+// from, err := keys.NewKeyBaseFromDir(path)
+// if err != nil {
+// return fmt.Errorf("unable to load keybase: %w", err)
+// }
+
+// keys, err := from.List()
+// if err != nil {
+// return fmt.Errorf("unable to list keys: %w", path, err)
+// }
+
+// for _, key := range keys {
+// armor, err := from.Export(key.GetName())
+// if err != nil {
+// return fmt.Errorf("unable to import key %q: %w", key.GetName(), err)
+// }
+
+// err = to.Import(key.GetName(), armor)
+// if err != nil {
+// return fmt.Errorf("unable to import key %q: %w", key.GetName(), err)
+// }
+// }
+
+// return nil
+// }
+
+// type PackagePath struct {
+// Path string
+// CreatorNameOrAddress string
+// }
+
+// func ParsePackagePath(path string) (PackagePath, error) {
+// var ppath PackagePath
+
+// upath, err := url.Parse(path)
+// if err != nil {
+// return ppath, fmt.Errorf("unable to parse package path: %w", err)
+// }
+
+// // Get path
+// ppath.Path = filepath.Clean(upath.Path)
+// // Check for options
+// ppath.CreatorNameOrAddress = upath.Query().Get("creator")
+// return ppath, nil
+// }
+
+// func LoadKeyabaseBalanceFromPath(kb keys.Keybase) ([]gnoland.Balance, error) {
+// keys, err := kb.List()
+// if err != nil {
+// return nil, nil
+// }
+
+// for _, info := range keys {
+// info.GetName()
+// }
+// }
+
+// loadAccount with the given name and adds it to the keybase.
+func loadAccount(kb keys.Keybase, accountName string) (gnoland.Balance, error) {
+ var balance gnoland.Balance
+ 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()
+ return gnoland.Balance{
+ Address: address,
+ Amount: std.Coins{std.NewCoin("ugnot", 10e6)},
+ }, nil
+}
diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go
index 02d97dff733..baa8c3626df 100644
--- a/contribs/gnodev/pkg/dev/node.go
+++ b/contribs/gnodev/pkg/dev/node.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
+ "path/filepath"
"strings"
"unicode"
@@ -27,22 +28,36 @@ import (
)
type NodeConfig struct {
- PackagesPathList []string
- TMConfig *tmcfg.Config
- NoReplay bool
- MaxGasPerBlock int64
- ChainID string
+ DefaultCreator crypto.Address
+ BalancesList []gnoland.Balance
+ PackagesPathList []PackagePath
+ TMConfig *tmcfg.Config
+ SkipFailingGenesisTxs bool
+ NoReplay bool
+ MaxGasPerBlock int64
+ ChainID string
}
func DefaultNodeConfig(rootdir string) *NodeConfig {
tmc := gnoland.NewDefaultTMConfig(rootdir)
tmc.Consensus.SkipTimeoutCommit = false // avoid time drifting, see issue #1507
+ defaultCreator := crypto.MustAddressFromString(integration.DefaultAccount_Address)
+ balances := []gnoland.Balance{
+ {
+ Address: defaultCreator,
+ Amount: std.Coins{std.NewCoin("ugnot", 10e6)},
+ },
+ }
+
return &NodeConfig{
- ChainID: tmc.ChainID(),
- PackagesPathList: []string{},
- TMConfig: tmc,
- MaxGasPerBlock: 10_000_000_000,
+ DefaultCreator: defaultCreator,
+ BalancesList: balances,
+ ChainID: tmc.ChainID(),
+ PackagesPathList: []PackagePath{},
+ TMConfig: tmc,
+ SkipFailingGenesisTxs: true,
+ MaxGasPerBlock: 10_000_000_000,
}
}
@@ -54,30 +69,21 @@ type Node struct {
emitter emitter.Emitter
client client.Client
logger *slog.Logger
- pkgs PkgsMap // path -> pkg
+ pkgs PackagesMap // path -> pkg
// keep track of number of loaded package to be able to skip them on restore
loadedPackages int
}
-var (
- DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
- DefaultCreator = crypto.MustAddressFromString(integration.DefaultAccount_Address)
- DefaultBalance = []gnoland.Balance{
- {
- Address: DefaultCreator,
- Amount: std.MustParseCoins("10000000000000ugnot"),
- },
- }
-)
+var DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitter, cfg *NodeConfig) (*Node, error) {
- mpkgs, err := newPkgsMap(cfg.PackagesPathList)
+ mpkgs, err := NewPackagesMap(cfg.PackagesPathList)
if err != nil {
return nil, fmt.Errorf("unable map pkgs list: %w", err)
}
- pkgsTxs, err := mpkgs.Load(DefaultCreator, DefaultFee, nil)
+ pkgsTxs, err := mpkgs.Load(cfg.DefaultCreator, DefaultFee)
if err != nil {
return nil, fmt.Errorf("unable to load genesis packages: %w", err)
}
@@ -86,7 +92,7 @@ func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitte
// generate genesis state
genesis := gnoland.GnoGenesisState{
- Balances: DefaultBalance,
+ Balances: cfg.BalancesList,
Txs: pkgsTxs,
}
@@ -131,15 +137,36 @@ func (d *Node) GetRemoteAddress() string {
func (d *Node) UpdatePackages(paths ...string) error {
var n int
for _, path := range paths {
+ abspath, err := filepath.Abs(path)
+ if err != nil {
+ return fmt.Errorf("unable to resolve abs path of %q: %w", path, err)
+ }
+
+ creator := d.config.DefaultCreator
+ var deposit std.Coins
+ for _, ppath := range d.config.PackagesPathList {
+ if !strings.HasPrefix(abspath, ppath.Path) {
+ continue
+ }
+
+ creator = ppath.Creator
+ deposit = ppath.Deposit
+ }
+
// List all packages from target path
- pkgslist, err := gnomod.ListPkgs(path)
+ pkgslist, err := gnomod.ListPkgs(abspath)
if err != nil {
return fmt.Errorf("failed to list gno packages for %q: %w", path, err)
}
// Update or add package in the current known list.
for _, pkg := range pkgslist {
- d.pkgs[pkg.Dir] = pkg
+ d.pkgs[pkg.Dir] = Package{
+ Pkg: pkg,
+ Creator: creator,
+ Deposit: deposit,
+ }
+
d.logger.Debug("pkgs update", "name", pkg.Name, "path", pkg.Dir)
}
@@ -159,13 +186,13 @@ func (d *Node) Reset(ctx context.Context) error {
}
// Generate a new genesis state based on the current packages
- txs, err := d.pkgs.Load(DefaultCreator, DefaultFee, nil)
+ txs, err := d.pkgs.Load(d.config.DefaultCreator, DefaultFee)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
genesis := gnoland.GnoGenesisState{
- Balances: DefaultBalance,
+ Balances: d.config.BalancesList,
Txs: txs,
}
@@ -217,14 +244,14 @@ func (d *Node) Reload(ctx context.Context) error {
}
// Load genesis packages
- pkgsTxs, err := d.pkgs.Load(DefaultCreator, DefaultFee, nil)
+ pkgsTxs, err := d.pkgs.Load(d.config.DefaultCreator, DefaultFee)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
// Create genesis with loaded pkgs + previous state
genesis := gnoland.GnoGenesisState{
- Balances: DefaultBalance,
+ Balances: d.config.BalancesList,
Txs: append(pkgsTxs, state...),
}
diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go
new file mode 100644
index 00000000000..65acb8ec585
--- /dev/null
+++ b/contribs/gnodev/pkg/dev/packages.go
@@ -0,0 +1,169 @@
+package dev
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "path/filepath"
+
+ vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
+ gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
+ "github.com/gnolang/gno/gnovm/pkg/gnomod"
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+type PackagePath struct {
+ Path string
+ Creator crypto.Address
+ Deposit std.Coins
+}
+
+func ResolvePackagePathQuery(kb keys.Keybase, path string) (PackagePath, error) {
+ var ppath PackagePath
+
+ upath, err := url.Parse(filepath.Clean(path))
+ if err != nil {
+ return ppath, fmt.Errorf("malformed path/query: %w", err)
+ }
+ ppath.Path = upath.Path
+
+ // Check for creator option
+ creator := upath.Query().Get("creator")
+ if creator != "" {
+ address, err := crypto.AddressFromBech32(creator)
+ if err != nil {
+ info, nameErr := kb.GetByName(creator)
+ if nameErr != nil {
+ return ppath, fmt.Errorf("invalid name or address for creator %q", creator)
+ }
+
+ address = info.GetAddress()
+ }
+
+ ppath.Creator = address
+ }
+
+ // Check for deposit option
+ deposit := upath.Query().Get("deposit")
+ if deposit != "" {
+ coins, err := std.ParseCoins(deposit)
+ if err != nil {
+ return ppath, fmt.Errorf(
+ "unable to parse deposit amount %q (should be in the form xxxugnot): %w", deposit, err,
+ )
+ }
+
+ ppath.Deposit = coins
+ }
+
+ return ppath, nil
+}
+
+type Package struct {
+ gnomod.Pkg
+ Creator crypto.Address
+ Deposit std.Coins
+}
+
+type PackagesMap map[string]Package
+
+var (
+ ErrEmptyCreatorPackage = errors.New("no creator specified for package")
+ ErrEmptyDepositPackage = errors.New("no deposit specified for package")
+)
+
+func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) {
+ pkgs := make(map[string]Package)
+ for _, ppath := range ppaths {
+ fmt.Println(ppath)
+ if ppath.Creator.IsZero() {
+ return nil, fmt.Errorf("unable to load package %q: %w", ppath.Path, ErrEmptyCreatorPackage)
+ }
+
+ // if ppath.Deposit.Empty() {
+ // return nil, fmt.Errorf("unable to load package %q: %w", ppath.Path, ErrEmptyDepositPackage)
+ // }
+
+ abspath, err := filepath.Abs(ppath.Path)
+ if err != nil {
+ return nil, fmt.Errorf("unable to guess absolute path for %q: %w", ppath.Path, err)
+ }
+
+ // rootdir, err := gnomod.FindRootDir(abspath)
+ // if err != nil {
+ // return nil, fmt.Errorf("unable to find rootdir for package %q: %w", ppath.Path, err)
+ // }
+
+ // list all packages from target path
+ pkgslist, err := gnomod.ListPkgs(abspath)
+ if err != nil {
+ return nil, fmt.Errorf("listing gno packages: %w", err)
+ }
+
+ for _, pkg := range pkgslist {
+ if pkg.Dir == "" {
+ continue
+ }
+
+ if _, ok := pkgs[pkg.Dir]; ok {
+ continue // skip
+ }
+ pkgs[pkg.Dir] = Package{
+ Pkg: pkg,
+ Creator: ppath.Creator,
+ Deposit: ppath.Deposit,
+ }
+ }
+ }
+
+ return pkgs, nil
+}
+
+func (pm PackagesMap) toList() gnomod.PkgList {
+ list := make([]gnomod.Pkg, 0, len(pm))
+ for _, pkg := range pm {
+ list = append(list, pkg.Pkg)
+ }
+ return list
+}
+
+func (pm PackagesMap) Load(creator bft.Address, fee std.Fee) ([]std.Tx, error) {
+ pkgs := pm.toList()
+
+ sorted, err := pkgs.Sort()
+ if err != nil {
+ return nil, fmt.Errorf("unable to sort pkgs: %w", err)
+ }
+
+ nonDraft := sorted.GetNonDraftPkgs()
+ txs := []std.Tx{}
+ for _, modPkg := range nonDraft {
+ pkg := pm[modPkg.Dir]
+
+ // Open files in directory as MemPackage.
+ memPkg := gno.ReadMemPackage(modPkg.Dir, modPkg.Name)
+ if err := memPkg.Validate(); err != nil {
+ return nil, fmt.Errorf("invalid package: %w", err)
+ }
+
+ // Create transaction
+ tx := std.Tx{
+ Fee: fee,
+ Msgs: []std.Msg{
+ vmm.MsgAddPackage{
+ Creator: pkg.Creator,
+ Deposit: pkg.Deposit,
+ Package: memPkg,
+ },
+ },
+ }
+
+ tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
+ txs = append(txs, tx)
+ }
+
+ return txs, nil
+}
diff --git a/contribs/gnodev/pkg/dev/packages_test.go b/contribs/gnodev/pkg/dev/packages_test.go
new file mode 100644
index 00000000000..e0941e29d98
--- /dev/null
+++ b/contribs/gnodev/pkg/dev/packages_test.go
@@ -0,0 +1,71 @@
+package dev
+
+import (
+ "testing"
+
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/std"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestResolvePackagePathQuery(t *testing.T) {
+ var (
+ testingName = "testAccount"
+ testingMnemonic = `special hip mail knife manual boy essay certain broccoli group token exchange problem subject garbage chaos program monitor happy magic upgrade kingdom cluster enemy`
+ testingAddress = crypto.MustAddressFromString("g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na")
+ )
+
+ kb := keys.NewInMemory()
+ kb.CreateAccount(testingName, testingMnemonic, "", "", 0, 0)
+
+ cases := []struct {
+ Path string
+ ExpectedPackagePath PackagePath
+ ShouldFail bool
+ }{
+ {".", PackagePath{
+ Path: ".",
+ }, false},
+ {"/simple/path", PackagePath{
+ Path: "/simple/path",
+ }, false},
+ {"/ambiguo/u//s/path///", PackagePath{
+ Path: "/ambiguo/u/s/path",
+ }, false},
+ {"/path/with/creator?creator=testAccount", PackagePath{
+ Path: "/path/with/creator",
+ Creator: testingAddress,
+ }, false},
+ {"/path/with/deposit?deposit=100ugnot", PackagePath{
+ Path: "/path/with/deposit",
+ Deposit: std.MustParseCoins("100ugnot"),
+ }, false},
+ {".?creator=g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na&deposit=100ugnot", PackagePath{
+ Path: ".",
+ Creator: testingAddress,
+ Deposit: std.MustParseCoins("100ugnot"),
+ }, false},
+
+ // errors cases
+ {"/invalid/account?creator=UnknownAccount", PackagePath{}, true},
+ {"/invalid/address?creator=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", PackagePath{}, true},
+ {"/invalid/deposit?deposit=abcd", PackagePath{}, true},
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Path, func(t *testing.T) {
+ result, err := ResolvePackagePathQuery(kb, tc.Path)
+ if tc.ShouldFail {
+ assert.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+
+ assert.Equal(t, tc.ExpectedPackagePath.Path, result.Path)
+ assert.Equal(t, tc.ExpectedPackagePath.Creator, result.Creator)
+ assert.Equal(t, tc.ExpectedPackagePath.Deposit.String(), result.Deposit.String())
+ })
+ }
+}
diff --git a/contribs/gnodev/pkg/dev/pkgs.go b/contribs/gnodev/pkg/dev/pkgs.go
deleted file mode 100644
index c02508ff33d..00000000000
--- a/contribs/gnodev/pkg/dev/pkgs.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package dev
-
-import (
- "fmt"
-
- vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
- gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
- "github.com/gnolang/gno/gnovm/pkg/gnomod"
- bft "github.com/gnolang/gno/tm2/pkg/bft/types"
- "github.com/gnolang/gno/tm2/pkg/std"
-)
-
-type PkgsMap map[string]gnomod.Pkg
-
-func newPkgsMap(paths []string) (PkgsMap, error) {
- pkgs := make(map[string]gnomod.Pkg)
- for _, path := range paths {
- // list all packages from target path
- pkgslist, err := gnomod.ListPkgs(path)
- if err != nil {
- return nil, fmt.Errorf("listing gno packages: %w", err)
- }
-
- for _, pkg := range pkgslist {
- if pkg.Dir == "" {
- continue
- }
-
- if _, ok := pkgs[pkg.Dir]; ok {
- continue // skip
- }
- pkgs[pkg.Dir] = pkg
- }
- }
-
- // Filter out draft packages.
- return pkgs, nil
-}
-
-func (pm PkgsMap) toList() gnomod.PkgList {
- list := make([]gnomod.Pkg, 0, len(pm))
- for _, pkg := range pm {
- list = append(list, pkg)
- }
- return list
-}
-
-func (pm PkgsMap) Load(creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) {
- pkgs := pm.toList()
-
- sorted, err := pkgs.Sort()
- if err != nil {
- return nil, fmt.Errorf("unable to sort pkgs: %w", err)
- }
-
- nonDraft := sorted.GetNonDraftPkgs()
- txs := []std.Tx{}
- for _, pkg := range nonDraft {
- // Open files in directory as MemPackage.
- memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name)
- if err := memPkg.Validate(); err != nil {
- return nil, fmt.Errorf("invalid package: %w", err)
- }
-
- // Create transaction
- tx := std.Tx{
- Fee: fee,
- Msgs: []std.Msg{
- vmm.MsgAddPackage{
- Creator: creator,
- Package: memPkg,
- Deposit: deposit,
- },
- },
- }
-
- tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
- txs = append(txs, tx)
- }
-
- return txs, nil
-}
diff --git a/gno.land/cmd/genesis/balances_add.go b/gno.land/cmd/genesis/balances_add.go
index d1e88efcc6b..46fde3a7867 100644
--- a/gno.land/cmd/genesis/balances_add.go
+++ b/gno.land/cmd/genesis/balances_add.go
@@ -8,8 +8,8 @@ import (
"fmt"
"io"
"os"
- "strings"
+ "github.com/gnolang/gno/gno.land/pkg/balances"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/types"
@@ -93,16 +93,16 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
return errNoBalanceSource
}
- finalBalances := make(accountBalances)
+ finalBalances := balances.New()
// Get the balance sheet from the source
if singleEntriesSet {
- balances, err := getBalancesFromEntries(cfg.singleEntries)
+ balances, err := balances.GetBalancesFromEntries(cfg.singleEntries...)
if err != nil {
return fmt.Errorf("unable to get balances from entries, %w", err)
}
- finalBalances.leftMerge(balances)
+ finalBalances.LeftMerge(balances)
}
if balanceSheetSet {
@@ -112,12 +112,12 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
return fmt.Errorf("unable to open balance sheet, %w", loadErr)
}
- balances, err := getBalancesFromSheet(file)
+ balances, err := balances.GetBalancesFromSheet(file)
if err != nil {
return fmt.Errorf("unable to get balances from balance sheet, %w", err)
}
- finalBalances.leftMerge(balances)
+ finalBalances.LeftMerge(balances)
}
if txFileSet {
@@ -132,7 +132,7 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
return fmt.Errorf("unable to get balances from tx file, %w", err)
}
- finalBalances.leftMerge(balances)
+ finalBalances.LeftMerge(balances)
}
// Initialize genesis app state if it is not initialized already
@@ -149,10 +149,10 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
// Merge the two balance sheets, with the input
// having precedence over the genesis balances
- finalBalances.leftMerge(genesisBalances)
+ finalBalances.LeftMerge(genesisBalances)
// Save the balances
- state.Balances = finalBalances.toList()
+ state.Balances = finalBalances.List()
genesis.AppState = state
// Save the updated genesis
@@ -174,56 +174,6 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
return nil
}
-// getBalancesFromEntries extracts the balance entries
-// from the array of balance
-func getBalancesFromEntries(entries []string) (accountBalances, error) {
- balances := make(accountBalances)
-
- for _, entry := range entries {
- var balance gnoland.Balance
- if err := balance.Parse(entry); err != nil {
- return nil, fmt.Errorf("unable to parse balance entry: %w", err)
- }
- balances[balance.Address] = balance
- }
-
- return balances, nil
-}
-
-// getBalancesFromSheet extracts the balance sheet from the passed in
-// balance sheet file, that has the format of
=ugnot
-func getBalancesFromSheet(sheet io.Reader) (accountBalances, error) {
- // Parse the balances
- balances := make(accountBalances)
- scanner := bufio.NewScanner(sheet)
-
- for scanner.Scan() {
- entry := scanner.Text()
-
- // Remove comments
- entry = strings.Split(entry, "#")[0]
- entry = strings.TrimSpace(entry)
-
- // Skip empty lines
- if entry == "" {
- continue
- }
-
- var balance gnoland.Balance
- if err := balance.Parse(entry); err != nil {
- return nil, fmt.Errorf("unable to extract balance data, %w", err)
- }
-
- balances[balance.Address] = balance
- }
-
- if err := scanner.Err(); err != nil {
- return nil, fmt.Errorf("error encountered while scanning, %w", err)
- }
-
- return balances, nil
-}
-
// getBalancesFromTransactions constructs a balance map based on MsgSend messages.
// This way of determining the final balance sheet is not valid, since it doesn't take into
// account different message types (ex. MsgCall) that can initialize accounts with some balance values.
@@ -234,8 +184,8 @@ func getBalancesFromTransactions(
ctx context.Context,
io commands.IO,
reader io.Reader,
-) (accountBalances, error) {
- balances := make(accountBalances)
+) (balances.Balances, error) {
+ balances := balances.New()
scanner := bufio.NewScanner(reader)
@@ -336,9 +286,9 @@ func getBalancesFromTransactions(
// mapGenesisBalancesFromState extracts the initial account balances from the
// genesis app state
-func mapGenesisBalancesFromState(state gnoland.GnoGenesisState) (accountBalances, error) {
+func mapGenesisBalancesFromState(state gnoland.GnoGenesisState) (balances.Balances, error) {
// Construct the initial genesis balance sheet
- genesisBalances := make(accountBalances)
+ genesisBalances := balances.New()
for _, balance := range state.Balances {
genesisBalances[balance.Address] = balance
diff --git a/gno.land/cmd/genesis/balances_add_test.go b/gno.land/cmd/genesis/balances_add_test.go
index 0dd3366869f..edcbbd589d9 100644
--- a/gno.land/cmd/genesis/balances_add_test.go
+++ b/gno.land/cmd/genesis/balances_add_test.go
@@ -4,8 +4,6 @@ import (
"bytes"
"context"
"fmt"
- "math"
- "strconv"
"strings"
"testing"
@@ -408,135 +406,6 @@ func TestGenesis_Balances_Add(t *testing.T) {
})
}
-func TestBalances_GetBalancesFromEntries(t *testing.T) {
- t.Parallel()
-
- t.Run("valid balances", func(t *testing.T) {
- t.Parallel()
-
- // Generate dummy keys
- dummyKeys := getDummyKeys(t, 2)
- amount := std.NewCoins(std.NewCoin("ugnot", 10))
-
- balances := make([]string, len(dummyKeys))
-
- for index, key := range dummyKeys {
- balances[index] = fmt.Sprintf(
- "%s=%dugnot",
- key.Address().String(),
- amount.AmountOf("ugnot"),
- )
- }
-
- balanceMap, err := getBalancesFromEntries(balances)
- require.NoError(t, err)
-
- // Validate the balance map
- assert.Len(t, balanceMap, len(dummyKeys))
- for _, key := range dummyKeys {
- assert.Equal(t, amount, balanceMap[key.Address()].Amount)
- }
- })
-
- t.Run("malformed balance, invalid format", func(t *testing.T) {
- t.Parallel()
-
- balances := []string{
- "malformed balance",
- }
-
- balanceMap, err := getBalancesFromEntries(balances)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "malformed entry")
- })
-
- t.Run("malformed balance, invalid address", func(t *testing.T) {
- t.Parallel()
-
- balances := []string{
- "dummyaddress=10ugnot",
- }
-
- balanceMap, err := getBalancesFromEntries(balances)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid address")
- })
-
- t.Run("malformed balance, invalid amount", func(t *testing.T) {
- t.Parallel()
-
- dummyKey := getDummyKey(t)
-
- balances := []string{
- fmt.Sprintf(
- "%s=%sugnot",
- dummyKey.Address().String(),
- strconv.FormatUint(math.MaxUint64, 10),
- ),
- }
-
- balanceMap, err := getBalancesFromEntries(balances)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid amount")
- })
-}
-
-func TestBalances_GetBalancesFromSheet(t *testing.T) {
- t.Parallel()
-
- t.Run("valid balances", func(t *testing.T) {
- t.Parallel()
-
- // Generate dummy keys
- dummyKeys := getDummyKeys(t, 2)
- amount := std.NewCoins(std.NewCoin("ugnot", 10))
-
- balances := make([]string, len(dummyKeys))
-
- for index, key := range dummyKeys {
- balances[index] = fmt.Sprintf(
- "%s=%dugnot",
- key.Address().String(),
- amount.AmountOf("ugnot"),
- )
- }
-
- reader := strings.NewReader(strings.Join(balances, "\n"))
- balanceMap, err := getBalancesFromSheet(reader)
- require.NoError(t, err)
-
- // Validate the balance map
- assert.Len(t, balanceMap, len(dummyKeys))
- for _, key := range dummyKeys {
- assert.Equal(t, amount, balanceMap[key.Address()].Amount)
- }
- })
-
- t.Run("malformed balance, invalid amount", func(t *testing.T) {
- t.Parallel()
-
- dummyKey := getDummyKey(t)
-
- balances := []string{
- fmt.Sprintf(
- "%s=%sugnot",
- dummyKey.Address().String(),
- strconv.FormatUint(math.MaxUint64, 10),
- ),
- }
-
- reader := strings.NewReader(strings.Join(balances, "\n"))
-
- balanceMap, err := getBalancesFromSheet(reader)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid amount")
- })
-}
-
func TestBalances_GetBalancesFromTransactions(t *testing.T) {
t.Parallel()
diff --git a/gno.land/cmd/genesis/types.go b/gno.land/cmd/genesis/types.go
index dba39ea8ec1..a48bfaf7b31 100644
--- a/gno.land/cmd/genesis/types.go
+++ b/gno.land/cmd/genesis/types.go
@@ -1,8 +1,6 @@
package main
import (
- "github.com/gnolang/gno/gno.land/pkg/gnoland"
- "github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/std"
)
@@ -37,25 +35,3 @@ func (i *txStore) leftMerge(b txStore) error {
return nil
}
-
-type accountBalances map[types.Address]gnoland.Balance // address -> balance (ugnot)
-
-// toList linearizes the account balances map
-func (a accountBalances) toList() []gnoland.Balance {
- balances := make([]gnoland.Balance, 0, len(a))
-
- for _, balance := range a {
- balances = append(balances, balance)
- }
-
- return balances
-}
-
-// leftMerge left-merges the two maps
-func (a accountBalances) leftMerge(b accountBalances) {
- for key, bVal := range b {
- if _, present := (a)[key]; !present {
- (a)[key] = bVal
- }
- }
-}
diff --git a/gno.land/pkg/balances/balances.go b/gno.land/pkg/balances/balances.go
new file mode 100644
index 00000000000..0d6f313b00b
--- /dev/null
+++ b/gno.land/pkg/balances/balances.go
@@ -0,0 +1,100 @@
+package balances
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+type Balances map[crypto.Address]gnoland.Balance
+
+func New() Balances {
+ return make(Balances)
+}
+
+func (balances Balances) Set(address crypto.Address, amount std.Coins) {
+ balances[address] = gnoland.Balance{
+ Address: address,
+ Amount: amount,
+ }
+}
+
+func (balances Balances) Get(address crypto.Address) (balance gnoland.Balance, ok bool) {
+ balance, ok = balances[address]
+ return
+}
+
+func (balances Balances) List() []gnoland.Balance {
+ list := make([]gnoland.Balance, 0, len(balances))
+ for _, balance := range balances {
+ list = append(list, balance)
+ }
+ return list
+}
+
+// leftMerge left-merges the two maps
+func (a Balances) LeftMerge(b Balances) {
+ for key, bVal := range b {
+ if _, present := (a)[key]; !present {
+ (a)[key] = bVal
+ }
+ }
+}
+
+func GetBalancesFromEntries(entries ...string) (Balances, error) {
+ balances := New()
+ return balances, balances.LoadFromEntries(entries...)
+}
+
+// LoadFromEntries extracts the balance entries in the form of =
+func (balances Balances) LoadFromEntries(entries ...string) error {
+ for _, entry := range entries {
+ var balance gnoland.Balance
+ if err := balance.Parse(entry); err != nil {
+ return fmt.Errorf("unable to parse balance entry: %w", err)
+ }
+ balances[balance.Address] = balance
+ }
+
+ return nil
+}
+
+func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
+ balances := New()
+ return balances, balances.LoadFromSheet(sheet)
+}
+
+// LoadFromSheet extracts the balance sheet from the passed in
+// balance sheet file, that has the format of =ugnot
+func (balances Balances) LoadFromSheet(sheet io.Reader) error {
+ // Parse the balances
+ scanner := bufio.NewScanner(sheet)
+
+ for scanner.Scan() {
+ entry := scanner.Text()
+
+ // Remove comments
+ entry = strings.Split(entry, "#")[0]
+ entry = strings.TrimSpace(entry)
+
+ // Skip empty lines
+ if entry == "" {
+ continue
+ }
+
+ if err := balances.LoadFromEntries(entry); err != nil {
+ return fmt.Errorf("unable to load entries: %w", err)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("error encountered while scanning, %w", err)
+ }
+
+ return nil
+}
diff --git a/gno.land/pkg/balances/balances_test.go b/gno.land/pkg/balances/balances_test.go
new file mode 100644
index 00000000000..7d42f4ea56c
--- /dev/null
+++ b/gno.land/pkg/balances/balances_test.go
@@ -0,0 +1,190 @@
+package balances
+
+import (
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
+ "github.com/gnolang/gno/tm2/pkg/crypto/hd"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys/client"
+ "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
+ "github.com/gnolang/gno/tm2/pkg/std"
+ "github.com/jaekwon/testify/require"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBalances_GetBalancesFromEntries(t *testing.T) {
+ t.Parallel()
+
+ t.Run("valid balances", func(t *testing.T) {
+ t.Parallel()
+
+ // Generate dummy keys
+ dummyKeys := getDummyKeys(t, 2)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
+
+ entries := make([]string, len(dummyKeys))
+
+ for index, key := range dummyKeys {
+ entries[index] = fmt.Sprintf(
+ "%s=%dugnot",
+ key.Address().String(),
+ amount.AmountOf("ugnot"),
+ )
+ }
+
+ balanceMap, err := GetBalancesFromEntries(entries...)
+ require.NoError(t, err)
+
+ // Validate the balance map
+ assert.Len(t, balanceMap, len(dummyKeys))
+ for _, key := range dummyKeys {
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
+ }
+ })
+
+ t.Run("malformed balance, invalid format", func(t *testing.T) {
+ t.Parallel()
+
+ entries := []string{
+ "malformed balance",
+ }
+
+ balanceMap, err := GetBalancesFromEntries(entries...)
+
+ assert.Nil(t, balanceMap)
+ assert.ErrorContains(t, err, "malformed entry")
+ })
+
+ t.Run("malformed balance, invalid address", func(t *testing.T) {
+ t.Parallel()
+
+ balances := []string{
+ "dummyaddress=10ugnot",
+ }
+
+ balanceMap, err := GetBalancesFromEntries(balances...)
+
+ assert.Nil(t, balanceMap)
+ assert.ErrorContains(t, err, "invalid address")
+ })
+
+ t.Run("malformed balance, invalid amount", func(t *testing.T) {
+ t.Parallel()
+
+ dummyKey := getDummyKey(t)
+
+ balances := []string{
+ fmt.Sprintf(
+ "%s=%sugnot",
+ dummyKey.Address().String(),
+ strconv.FormatUint(math.MaxUint64, 10),
+ ),
+ }
+
+ balanceMap, err := GetBalancesFromEntries(balances...)
+
+ assert.Nil(t, balanceMap)
+ assert.ErrorContains(t, err, "invalid amount")
+ })
+}
+
+func TestBalances_GetBalancesFromSheet(t *testing.T) {
+ t.Parallel()
+
+ t.Run("valid balances", func(t *testing.T) {
+ t.Parallel()
+
+ // Generate dummy keys
+ dummyKeys := getDummyKeys(t, 2)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
+
+ balances := make([]string, len(dummyKeys))
+
+ for index, key := range dummyKeys {
+ balances[index] = fmt.Sprintf(
+ "%s=%dugnot",
+ key.Address().String(),
+ amount.AmountOf("ugnot"),
+ )
+ }
+
+ reader := strings.NewReader(strings.Join(balances, "\n"))
+ balanceMap, err := GetBalancesFromSheet(reader)
+ require.NoError(t, err)
+
+ // Validate the balance map
+ assert.Len(t, balanceMap, len(dummyKeys))
+ for _, key := range dummyKeys {
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
+ }
+ })
+
+ t.Run("malformed balance, invalid amount", func(t *testing.T) {
+ t.Parallel()
+
+ dummyKey := getDummyKey(t)
+
+ balances := []string{
+ fmt.Sprintf(
+ "%s=%sugnot",
+ dummyKey.Address().String(),
+ strconv.FormatUint(math.MaxUint64, 10),
+ ),
+ }
+
+ reader := strings.NewReader(strings.Join(balances, "\n"))
+
+ balanceMap, err := GetBalancesFromSheet(reader)
+
+ assert.Nil(t, balanceMap)
+ assert.ErrorContains(t, err, "invalid amount")
+ })
+}
+
+// XXX: this function should probably be exposed somewhere as it's duplicate of
+// cmd/genesis/...
+
+// getDummyKey generates a random public key,
+// and returns the key info
+func getDummyKey(t *testing.T) crypto.PubKey {
+ t.Helper()
+
+ mnemonic, err := client.GenerateMnemonic(256)
+ require.NoError(t, err)
+
+ seed := bip39.NewSeed(mnemonic, "")
+
+ return generateKeyFromSeed(seed, 0).PubKey()
+}
+
+// getDummyKeys generates random keys for testing
+func getDummyKeys(t *testing.T, count int) []crypto.PubKey {
+ t.Helper()
+
+ dummyKeys := make([]crypto.PubKey, count)
+
+ for i := 0; i < count; i++ {
+ dummyKeys[i] = getDummyKey(t)
+ }
+
+ return dummyKeys
+}
+
+// generateKeyFromSeed generates a private key from
+// the provided seed and index
+func generateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey {
+ pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index)
+
+ masterPriv, ch := hd.ComputeMastersFromSeed(seed)
+
+ //nolint:errcheck // This derivation can never error out, since the path params
+ // are always going to be valid
+ derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String())
+
+ return secp256k1.PrivKeySecp256k1(derivedPriv)
+}
From 9bb5de68c9fb4c1cd65bb3218fc89cc683260143 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Mon, 15 Apr 2024 14:34:44 +0200
Subject: [PATCH 02/23] fix: cleanup
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/account.go | 41 ++++++
contribs/gnodev/main.go | 187 ++++++++++++++----------
contribs/gnodev/pkg/dev/keybase.go | 102 -------------
contribs/gnodev/pkg/dev/node.go | 14 +-
contribs/gnodev/pkg/dev/packages.go | 18 +--
contribs/gnodev/pkg/rawterm/keypress.go | 1 +
gno.land/pkg/gnoland/types.go | 90 ++++++++++++
gno.land/pkg/gnoland/types_test.go | 181 +++++++++++++++++++++++
8 files changed, 440 insertions(+), 194 deletions(-)
create mode 100644 contribs/gnodev/account.go
delete mode 100644 contribs/gnodev/pkg/dev/keybase.go
diff --git a/contribs/gnodev/account.go b/contribs/gnodev/account.go
new file mode 100644
index 00000000000..d7e47e5f62b
--- /dev/null
+++ b/contribs/gnodev/account.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+type varAccounts map[string]std.Coins // name or bech32 -> coins
+
+func (va *varAccounts) Set(value string) error {
+ if *va == nil {
+ *va = map[string]std.Coins{}
+ }
+ accounts := *va
+
+ user, amount, found := strings.Cut(value, ":")
+ accounts[user] = nil
+ if !found {
+ return nil
+ }
+
+ coins, err := std.ParseCoins(amount)
+ if err != nil {
+ return fmt.Errorf("unable to parse coins from %q: %w", user, err)
+ }
+
+ // Add the parsed amount into user
+ accounts[user] = coins
+ return nil
+}
+
+func (va varAccounts) String() string {
+ accs := make([]string, 0, len(va))
+ for user, balance := range va {
+ accs = append(accs, fmt.Sprintf("%s(%s)", user, balance.String()))
+ }
+
+ return strings.Join(accs, ",")
+}
diff --git a/contribs/gnodev/main.go b/contribs/gnodev/main.go
index 9bd52d89ec7..f024f4e0bae 100644
--- a/contribs/gnodev/main.go
+++ b/contribs/gnodev/main.go
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"log/slog"
- "math/rand"
"net"
"net/http"
"os"
@@ -29,10 +28,13 @@ import (
gnolog "github.com/gnolang/gno/gno.land/pkg/log"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
+ "github.com/gnolang/gno/tm2/pkg/amino"
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/crypto/bip39"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
osm "github.com/gnolang/gno/tm2/pkg/os"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/muesli/termenv"
@@ -60,21 +62,21 @@ type devCfg struct {
nodeProxyAppListenerAddr string
// Users default
- genesisCreator string
- home string
- root string
+ genesisCreator string
+ home string
+ root string
+ additionalUsers varAccounts
// Node Configuration
- minimal bool
- verbose bool
- hotreload bool
- noWatch bool
- noReplay bool
- maxGas int64
- chainId string
- serverMode bool
- balancesFile string
- additionalUsers string
+ minimal bool
+ verbose bool
+ hotreload bool
+ noWatch bool
+ noReplay bool
+ maxGas int64
+ chainId string
+ serverMode bool
+ balancesFile string
}
var defaultDevOptions = &devCfg{
@@ -142,18 +144,17 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
"listening address for GnoLand RPC node",
)
- fs.StringVar(
- &c.nodeRPCListenerAddr,
- "add-users",
- defaultDevOptions.nodeRPCListenerAddr,
- "pre-add users, separated by commas",
+ fs.Var(
+ &c.additionalUsers,
+ "add-user",
+ "pre-add a user",
)
fs.StringVar(
&c.genesisCreator,
- "creator",
+ "genesis-creator",
defaultDevOptions.genesisCreator,
- "name of the genesis creator (from Keybase) or address",
+ "name or bech32 address of the genesis creator",
)
fs.BoolVar(
@@ -228,7 +229,7 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
emitterServer := emitter.NewServer(loggerEvents)
// load keybase
- kb, err := setupKeybase(cfg)
+ kb, err := setupKeybase(cfg, logger)
if err != nil {
return fmt.Errorf("unable to load keybase: %w", err)
}
@@ -241,18 +242,14 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
// Setup Dev Node
// XXX: find a good way to export or display node logs
- devNode, err := setupDevNode(ctx, logger, cfg, emitterServer, kb, pkgpaths)
+ nodeLogger := logger.WithGroup(NodeLogName)
+ devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, kb, pkgpaths)
if err != nil {
return err
}
defer devNode.Close()
- logger.WithGroup(NodeLogName).
- Info("node started",
- "lisn", devNode.GetRemoteAddress(),
- "addr", gnodev.DefaultCreator.String(),
- "chainID", cfg.chainId,
- )
+ nodeLogger.Info("node started", "lisn", devNode.GetRemoteAddress(), "chainID", cfg.chainId)
// Create server
mux := http.NewServeMux()
@@ -292,14 +289,17 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
// Add node pkgs to watcher
watcher.AddPackages(devNode.ListPkgs()...)
- logger.WithGroup("--- READY").Info("for commands and help, press `h`")
+ if !cfg.serverMode {
+ logger.WithGroup("--- READY").Info("for commands and help, press `h`")
+ }
// Run the main event loop
- return runEventLoop(ctx, logger, rt, devNode, watcher)
+ return runEventLoop(ctx, logger, kb, rt, devNode, watcher)
}
var helper string = `
-H Help - display this message
+A Accounts - Display known accounts
+H Help - Display this message
R Reload - Reload all packages to take change into account.
Ctrl+R Reset - Reset application state.
Ctrl+C Exit - Exit the application
@@ -308,6 +308,7 @@ Ctrl+C Exit - Exit the application
func runEventLoop(
ctx context.Context,
logger *slog.Logger,
+ kb keys.Keybase,
rt *rawterm.RawTerm,
dnode *dev.Node,
watch *watcher.PackageWatcher,
@@ -346,9 +347,11 @@ func runEventLoop(
)
switch key.Upper() {
- case rawterm.KeyH:
+ case rawterm.KeyH: // Helper
logger.Info("Gno Dev Helper", "helper", helper)
- case rawterm.KeyR:
+ case rawterm.KeyA: // Accounts
+ logAccounts(logger.WithGroup("accounts"), kb, dnode)
+ case rawterm.KeyR: // Reload
logger.WithGroup(NodeLogName).Info("reloading...")
if err = dnode.ReloadAll(ctx); err != nil {
logger.WithGroup(NodeLogName).
@@ -356,17 +359,14 @@ func runEventLoop(
}
- case rawterm.KeyCtrlR:
+ case rawterm.KeyCtrlR: // Reset
logger.WithGroup(NodeLogName).Info("reseting node state...")
if err = dnode.Reset(ctx); err != nil {
logger.WithGroup(NodeLogName).
Error("unable to reset node state", "err", err)
}
- case rawterm.KeyCtrlC:
- return nil
- case rawterm.KeyCtrlE:
- panic("NOOOO")
+ case rawterm.KeyCtrlC: // Exit
return nil
default:
}
@@ -449,12 +449,11 @@ func setupDevNode(
kb keys.Keybase,
pkgspath []dev.PackagePath,
) (*gnodev.Node, error) {
- nodeLogger := logger.WithGroup(NodeLogName)
-
balances, err := generateBalances(kb, cfg)
if err != nil {
return nil, fmt.Errorf("unable to generate balances: %w", err)
}
+ logger.Debug("balances loaded", "list", balances.List())
// configure gnoland node
config := gnodev.DefaultNodeConfig(cfg.root)
@@ -469,7 +468,7 @@ func setupDevNode(
config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr
config.TMConfig.ProxyApp = defaultDevOptions.nodeProxyAppListenerAddr
- return gnodev.NewDevNode(ctx, nodeLogger, remitter, config)
+ return gnodev.NewDevNode(ctx, logger, remitter, config)
}
// setupGnowebServer initializes and starts the Gnoweb server.
@@ -501,6 +500,7 @@ func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([
return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err)
}
+ // Assign a default creator if user haven't specified it.
if path.Creator.IsZero() {
path.Creator = defaultKey.GetAddress()
}
@@ -508,9 +508,8 @@ func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([
paths[i] = path
}
- // Add examples folder if minimal specified
+ // Add examples folder if minimal is set to false
if !cfg.minimal {
- fmt.Println("adding example folder: ", filepath.Join(cfg.root, "examples"))
paths = append(paths, gnodev.PackagePath{
Path: filepath.Join(cfg.root, "examples"),
Creator: defaultKey.GetAddress(),
@@ -523,17 +522,23 @@ func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([
func generateBalances(kb keys.Keybase, cfg *devCfg) (balances.Balances, error) {
bls := balances.New()
- amount := std.Coins{std.NewCoin("ugnot", 10e6)}
+ unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
keys, err := kb.List()
if err != nil {
return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
}
- // Automatically set every key from keybase to unlimited found
+ // Automatically set every key from keybase to unlimited found (or pre
+ // defined found if specified)
for _, key := range keys {
+ found := unlimitedFund
+ if preDefinedFound, ok := cfg.additionalUsers[key.GetName()]; ok && preDefinedFound != nil {
+ found = preDefinedFound
+ }
+
address := key.GetAddress()
- bls[address] = gnoland.Balance{Amount: amount, Address: address}
+ bls[address] = gnoland.Balance{Amount: found, Address: address}
}
if cfg.balancesFile == "" {
@@ -550,12 +555,12 @@ func generateBalances(kb keys.Keybase, cfg *devCfg) (balances.Balances, error) {
return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
}
- // Left merge keybase balance into file balance
+ // Left merge keybase balance into loaded file balance
blsFile.LeftMerge(bls)
return blsFile, nil
}
-func listenForKeyPress(w io.Writer, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
+func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
cc := make(chan rawterm.KeyPress, 1)
go func() {
defer close(cc)
@@ -586,15 +591,6 @@ func createAccount(kb keys.Keybase, accountName string) (keys.Info, error) {
return kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
}
-func checkForError(w io.Writer, err error) {
- if err != nil {
- fmt.Fprintf(w, "[ERROR] - %s\n", err.Error())
- return
- }
-
- fmt.Fprintln(w, "[DONE]")
-}
-
func resolveUnixOrTCPAddr(in string) (out string) {
var err error
var addr net.Addr
@@ -618,7 +614,7 @@ func resolveUnixOrTCPAddr(in string) (out string) {
panic(err)
}
-func setupKeybase(cfg *devCfg) (keys.Keybase, error) {
+func setupKeybase(cfg *devCfg, logger *slog.Logger) (keys.Keybase, error) {
kb := keys.NewInMemory()
if cfg.home != "" {
// load home keybase into our inMemory keybase
@@ -646,34 +642,75 @@ func setupKeybase(cfg *devCfg) (keys.Keybase, error) {
}
// Add additional users to our keybase
- addUsers := strings.Split(cfg.additionalUsers, ",")
- for _, user := range addUsers {
- if _, err := createAccount(kb, user); err != nil {
+ for user := range cfg.additionalUsers {
+ info, err := createAccount(kb, user)
+ if err != nil {
return nil, fmt.Errorf("unable to create user %q: %w", user, err)
}
- }
- // Add default creator it doesn't exist
- ok, _ := kb.HasByAddress(DefaultCreatorAddress)
- if ok {
- return kb, nil
+ logger.Info("additional user", "name", info.GetName(), "addr", info.GetAddress())
}
- for i := 0; i < 5; i++ {
- creatorName := fmt.Sprintf("_testUser%05d\n", rand.Intn(100000))
- ok, _ := kb.HasByName(creatorName)
- if ok {
- continue
+ // Next, make sure that we have a default address to load packages
+ var info keys.Info
+ var err error
+
+ info, err = kb.GetByNameOrAddress(cfg.genesisCreator)
+ switch {
+ case err == nil: // user already have a default user
+ case keyerror.IsErrKeyNotFound(err):
+ // if the key isn't found, create a default one
+ creatorName := fmt.Sprintf("_default#%.10s", DefaultCreatorAddress.String())
+ if ok, _ := kb.HasByName(creatorName); ok {
+ // if a collision happen here, someone really want to not run.
+ return nil, fmt.Errorf("unable to create creator account, delete %q from your keybase", creatorName)
}
- if _, err := kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0); err != nil {
+ info, err = kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0)
+ if err != nil {
return nil, fmt.Errorf("unable to create default %q account: %w", DefaultCreatorName, err)
}
+ default:
+ return nil, fmt.Errorf("unable to get address %q from keybase: %w", info.GetAddress(), err)
+ }
+
+ logger.Info("default creator", "name", info.GetName(), "addr", info.GetAddress())
+ return kb, nil
+}
+
+func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
+ keys, err := kb.List()
+ if err != nil {
+ return fmt.Errorf("unable to get keybase keys list: %w", err)
+ }
+
+ accounts := make([]string, len(keys))
+ for i, key := range keys {
+ if key.GetName() == "" {
+ continue // skip empty key name
+ }
+
+ address := key.GetAddress()
+ qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address.String(), []byte{})
+ if err != nil {
+ return fmt.Errorf("unable to querry account %q: %w", address.String(), err)
+ }
+
+ var qret struct{ BaseAccount std.BaseAccount }
+ if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil {
+ return fmt.Errorf("unable to unmarshal query response: %w", err)
+ }
- return kb, nil
+ // format name - (address) -> (coins) -> (acct-num) -> (seq)
+ accounts[i] = fmt.Sprintf("%s: addr(%s) coins(%s) acct_num(%d)",
+ key.GetName(),
+ address.String(),
+ qret.BaseAccount.GetCoins().String(),
+ qret.BaseAccount.GetAccountNumber())
}
- panic("unable to generate ranmdom test user name")
+ logger.Info("current accounts", "balances", strings.Join(accounts, "\n"))
+ return nil
}
func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger {
diff --git a/contribs/gnodev/pkg/dev/keybase.go b/contribs/gnodev/pkg/dev/keybase.go
deleted file mode 100644
index 7f69a36c31b..00000000000
--- a/contribs/gnodev/pkg/dev/keybase.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package dev
-
-import (
- "fmt"
-
- "github.com/gnolang/gno/gno.land/pkg/gnoland"
- "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
- "github.com/gnolang/gno/tm2/pkg/std"
-)
-
-// type Keybase struct {
-// keys.Keybase
-// }
-
-// func NewKeybase() *Keybase {
-// return &Keybase{
-// Keybase: keys.NewInMemory(),
-// }
-// }
-
-// func (to *Keybase) ImportKeybaseFromPath(path string) error {
-// from, err := keys.NewKeyBaseFromDir(path)
-// if err != nil {
-// return fmt.Errorf("unable to load keybase: %w", err)
-// }
-
-// keys, err := from.List()
-// if err != nil {
-// return fmt.Errorf("unable to list keys: %w", path, err)
-// }
-
-// for _, key := range keys {
-// armor, err := from.Export(key.GetName())
-// if err != nil {
-// return fmt.Errorf("unable to import key %q: %w", key.GetName(), err)
-// }
-
-// err = to.Import(key.GetName(), armor)
-// if err != nil {
-// return fmt.Errorf("unable to import key %q: %w", key.GetName(), err)
-// }
-// }
-
-// return nil
-// }
-
-// type PackagePath struct {
-// Path string
-// CreatorNameOrAddress string
-// }
-
-// func ParsePackagePath(path string) (PackagePath, error) {
-// var ppath PackagePath
-
-// upath, err := url.Parse(path)
-// if err != nil {
-// return ppath, fmt.Errorf("unable to parse package path: %w", err)
-// }
-
-// // Get path
-// ppath.Path = filepath.Clean(upath.Path)
-// // Check for options
-// ppath.CreatorNameOrAddress = upath.Query().Get("creator")
-// return ppath, nil
-// }
-
-// func LoadKeyabaseBalanceFromPath(kb keys.Keybase) ([]gnoland.Balance, error) {
-// keys, err := kb.List()
-// if err != nil {
-// return nil, nil
-// }
-
-// for _, info := range keys {
-// info.GetName()
-// }
-// }
-
-// loadAccount with the given name and adds it to the keybase.
-func loadAccount(kb keys.Keybase, accountName string) (gnoland.Balance, error) {
- var balance gnoland.Balance
- 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()
- return gnoland.Balance{
- Address: address,
- Amount: std.Coins{std.NewCoin("ugnot", 10e6)},
- }, nil
-}
diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go
index baa8c3626df..786c9349d0e 100644
--- a/contribs/gnodev/pkg/dev/node.go
+++ b/contribs/gnodev/pkg/dev/node.go
@@ -46,7 +46,7 @@ func DefaultNodeConfig(rootdir string) *NodeConfig {
balances := []gnoland.Balance{
{
Address: defaultCreator,
- Amount: std.Coins{std.NewCoin("ugnot", 10e6)},
+ Amount: std.Coins{std.NewCoin("ugnot", 10e12)},
},
}
@@ -83,7 +83,7 @@ func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitte
return nil, fmt.Errorf("unable map pkgs list: %w", err)
}
- pkgsTxs, err := mpkgs.Load(cfg.DefaultCreator, DefaultFee)
+ pkgsTxs, err := mpkgs.Load(DefaultFee)
if err != nil {
return nil, fmt.Errorf("unable to load genesis packages: %w", err)
}
@@ -186,7 +186,7 @@ func (d *Node) Reset(ctx context.Context) error {
}
// Generate a new genesis state based on the current packages
- txs, err := d.pkgs.Load(d.config.DefaultCreator, DefaultFee)
+ txs, err := d.pkgs.Load(DefaultFee)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
@@ -244,7 +244,7 @@ func (d *Node) Reload(ctx context.Context) error {
}
// Load genesis packages
- pkgsTxs, err := d.pkgs.Load(d.config.DefaultCreator, DefaultFee)
+ pkgsTxs, err := d.pkgs.Load(DefaultFee)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
@@ -318,6 +318,12 @@ func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) {
return txs, nil
}
+// GetBlockTransactions returns the transactions contained
+// within the specified block, if any
+func (d *Node) CurrentBalances(blockNum uint64) ([]std.Tx, error) {
+ return nil, nil
+}
+
// GetBlockTransactions returns the transactions contained
// within the specified block, if any
// GetLatestBlockNumber returns the latest block height from the chain
diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go
index 65acb8ec585..bf69338b2b5 100644
--- a/contribs/gnodev/pkg/dev/packages.go
+++ b/contribs/gnodev/pkg/dev/packages.go
@@ -9,7 +9,6 @@ import (
vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
- bft "github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/std"
@@ -24,7 +23,7 @@ type PackagePath struct {
func ResolvePackagePathQuery(kb keys.Keybase, path string) (PackagePath, error) {
var ppath PackagePath
- upath, err := url.Parse(filepath.Clean(path))
+ upath, err := url.Parse(path)
if err != nil {
return ppath, fmt.Errorf("malformed path/query: %w", err)
}
@@ -78,25 +77,15 @@ var (
func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) {
pkgs := make(map[string]Package)
for _, ppath := range ppaths {
- fmt.Println(ppath)
if ppath.Creator.IsZero() {
return nil, fmt.Errorf("unable to load package %q: %w", ppath.Path, ErrEmptyCreatorPackage)
}
- // if ppath.Deposit.Empty() {
- // return nil, fmt.Errorf("unable to load package %q: %w", ppath.Path, ErrEmptyDepositPackage)
- // }
-
abspath, err := filepath.Abs(ppath.Path)
if err != nil {
return nil, fmt.Errorf("unable to guess absolute path for %q: %w", ppath.Path, err)
}
- // rootdir, err := gnomod.FindRootDir(abspath)
- // if err != nil {
- // return nil, fmt.Errorf("unable to find rootdir for package %q: %w", ppath.Path, err)
- // }
-
// list all packages from target path
pkgslist, err := gnomod.ListPkgs(abspath)
if err != nil {
@@ -130,7 +119,7 @@ func (pm PackagesMap) toList() gnomod.PkgList {
return list
}
-func (pm PackagesMap) Load(creator bft.Address, fee std.Fee) ([]std.Tx, error) {
+func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) {
pkgs := pm.toList()
sorted, err := pkgs.Sort()
@@ -142,6 +131,9 @@ func (pm PackagesMap) Load(creator bft.Address, fee std.Fee) ([]std.Tx, error) {
txs := []std.Tx{}
for _, modPkg := range nonDraft {
pkg := pm[modPkg.Dir]
+ if pkg.Creator.IsZero() {
+ return nil, fmt.Errorf("no creator was set for %q", pkg.Dir)
+ }
// Open files in directory as MemPackage.
memPkg := gno.ReadMemPackage(modPkg.Dir, modPkg.Name)
diff --git a/contribs/gnodev/pkg/rawterm/keypress.go b/contribs/gnodev/pkg/rawterm/keypress.go
index 20503476a9b..48f8367a65b 100644
--- a/contribs/gnodev/pkg/rawterm/keypress.go
+++ b/contribs/gnodev/pkg/rawterm/keypress.go
@@ -18,6 +18,7 @@ const (
KeyCtrlR KeyPress = '\x12' // Ctrl+R
KeyCtrlT KeyPress = '\x14' // Ctrl+T
+ KeyA KeyPress = 'A'
KeyH KeyPress = 'H'
KeyR KeyPress = 'R'
)
diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go
index 5d68064c9c5..da6bef97746 100644
--- a/gno.land/pkg/gnoland/types.go
+++ b/gno.land/pkg/gnoland/types.go
@@ -1,8 +1,10 @@
package gnoland
import (
+ "bufio"
"errors"
"fmt"
+ "io"
"strings"
bft "github.com/gnolang/gno/tm2/pkg/bft/types"
@@ -77,3 +79,91 @@ func (b Balance) MarshalAmino() (string, error) {
func (b Balance) String() string {
return fmt.Sprintf("%s=%s", b.Address.String(), b.Amount.String())
}
+
+type Balances map[crypto.Address]Balance
+
+func New() Balances {
+ return make(Balances)
+}
+
+func (balances Balances) Set(address crypto.Address, amount std.Coins) {
+ balances[address] = Balance{
+ Address: address,
+ Amount: amount,
+ }
+}
+
+func (balances Balances) Get(address crypto.Address) (balance Balance, ok bool) {
+ balance, ok = balances[address]
+ return
+}
+
+func (balances Balances) List() []Balance {
+ list := make([]Balance, 0, len(balances))
+ for _, balance := range balances {
+ list = append(list, balance)
+ }
+ return list
+}
+
+// leftMerge left-merges the two maps
+func (a Balances) LeftMerge(b Balances) {
+ for key, bVal := range b {
+ if _, present := (a)[key]; !present {
+ (a)[key] = bVal
+ }
+ }
+}
+
+func GetBalancesFromEntries(entries ...string) (Balances, error) {
+ balances := New()
+ return balances, balances.LoadFromEntries(entries...)
+}
+
+// LoadFromEntries extracts the balance entries in the form of =
+func (balances Balances) LoadFromEntries(entries ...string) error {
+ for _, entry := range entries {
+ var balance Balance
+ if err := balance.Parse(entry); err != nil {
+ return fmt.Errorf("unable to parse balance entry: %w", err)
+ }
+ balances[balance.Address] = balance
+ }
+
+ return nil
+}
+
+func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
+ balances := New()
+ return balances, balances.LoadFromSheet(sheet)
+}
+
+// LoadFromSheet extracts the balance sheet from the passed in
+// balance sheet file, that has the format of =ugnot
+func (balances Balances) LoadFromSheet(sheet io.Reader) error {
+ // Parse the balances
+ scanner := bufio.NewScanner(sheet)
+
+ for scanner.Scan() {
+ entry := scanner.Text()
+
+ // Remove comments
+ entry = strings.Split(entry, "#")[0]
+ entry = strings.TrimSpace(entry)
+
+ // Skip empty lines
+ if entry == "" {
+ continue
+ }
+
+ if err := balances.LoadFromEntries(entry); err != nil {
+ return fmt.Errorf("unable to load entries: %w", err)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("error encountered while scanning, %w", err)
+ }
+
+ return nil
+}
diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go
index 97222d0cdfd..00e5d46d996 100644
--- a/gno.land/pkg/gnoland/types_test.go
+++ b/gno.land/pkg/gnoland/types_test.go
@@ -2,11 +2,18 @@ package gnoland
import (
"fmt"
+ "math"
+ "strconv"
+ "strings"
"testing"
"github.com/gnolang/gno/tm2/pkg/amino"
bft "github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
+ "github.com/gnolang/gno/tm2/pkg/crypto/hd"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys/client"
+ "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/jaekwon/testify/assert"
"github.com/jaekwon/testify/require"
@@ -96,3 +103,177 @@ func TestBalance_AminoMarshalJSON(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, expectedJSON, string(balancesJSON))
}
+
+func TestBalances_GetBalancesFromEntries(t *testing.T) {
+ t.Parallel()
+
+ t.Run("valid balances", func(t *testing.T) {
+ t.Parallel()
+
+ // Generate dummy keys
+ dummyKeys := getDummyKeys(t, 2)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
+
+ entries := make([]string, len(dummyKeys))
+
+ for index, key := range dummyKeys {
+ entries[index] = fmt.Sprintf(
+ "%s=%dugnot",
+ key.Address().String(),
+ amount.AmountOf("ugnot"),
+ )
+ }
+
+ balanceMap, err := GetBalancesFromEntries(entries...)
+ require.NoError(t, err)
+
+ // Validate the balance map
+ assert.Len(t, balanceMap, len(dummyKeys))
+ for _, key := range dummyKeys {
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
+ }
+ })
+
+ t.Run("malformed balance, invalid format", func(t *testing.T) {
+ t.Parallel()
+
+ entries := []string{
+ "malformed balance",
+ }
+
+ balanceMap, err := GetBalancesFromEntries(entries...)
+ require.NoError(t, err)
+
+ assert.Nil(t, balanceMap)
+ assert.Contains(t, err.Error(), "malformed entry")
+ })
+
+ t.Run("malformed balance, invalid address", func(t *testing.T) {
+ t.Parallel()
+
+ balances := []string{
+ "dummyaddress=10ugnot",
+ }
+
+ balanceMap, err := GetBalancesFromEntries(balances...)
+ require.NoError(t, err)
+
+ assert.Nil(t, balanceMap)
+ assert.Contains(t, err.Error(), "invalid address")
+ })
+
+ t.Run("malformed balance, invalid amount", func(t *testing.T) {
+ t.Parallel()
+
+ dummyKey := getDummyKey(t)
+
+ balances := []string{
+ fmt.Sprintf(
+ "%s=%sugnot",
+ dummyKey.Address().String(),
+ strconv.FormatUint(math.MaxUint64, 10),
+ ),
+ }
+
+ balanceMap, err := GetBalancesFromEntries(balances...)
+
+ assert.Nil(t, balanceMap)
+ assert.ErrorContains(t, err, "invalid amount")
+ })
+}
+
+func TestBalances_GetBalancesFromSheet(t *testing.T) {
+ t.Parallel()
+
+ t.Run("valid balances", func(t *testing.T) {
+ t.Parallel()
+
+ // Generate dummy keys
+ dummyKeys := getDummyKeys(t, 2)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
+
+ balances := make([]string, len(dummyKeys))
+
+ for index, key := range dummyKeys {
+ balances[index] = fmt.Sprintf(
+ "%s=%dugnot",
+ key.Address().String(),
+ amount.AmountOf("ugnot"),
+ )
+ }
+
+ reader := strings.NewReader(strings.Join(balances, "\n"))
+ balanceMap, err := GetBalancesFromSheet(reader)
+ require.NoError(t, err)
+
+ // Validate the balance map
+ assert.Len(t, balanceMap, len(dummyKeys))
+ for _, key := range dummyKeys {
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
+ }
+ })
+
+ t.Run("malformed balance, invalid amount", func(t *testing.T) {
+ t.Parallel()
+
+ dummyKey := getDummyKey(t)
+
+ balances := []string{
+ fmt.Sprintf(
+ "%s=%sugnot",
+ dummyKey.Address().String(),
+ strconv.FormatUint(math.MaxUint64, 10),
+ ),
+ }
+
+ reader := strings.NewReader(strings.Join(balances, "\n"))
+
+ balanceMap, err := GetBalancesFromSheet(reader)
+
+ assert.Nil(t, balanceMap)
+ assert.ErrorContains(t, err, "invalid amount")
+ })
+}
+
+// XXX: this function should probably be exposed somewhere as it's duplicate of
+// cmd/genesis/...
+
+// getDummyKey generates a random public key,
+// and returns the key info
+func getDummyKey(t *testing.T) crypto.PubKey {
+ t.Helper()
+
+ mnemonic, err := client.GenerateMnemonic(256)
+ require.NoError(t, err)
+
+ seed := bip39.NewSeed(mnemonic, "")
+
+ return generateKeyFromSeed(seed, 0).PubKey()
+}
+
+// getDummyKeys generates random keys for testing
+func getDummyKeys(t *testing.T, count int) []crypto.PubKey {
+ t.Helper()
+
+ dummyKeys := make([]crypto.PubKey, count)
+
+ for i := 0; i < count; i++ {
+ dummyKeys[i] = getDummyKey(t)
+ }
+
+ return dummyKeys
+}
+
+// generateKeyFromSeed generates a private key from
+// the provided seed and index
+func generateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey {
+ pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index)
+
+ masterPriv, ch := hd.ComputeMastersFromSeed(seed)
+
+ //nolint:errcheck // This derivation can never error out, since the path params
+ // are always going to be valid
+ derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String())
+
+ return secp256k1.PrivKeySecp256k1(derivedPriv)
+}
From ca194ef8a299a5646faca6f6fb65ad1ece1a3255 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 16 Apr 2024 16:18:06 +0200
Subject: [PATCH 03/23] fix: cleanup and move gnodev into /cmd
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/Makefile | 4 +-
contribs/gnodev/account.go | 41 --
contribs/gnodev/cmd/gnodev/accounts.go | 208 +++++++
contribs/gnodev/cmd/gnodev/logger.go | 35 ++
contribs/gnodev/cmd/gnodev/main.go | 413 +++++++++++++
contribs/gnodev/cmd/gnodev/setup_node.go | 73 +++
contribs/gnodev/cmd/gnodev/setup_term.go | 24 +
contribs/gnodev/cmd/gnodev/setup_web.go | 20 +
contribs/gnodev/main.go | 738 -----------------------
docs/gno-tooling/cli/gnodev.md | 50 +-
gno.land/pkg/balances/balances.go | 100 ---
gno.land/pkg/balances/balances_test.go | 190 ------
gno.land/pkg/gnoland/types.go | 6 +-
13 files changed, 819 insertions(+), 1083 deletions(-)
delete mode 100644 contribs/gnodev/account.go
create mode 100644 contribs/gnodev/cmd/gnodev/accounts.go
create mode 100644 contribs/gnodev/cmd/gnodev/logger.go
create mode 100644 contribs/gnodev/cmd/gnodev/main.go
create mode 100644 contribs/gnodev/cmd/gnodev/setup_node.go
create mode 100644 contribs/gnodev/cmd/gnodev/setup_term.go
create mode 100644 contribs/gnodev/cmd/gnodev/setup_web.go
delete mode 100644 contribs/gnodev/main.go
delete mode 100644 gno.land/pkg/balances/balances.go
delete mode 100644 gno.land/pkg/balances/balances_test.go
diff --git a/contribs/gnodev/Makefile b/contribs/gnodev/Makefile
index 23fb22a372d..7395b166a3e 100644
--- a/contribs/gnodev/Makefile
+++ b/contribs/gnodev/Makefile
@@ -3,7 +3,7 @@ GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../)
GOBUILD_FLAGS := -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)"
install:
- go install $(GOBUILD_FLAGS) .
+ go install $(GOBUILD_FLAGS) ./cmd/gnodev
build:
- go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gno
+ go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gnodev
diff --git a/contribs/gnodev/account.go b/contribs/gnodev/account.go
deleted file mode 100644
index d7e47e5f62b..00000000000
--- a/contribs/gnodev/account.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/gnolang/gno/tm2/pkg/std"
-)
-
-type varAccounts map[string]std.Coins // name or bech32 -> coins
-
-func (va *varAccounts) Set(value string) error {
- if *va == nil {
- *va = map[string]std.Coins{}
- }
- accounts := *va
-
- user, amount, found := strings.Cut(value, ":")
- accounts[user] = nil
- if !found {
- return nil
- }
-
- coins, err := std.ParseCoins(amount)
- if err != nil {
- return fmt.Errorf("unable to parse coins from %q: %w", user, err)
- }
-
- // Add the parsed amount into user
- accounts[user] = coins
- return nil
-}
-
-func (va varAccounts) String() string {
- accs := make([]string, 0, len(va))
- for user, balance := range va {
- accs = append(accs, fmt.Sprintf("%s(%s)", user, balance.String()))
- }
-
- return strings.Join(accs, ",")
-}
diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go
new file mode 100644
index 00000000000..3335cd7fde9
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/accounts.go
@@ -0,0 +1,208 @@
+package main
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ "github.com/gnolang/gno/tm2/pkg/amino"
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+type varAccounts map[string]std.Coins // name or bech32 -> coins
+
+func (va *varAccounts) Set(value string) error {
+ if *va == nil {
+ *va = map[string]std.Coins{}
+ }
+ accounts := *va
+
+ user, amount, found := strings.Cut(value, ":")
+ accounts[user] = nil
+ if !found {
+ return nil
+ }
+
+ coins, err := std.ParseCoins(amount)
+ if err != nil {
+ return fmt.Errorf("unable to parse coins from %q: %w", user, err)
+ }
+
+ // Add the parsed amount into user
+ accounts[user] = coins
+ return nil
+}
+
+func (va varAccounts) String() string {
+ accs := make([]string, 0, len(va))
+ for user, balance := range va {
+ accs = append(accs, fmt.Sprintf("%s(%s)", user, balance.String()))
+ }
+
+ return strings.Join(accs, ",")
+}
+
+func setupKeybase(logger *slog.Logger, cfg *devCfg) (keys.Keybase, error) {
+ kb := keys.NewInMemory()
+ if cfg.home != "" {
+ // Load home keybase into our inMemory keybase
+ kbHome, err := keys.NewKeyBaseFromDir(cfg.home)
+ if err != nil {
+ return nil, fmt.Errorf("unable to load keybase from dir %q: %w", cfg.home, err)
+ }
+
+ keys, err := kbHome.List()
+ if err != nil {
+ return nil, fmt.Errorf("unable to list keys from keybase %q: %w", cfg.home, err)
+ }
+
+ for _, key := range keys {
+ name := key.GetName()
+ armor, err := kbHome.Export(name)
+ if err != nil {
+ return nil, fmt.Errorf("unable to export key %q: %w", name, err)
+ }
+
+ if err := kb.Import(name, armor); err != nil {
+ return nil, fmt.Errorf("unable to import key %q: %w", name, err)
+ }
+ }
+ }
+
+ // Add additional users to our keybase
+ for user := range cfg.additionalUsers {
+ info, err := createAccount(kb, user)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create user %q: %w", user, err)
+ }
+
+ logger.Info("additional user", "name", info.GetName(), "addr", info.GetAddress())
+ }
+
+ // Next, make sure that we have a default address to load packages
+ info, err := kb.GetByNameOrAddress(cfg.genesisCreator)
+ switch {
+ case err == nil: // user already have a default user
+ break
+ case keyerror.IsErrKeyNotFound(err):
+ // If the key isn't found, create a default one
+ creatorName := fmt.Sprintf("_default#%.10s", DefaultCreatorAddress.String())
+ if ok, _ := kb.HasByName(creatorName); ok {
+ return nil, fmt.Errorf("unable to create creator account, delete %q from your keybase", creatorName)
+ }
+
+ info, err = kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create default %q account: %w", DefaultCreatorName, err)
+ }
+ default:
+ return nil, fmt.Errorf("unable to get address %q from keybase: %w", info.GetAddress(), err)
+ }
+
+ logger.Info("default creator", "name", info.GetName(), "addr", info.GetAddress())
+ return kb, nil
+}
+
+func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
+ bls := gnoland.NewBalances()
+ unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
+
+ keys, err := kb.List()
+ if err != nil {
+ return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
+ }
+
+ // Automatically set every key from keybase to unlimited found (or pre
+ // defined found if specified)
+ for _, key := range keys {
+ found := unlimitedFund
+ if preDefinedFound, ok := cfg.additionalUsers[key.GetName()]; ok && preDefinedFound != nil {
+ found = preDefinedFound
+ }
+
+ address := key.GetAddress()
+ bls[address] = gnoland.Balance{Amount: found, Address: address}
+ }
+
+ if cfg.balancesFile == "" {
+ return bls, nil
+ }
+
+ file, err := os.Open(cfg.balancesFile)
+ if err != nil {
+ return nil, fmt.Errorf("unable to open balance file %q: %w", cfg.balancesFile, err)
+ }
+
+ blsFile, err := gnoland.GetBalancesFromSheet(file)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
+ }
+
+ // Left merge keybase balance into loaded file balance
+ blsFile.LeftMerge(bls)
+ return blsFile, nil
+}
+
+func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
+ keys, err := kb.List()
+ if err != nil {
+ return fmt.Errorf("unable to get keybase keys list: %w", err)
+ }
+
+ var tab strings.Builder
+ tab.WriteRune('\n')
+ tabw := tabwriter.NewWriter(&tab, 0, 0, 2, ' ', tabwriter.TabIndent)
+
+ fmt.Fprintln(tabw, "KeyName\tAddress\tBalance") // Table header
+ for _, key := range keys {
+ if key.GetName() == "" {
+ continue // skip empty key name
+ }
+
+ address := key.GetAddress()
+ // XXX: use client from node from argument, should be exposed by the node directly
+ qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address.String(), []byte{})
+ if err != nil {
+ return fmt.Errorf("unable to querry account %q: %w", address.String(), err)
+ }
+
+ var qret struct{ BaseAccount std.BaseAccount }
+ if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil {
+ return fmt.Errorf("unable to unmarshal query response: %w", err)
+ }
+
+ // Insert row with name, addr, balance amount
+ fmt.Fprintf(tabw, "%s\t%s\t%s\n", key.GetName(),
+ address.String(),
+ qret.BaseAccount.GetCoins().String())
+ }
+ // Flush table
+ tabw.Flush()
+
+ headline := fmt.Sprintf("(%d) known accounts", len(keys))
+ logger.Info(headline, "table", tab.String())
+ return nil
+}
+
+// createAccount creates a new account with the given name and adds it to the keybase.
+func createAccount(kb keys.Keybase, accountName string) (keys.Info, error) {
+ entropy, err := bip39.NewEntropy(256)
+ if err != nil {
+ return nil, fmt.Errorf("error creating entropy: %w", err)
+ }
+
+ mnemonic, err := bip39.NewMnemonic(entropy)
+ if err != nil {
+ return nil, fmt.Errorf("error generating mnemonic: %w", err)
+ }
+
+ return kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
+}
diff --git a/contribs/gnodev/cmd/gnodev/logger.go b/contribs/gnodev/cmd/gnodev/logger.go
new file mode 100644
index 00000000000..9e69654f478
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/logger.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "io"
+ "log/slog"
+
+ "github.com/charmbracelet/lipgloss"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/logger"
+ gnolog "github.com/gnolang/gno/gno.land/pkg/log"
+ "github.com/muesli/termenv"
+)
+
+func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger {
+ level := slog.LevelInfo
+ if cfg.verbose {
+ level = slog.LevelDebug
+ }
+
+ if cfg.serverMode {
+ zaplogger := logger.NewZapLogger(out, level)
+ return gnolog.ZapLoggerToSlog(zaplogger)
+ }
+
+ // Detect term color profile
+ colorProfile := termenv.DefaultOutput().Profile
+ clogger := logger.NewColumnLogger(out, level, colorProfile)
+
+ // Register well known group color with system colors
+ clogger.RegisterGroupColor(NodeLogName, lipgloss.Color("3"))
+ clogger.RegisterGroupColor(WebLogName, lipgloss.Color("4"))
+ clogger.RegisterGroupColor(KeyPressLogName, lipgloss.Color("5"))
+ clogger.RegisterGroupColor(EventServerLogName, lipgloss.Color("6"))
+
+ return slog.New(clogger)
+}
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
new file mode 100644
index 00000000000..648fe78b20f
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -0,0 +1,413 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "os"
+ "path/filepath"
+
+ "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/watcher"
+ "github.com/gnolang/gno/gno.land/pkg/integration"
+ "github.com/gnolang/gno/gnovm/pkg/gnoenv"
+ "github.com/gnolang/gno/tm2/pkg/commands"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ osm "github.com/gnolang/gno/tm2/pkg/os"
+)
+
+const (
+ NodeLogName = "Node"
+ WebLogName = "GnoWeb"
+ KeyPressLogName = "KeyPress"
+ EventServerLogName = "Event"
+)
+
+var (
+ DefaultCreatorName = integration.DefaultAccount_Name
+ DefaultCreatorAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address)
+ DefaultCreatorSeed = integration.DefaultAccount_Seed
+)
+
+type devCfg struct {
+ // Listeners
+ webListenerAddr string
+ nodeRPCListenerAddr string
+ nodeP2PListenerAddr string
+ nodeProxyAppListenerAddr string
+
+ // Users default
+ genesisCreator string
+ home string
+ root string
+ additionalUsers varAccounts
+ balancesFile string
+
+ // Node Configuration
+ minimal bool
+ verbose bool
+ hotreload bool
+ noWatch bool
+ noReplay bool
+ maxGas int64
+ chainId string
+ serverMode bool
+}
+
+var defaultDevOptions = &devCfg{
+ chainId: "dev",
+ maxGas: 10_000_000_000,
+ webListenerAddr: "127.0.0.1:8888",
+ nodeRPCListenerAddr: "127.0.0.1:36657",
+ genesisCreator: DefaultCreatorAddress.String(),
+ home: gnoenv.HomeDir(),
+ root: gnoenv.RootDir(),
+
+ // As we have no reason to configure this yet, set this to random port
+ // to avoid potential conflict with other app
+ nodeP2PListenerAddr: "tcp://127.0.0.1:0",
+ nodeProxyAppListenerAddr: "tcp://127.0.0.1:0",
+}
+
+func main() {
+ cfg := &devCfg{}
+
+ stdio := commands.NewDefaultIO()
+ cmd := commands.NewCommand(
+ commands.Metadata{
+ Name: "gnodev",
+ ShortUsage: "gnodev [flags] [path ...]",
+ ShortHelp: "runs an in-memory node and gno.land web server for development purposes.",
+ LongHelp: `The gnodev command starts an in-memory node and a gno.land web interface
+primarily for realm package development. It automatically loads the 'examples' directory and any
+additional specified paths.`,
+ },
+ cfg,
+ func(_ context.Context, args []string) error {
+ return execDev(cfg, args, stdio)
+ })
+
+ cmd.Execute(context.Background(), os.Args[1:])
+}
+
+func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
+ fs.StringVar(
+ &c.home,
+ "home",
+ defaultDevOptions.home,
+ "user's local directory for keys",
+ )
+
+ fs.StringVar(
+ &c.root,
+ "root",
+ defaultDevOptions.root,
+ "gno root directory",
+ )
+
+ fs.StringVar(
+ &c.webListenerAddr,
+ "web-listener",
+ defaultDevOptions.webListenerAddr,
+ "web server listening address",
+ )
+
+ fs.StringVar(
+ &c.nodeRPCListenerAddr,
+ "node-rpc-listener",
+ defaultDevOptions.nodeRPCListenerAddr,
+ "listening address for GnoLand RPC node",
+ )
+
+ fs.Var(
+ &c.additionalUsers,
+ "add-user",
+ "pre-add a user",
+ )
+
+ fs.StringVar(
+ &c.genesisCreator,
+ "genesis-creator",
+ defaultDevOptions.genesisCreator,
+ "name or bech32 address of the genesis creator",
+ )
+
+ fs.BoolVar(
+ &c.minimal,
+ "minimal",
+ defaultDevOptions.minimal,
+ "do not load packages from the examples directory",
+ )
+
+ fs.BoolVar(
+ &c.serverMode,
+ "server-mode",
+ defaultDevOptions.serverMode,
+ "disable interaction, and adjust logging for server use.",
+ )
+
+ fs.BoolVar(
+ &c.verbose,
+ "verbose",
+ defaultDevOptions.verbose,
+ "enable verbose output for development",
+ )
+
+ fs.StringVar(
+ &c.chainId,
+ "chain-id",
+ defaultDevOptions.chainId,
+ "set node ChainID",
+ )
+
+ fs.BoolVar(
+ &c.noWatch,
+ "no-watch",
+ defaultDevOptions.noWatch,
+ "do not watch for file changes",
+ )
+
+ fs.BoolVar(
+ &c.noReplay,
+ "no-replay",
+ defaultDevOptions.noReplay,
+ "do not replay previous transactions upon reload",
+ )
+
+ fs.Int64Var(
+ &c.maxGas,
+ "max-gas",
+ defaultDevOptions.maxGas,
+ "set the maximum gas per block",
+ )
+}
+
+func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
+ ctx, cancel := context.WithCancelCause(context.Background())
+ defer cancel(nil)
+
+ // Setup Raw Terminal
+ rt, restore, err := setupRawTerm(cfg, io)
+ if err != nil {
+ return fmt.Errorf("unable to init raw term: %w", err)
+ }
+ defer restore()
+
+ // Setup trap signal
+ osm.TrapSignal(func() {
+ cancel(nil)
+ restore()
+ })
+
+ logger := setuplogger(cfg, rt)
+ loggerEvents := logger.WithGroup(EventServerLogName)
+ emitterServer := emitter.NewServer(loggerEvents)
+
+ // load keybase
+ kb, err := setupKeybase(logger, cfg)
+ if err != nil {
+ return fmt.Errorf("unable to load keybase: %w", err)
+ }
+
+ // Check and Parse packages
+ pkgpaths, err := resolvePackagesPathFromArgs(cfg, kb, args)
+ if err != nil {
+ return fmt.Errorf("unable to parse package paths: %w", err)
+ }
+
+ // Setup Dev Node
+ // XXX: find a good way to export or display node logs
+ nodeLogger := logger.WithGroup(NodeLogName)
+ devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, kb, pkgpaths)
+ if err != nil {
+ return err
+ }
+ defer devNode.Close()
+
+ nodeLogger.Info("node started", "lisn", devNode.GetRemoteAddress(), "chainID", cfg.chainId)
+
+ // Create server
+ mux := http.NewServeMux()
+ server := http.Server{
+ Handler: mux,
+ Addr: cfg.webListenerAddr,
+ }
+ defer server.Close()
+
+ // Setup gnoweb
+ webhandler := setupGnoWebServer(logger.WithGroup(WebLogName), cfg, devNode)
+
+ // Setup HotReload if needed
+ if !cfg.noWatch {
+ evtstarget := fmt.Sprintf("%s/_events", server.Addr)
+ mux.Handle("/_events", emitterServer)
+ mux.Handle("/", emitter.NewMiddleware(evtstarget, webhandler))
+ } else {
+ mux.Handle("/", webhandler)
+ }
+
+ go func() {
+ err := server.ListenAndServe()
+ cancel(err)
+ }()
+
+ logger.WithGroup(WebLogName).
+ Info("gnoweb started",
+ "lisn", fmt.Sprintf("http://%s", server.Addr))
+
+ watcher, err := watcher.NewPackageWatcher(loggerEvents, emitterServer)
+ if err != nil {
+ return fmt.Errorf("unable to setup packages watcher: %w", err)
+ }
+ defer watcher.Stop()
+
+ // Add node pkgs to watcher
+ watcher.AddPackages(devNode.ListPkgs()...)
+
+ if !cfg.serverMode {
+ logger.WithGroup("--- READY").Info("for commands and help, press `h`")
+ }
+
+ // Run the main event loop
+ return runEventLoop(ctx, logger, kb, rt, devNode, watcher)
+}
+
+var helper string = `
+A Accounts - Display known accounts
+H Help - Display this message
+R Reload - Reload all packages to take change into account.
+Ctrl+R Reset - Reset application state.
+Ctrl+C Exit - Exit the application
+`
+
+func runEventLoop(
+ ctx context.Context,
+ logger *slog.Logger,
+ kb keys.Keybase,
+ rt *rawterm.RawTerm,
+ dnode *dev.Node,
+ watch *watcher.PackageWatcher,
+) error {
+
+ keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt)
+ for {
+ var err error
+
+ select {
+ case <-ctx.Done():
+ return context.Cause(ctx)
+ case pkgs, ok := <-watch.PackagesUpdate:
+ if !ok {
+ return nil
+ }
+
+ // fmt.Fprintln(nodeOut, "Loading package updates...")
+ if err = dnode.UpdatePackages(pkgs.PackagesPath()...); err != nil {
+ return fmt.Errorf("unable to update packages: %w", err)
+ }
+
+ logger.WithGroup(NodeLogName).Info("reloading...")
+ if err = dnode.Reload(ctx); err != nil {
+ logger.WithGroup(NodeLogName).
+ Error("unable to reload node", "err", err)
+ }
+
+ case key, ok := <-keyPressCh:
+ if !ok {
+ return nil
+ }
+
+ logger.WithGroup(KeyPressLogName).Debug(
+ fmt.Sprintf("<%s>", key.String()),
+ )
+
+ switch key.Upper() {
+ case rawterm.KeyH: // Helper
+ logger.Info("Gno Dev Helper", "helper", helper)
+ case rawterm.KeyA: // Accounts
+ logAccounts(logger.WithGroup("accounts"), kb, dnode)
+ case rawterm.KeyR: // Reload
+ logger.WithGroup(NodeLogName).Info("reloading...")
+ if err = dnode.ReloadAll(ctx); err != nil {
+ logger.WithGroup(NodeLogName).
+ Error("unable to reload node", "err", err)
+
+ }
+
+ case rawterm.KeyCtrlR: // Reset
+ logger.WithGroup(NodeLogName).Info("reseting node state...")
+ if err = dnode.Reset(ctx); err != nil {
+ logger.WithGroup(NodeLogName).
+ Error("unable to reset node state", "err", err)
+ }
+
+ case rawterm.KeyCtrlC: // Exit
+ return nil
+ default:
+ }
+
+ // Reset listen for the next keypress
+ keyPressCh = listenForKeyPress(logger.WithGroup(KeyPressLogName), rt)
+ }
+ }
+}
+
+func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
+ cc := make(chan rawterm.KeyPress, 1)
+ go func() {
+ defer close(cc)
+ key, err := rt.ReadKeyPress()
+ if err != nil {
+ logger.Error("unable to read keypress", "err", err)
+ return
+ }
+
+ cc <- key
+ }()
+
+ return cc
+}
+
+func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]dev.PackagePath, error) {
+ paths := make([]dev.PackagePath, len(args))
+
+ if cfg.genesisCreator == "" {
+ return nil, fmt.Errorf("default genesis creator cannot be empty")
+ }
+
+ defaultKey, err := kb.GetByNameOrAddress(cfg.genesisCreator)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get genesis creator %q: %w", cfg.genesisCreator, err)
+ }
+
+ for i, arg := range args {
+ path, err := dev.ResolvePackagePathQuery(kb, arg)
+ if err != nil {
+ return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err)
+ }
+
+ // Assign a default creator if user haven't specified it.
+ if path.Creator.IsZero() {
+ path.Creator = defaultKey.GetAddress()
+ }
+
+ paths[i] = path
+ }
+
+ // Add examples folder if minimal is set to false
+ if !cfg.minimal {
+ paths = append(paths, gnodev.PackagePath{
+ Path: filepath.Join(cfg.root, "examples"),
+ Creator: defaultKey.GetAddress(),
+ Deposit: nil,
+ })
+ }
+
+ return paths, nil
+}
diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go
new file mode 100644
index 00000000000..dde94c843d2
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/setup_node.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "net"
+ "strings"
+
+ gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+)
+
+// setupDevNode initializes and returns a new DevNode.
+func setupDevNode(
+ ctx context.Context,
+ logger *slog.Logger,
+ cfg *devCfg,
+ remitter emitter.Emitter,
+ kb keys.Keybase,
+ pkgspath []gnodev.PackagePath,
+) (*gnodev.Node, error) {
+ balances, err := generateBalances(kb, cfg)
+ if err != nil {
+ return nil, fmt.Errorf("unable to generate balances: %w", err)
+ }
+ logger.Debug("balances loaded", "list", balances.List())
+
+ config := setupDevNodeConfig(cfg, balances, pkgspath)
+ return gnodev.NewDevNode(ctx, logger, remitter, config)
+}
+
+// setupDevNodeConfig creates and returns a new dev.NodeConfig.
+func setupDevNodeConfig(cfg *devCfg, balances gnoland.Balances, pkgspath []gnodev.PackagePath) *gnodev.NodeConfig {
+ config := gnodev.DefaultNodeConfig(cfg.root)
+ config.BalancesList = balances.List()
+ config.PackagesPathList = pkgspath
+ config.TMConfig.RPC.ListenAddress = resolveUnixOrTCPAddr(cfg.nodeRPCListenerAddr)
+ config.NoReplay = cfg.noReplay
+ config.MaxGasPerBlock = cfg.maxGas
+ config.ChainID = cfg.chainId
+
+ // other listeners
+ config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr
+ config.TMConfig.ProxyApp = defaultDevOptions.nodeProxyAppListenerAddr
+
+ return config
+}
+
+func resolveUnixOrTCPAddr(in string) (out string) {
+ var err error
+ var addr net.Addr
+
+ if strings.HasPrefix(in, "unix://") {
+ in = strings.TrimPrefix(in, "unix://")
+ if addr, err := net.ResolveUnixAddr("unix", in); err == nil {
+ return fmt.Sprintf("%s://%s", addr.Network(), addr.String())
+ }
+
+ err = fmt.Errorf("unable to resolve unix address `unix://%s`: %w", in, err)
+ } else { // don't bother to checking prefix
+ in = strings.TrimPrefix(in, "tcp://")
+ if addr, err = net.ResolveTCPAddr("tcp", in); err == nil {
+ return fmt.Sprintf("%s://%s", addr.Network(), addr.String())
+ }
+
+ err = fmt.Errorf("unable to resolve tcp address `tcp://%s`: %w", in, err)
+ }
+
+ panic(err)
+}
diff --git a/contribs/gnodev/cmd/gnodev/setup_term.go b/contribs/gnodev/cmd/gnodev/setup_term.go
new file mode 100644
index 00000000000..1f8f3046969
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/setup_term.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
+ "github.com/gnolang/gno/tm2/pkg/commands"
+)
+
+var noopRestore = func() error { return nil }
+
+func setupRawTerm(cfg *devCfg, io commands.IO) (*rawterm.RawTerm, func() error, error) {
+ rt := rawterm.NewRawTerm()
+ restore := noopRestore
+ if !cfg.serverMode {
+ var err error
+ restore, err = rt.Init()
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // correctly format output for terminal
+ io.SetOut(commands.WriteNopCloser(rt))
+ return rt, restore, nil
+}
diff --git a/contribs/gnodev/cmd/gnodev/setup_web.go b/contribs/gnodev/cmd/gnodev/setup_web.go
new file mode 100644
index 00000000000..17df502c3d8
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/setup_web.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "log/slog"
+ "net/http"
+
+ gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ "github.com/gnolang/gno/gno.land/pkg/gnoweb"
+)
+
+// setupGnowebServer initializes and starts the Gnoweb server.
+func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) http.Handler {
+ webConfig := gnoweb.NewDefaultConfig()
+ webConfig.RemoteAddr = dnode.GetRemoteAddress()
+ webConfig.HelpRemote = dnode.GetRemoteAddress()
+ webConfig.HelpChainID = cfg.chainId
+
+ app := gnoweb.MakeApp(logger, webConfig)
+ return app.Router
+}
diff --git a/contribs/gnodev/main.go b/contribs/gnodev/main.go
deleted file mode 100644
index f024f4e0bae..00000000000
--- a/contribs/gnodev/main.go
+++ /dev/null
@@ -1,738 +0,0 @@
-package main
-
-import (
- "context"
- "flag"
- "fmt"
- "io"
- "log/slog"
- "net"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/charmbracelet/lipgloss"
- "github.com/fsnotify/fsnotify"
- "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
- gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
- "github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
- "github.com/gnolang/gno/contribs/gnodev/pkg/logger"
- "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
- "github.com/gnolang/gno/contribs/gnodev/pkg/watcher"
- "github.com/gnolang/gno/gno.land/pkg/balances"
- "github.com/gnolang/gno/gno.land/pkg/gnoland"
- "github.com/gnolang/gno/gno.land/pkg/gnoweb"
- "github.com/gnolang/gno/gno.land/pkg/integration"
- gnolog "github.com/gnolang/gno/gno.land/pkg/log"
- "github.com/gnolang/gno/gnovm/pkg/gnoenv"
- "github.com/gnolang/gno/gnovm/pkg/gnomod"
- "github.com/gnolang/gno/tm2/pkg/amino"
- "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
- "github.com/gnolang/gno/tm2/pkg/commands"
- "github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
- osm "github.com/gnolang/gno/tm2/pkg/os"
- "github.com/gnolang/gno/tm2/pkg/std"
- "github.com/muesli/termenv"
-)
-
-const (
- NodeLogName = "Node"
- WebLogName = "GnoWeb"
- KeyPressLogName = "KeyPress"
- EventServerLogName = "Event"
-)
-
-var (
- DefaultCreatorName = integration.DefaultAccount_Name
- DefaultCreatorAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address)
- DefaultCreatorSeed = integration.DefaultAccount_Seed
- DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
-)
-
-type devCfg struct {
- // Listeners
- webListenerAddr string
- nodeRPCListenerAddr string
- nodeP2PListenerAddr string
- nodeProxyAppListenerAddr string
-
- // Users default
- genesisCreator string
- home string
- root string
- additionalUsers varAccounts
-
- // Node Configuration
- minimal bool
- verbose bool
- hotreload bool
- noWatch bool
- noReplay bool
- maxGas int64
- chainId string
- serverMode bool
- balancesFile string
-}
-
-var defaultDevOptions = &devCfg{
- chainId: "dev",
- maxGas: 10_000_000_000,
- webListenerAddr: "127.0.0.1:8888",
- nodeRPCListenerAddr: "127.0.0.1:36657",
- genesisCreator: DefaultCreatorAddress.String(),
- home: gnoenv.HomeDir(),
- root: gnoenv.RootDir(),
-
- // As we have no reason to configure this yet, set this to random port
- // to avoid potential conflict with other app
- nodeP2PListenerAddr: "tcp://127.0.0.1:0",
- nodeProxyAppListenerAddr: "tcp://127.0.0.1:0",
-}
-
-func main() {
- cfg := &devCfg{}
-
- stdio := commands.NewDefaultIO()
- cmd := commands.NewCommand(
- commands.Metadata{
- Name: "gnodev",
- ShortUsage: "gnodev [flags] [path ...]",
- ShortHelp: "runs an in-memory node and gno.land web server for development purposes.",
- LongHelp: `The gnodev command starts an in-memory node and a gno.land web interface
-primarily for realm package development. It automatically loads the 'examples' directory and any
-additional specified paths.`,
- },
- cfg,
- func(_ context.Context, args []string) error {
- return execDev(cfg, args, stdio)
- })
-
- cmd.Execute(context.Background(), os.Args[1:])
-}
-
-func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
- fs.StringVar(
- &c.home,
- "home",
- defaultDevOptions.home,
- "user's local directory for keys",
- )
-
- fs.StringVar(
- &c.root,
- "root",
- defaultDevOptions.root,
- "gno root directory",
- )
-
- fs.StringVar(
- &c.webListenerAddr,
- "web-listener",
- defaultDevOptions.webListenerAddr,
- "web server listening address",
- )
-
- fs.StringVar(
- &c.nodeRPCListenerAddr,
- "node-rpc-listener",
- defaultDevOptions.nodeRPCListenerAddr,
- "listening address for GnoLand RPC node",
- )
-
- fs.Var(
- &c.additionalUsers,
- "add-user",
- "pre-add a user",
- )
-
- fs.StringVar(
- &c.genesisCreator,
- "genesis-creator",
- defaultDevOptions.genesisCreator,
- "name or bech32 address of the genesis creator",
- )
-
- fs.BoolVar(
- &c.minimal,
- "minimal",
- defaultDevOptions.minimal,
- "do not load packages from the examples directory",
- )
-
- fs.BoolVar(
- &c.serverMode,
- "server-mode",
- defaultDevOptions.serverMode,
- "disable interaction, and adjust logging for server use.",
- )
-
- fs.BoolVar(
- &c.verbose,
- "verbose",
- defaultDevOptions.verbose,
- "enable verbose output for development",
- )
-
- fs.StringVar(
- &c.chainId,
- "chain-id",
- defaultDevOptions.chainId,
- "set node ChainID",
- )
-
- fs.BoolVar(
- &c.noWatch,
- "no-watch",
- defaultDevOptions.noWatch,
- "do not watch for file changes",
- )
-
- fs.BoolVar(
- &c.noReplay,
- "no-replay",
- defaultDevOptions.noReplay,
- "do not replay previous transactions upon reload",
- )
-
- fs.Int64Var(
- &c.maxGas,
- "max-gas",
- defaultDevOptions.maxGas,
- "set the maximum gas per block",
- )
-}
-
-func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
- ctx, cancel := context.WithCancelCause(context.Background())
- defer cancel(nil)
-
- // Setup Raw Terminal
- rt, restore, err := setupRawTerm(cfg, io)
- if err != nil {
- return fmt.Errorf("unable to init raw term: %w", err)
- }
- defer restore()
-
- // Setup trap signal
- osm.TrapSignal(func() {
- cancel(nil)
- restore()
- })
-
- logger := setuplogger(cfg, rt)
- loggerEvents := logger.WithGroup(EventServerLogName)
- emitterServer := emitter.NewServer(loggerEvents)
-
- // load keybase
- kb, err := setupKeybase(cfg, logger)
- if err != nil {
- return fmt.Errorf("unable to load keybase: %w", err)
- }
-
- // Check and Parse packages
- pkgpaths, err := resolvePackagesPathFromArgs(cfg, kb, args)
- if err != nil {
- return fmt.Errorf("unable to parse package paths: %w", err)
- }
-
- // Setup Dev Node
- // XXX: find a good way to export or display node logs
- nodeLogger := logger.WithGroup(NodeLogName)
- devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, kb, pkgpaths)
- if err != nil {
- return err
- }
- defer devNode.Close()
-
- nodeLogger.Info("node started", "lisn", devNode.GetRemoteAddress(), "chainID", cfg.chainId)
-
- // Create server
- mux := http.NewServeMux()
- server := http.Server{
- Handler: mux,
- Addr: cfg.webListenerAddr,
- }
- defer server.Close()
-
- // Setup gnoweb
- webhandler := setupGnoWebServer(logger.WithGroup(WebLogName), cfg, devNode)
-
- // Setup HotReload if needed
- if !cfg.noWatch {
- evtstarget := fmt.Sprintf("%s/_events", server.Addr)
- mux.Handle("/_events", emitterServer)
- mux.Handle("/", emitter.NewMiddleware(evtstarget, webhandler))
- } else {
- mux.Handle("/", webhandler)
- }
-
- go func() {
- err := server.ListenAndServe()
- cancel(err)
- }()
-
- logger.WithGroup(WebLogName).
- Info("gnoweb started",
- "lisn", fmt.Sprintf("http://%s", server.Addr))
-
- watcher, err := watcher.NewPackageWatcher(loggerEvents, emitterServer)
- if err != nil {
- return fmt.Errorf("unable to setup packages watcher: %w", err)
- }
- defer watcher.Stop()
-
- // Add node pkgs to watcher
- watcher.AddPackages(devNode.ListPkgs()...)
-
- if !cfg.serverMode {
- logger.WithGroup("--- READY").Info("for commands and help, press `h`")
- }
-
- // Run the main event loop
- return runEventLoop(ctx, logger, kb, rt, devNode, watcher)
-}
-
-var helper string = `
-A Accounts - Display known accounts
-H Help - Display this message
-R Reload - Reload all packages to take change into account.
-Ctrl+R Reset - Reset application state.
-Ctrl+C Exit - Exit the application
-`
-
-func runEventLoop(
- ctx context.Context,
- logger *slog.Logger,
- kb keys.Keybase,
- rt *rawterm.RawTerm,
- dnode *dev.Node,
- watch *watcher.PackageWatcher,
-) error {
-
- keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt)
- for {
- var err error
-
- select {
- case <-ctx.Done():
- return context.Cause(ctx)
- case pkgs, ok := <-watch.PackagesUpdate:
- if !ok {
- return nil
- }
-
- // fmt.Fprintln(nodeOut, "Loading package updates...")
- if err = dnode.UpdatePackages(pkgs.PackagesPath()...); err != nil {
- return fmt.Errorf("unable to update packages: %w", err)
- }
-
- logger.WithGroup(NodeLogName).Info("reloading...")
- if err = dnode.Reload(ctx); err != nil {
- logger.WithGroup(NodeLogName).
- Error("unable to reload node", "err", err)
- }
-
- case key, ok := <-keyPressCh:
- if !ok {
- return nil
- }
-
- logger.WithGroup(KeyPressLogName).Debug(
- fmt.Sprintf("<%s>", key.String()),
- )
-
- switch key.Upper() {
- case rawterm.KeyH: // Helper
- logger.Info("Gno Dev Helper", "helper", helper)
- case rawterm.KeyA: // Accounts
- logAccounts(logger.WithGroup("accounts"), kb, dnode)
- case rawterm.KeyR: // Reload
- logger.WithGroup(NodeLogName).Info("reloading...")
- if err = dnode.ReloadAll(ctx); err != nil {
- logger.WithGroup(NodeLogName).
- Error("unable to reload node", "err", err)
-
- }
-
- case rawterm.KeyCtrlR: // Reset
- logger.WithGroup(NodeLogName).Info("reseting node state...")
- if err = dnode.Reset(ctx); err != nil {
- logger.WithGroup(NodeLogName).
- Error("unable to reset node state", "err", err)
- }
-
- case rawterm.KeyCtrlC: // Exit
- return nil
- default:
- }
-
- // Reset listen for the next keypress
- keyPressCh = listenForKeyPress(logger.WithGroup(KeyPressLogName), rt)
- }
- }
-}
-
-func runPkgsWatcher(ctx context.Context, cfg *devCfg, pkgs []gnomod.Pkg, changedPathsCh chan<- []string) error {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- return fmt.Errorf("unable to watch files: %w", err)
- }
-
- if cfg.noWatch {
- // Noop watcher, wait until context has been cancel
- <-ctx.Done()
- return ctx.Err()
- }
-
- for _, pkg := range pkgs {
- if err := watcher.Add(pkg.Dir); err != nil {
- return fmt.Errorf("unable to watch %q: %w", pkg.Dir, err)
- }
- }
-
- const timeout = time.Millisecond * 500
-
- var debounceTimer <-chan time.Time
- pathList := []string{}
-
- for {
- select {
- case <-ctx.Done():
- return ctx.Err()
- case watchErr := <-watcher.Errors:
- return fmt.Errorf("watch error: %w", watchErr)
- case <-debounceTimer:
- changedPathsCh <- pathList
- // Reset pathList and debounceTimer
- pathList = []string{}
- debounceTimer = nil
- case evt := <-watcher.Events:
- if evt.Op != fsnotify.Write {
- continue
- }
-
- pathList = append(pathList, evt.Name)
- debounceTimer = time.After(timeout)
- }
- }
-}
-
-var noopRestore = func() error { return nil }
-
-func setupRawTerm(cfg *devCfg, io commands.IO) (*rawterm.RawTerm, func() error, error) {
- rt := rawterm.NewRawTerm()
- restore := noopRestore
- if !cfg.serverMode {
- var err error
- restore, err = rt.Init()
- if err != nil {
- return nil, nil, err
- }
- }
-
- // correctly format output for terminal
- io.SetOut(commands.WriteNopCloser(rt))
- return rt, restore, nil
-}
-
-// setupDevNode initializes and returns a new DevNode.
-func setupDevNode(
- ctx context.Context,
- logger *slog.Logger,
- cfg *devCfg,
- remitter emitter.Emitter,
- kb keys.Keybase,
- pkgspath []dev.PackagePath,
-) (*gnodev.Node, error) {
- balances, err := generateBalances(kb, cfg)
- if err != nil {
- return nil, fmt.Errorf("unable to generate balances: %w", err)
- }
- logger.Debug("balances loaded", "list", balances.List())
-
- // configure gnoland node
- config := gnodev.DefaultNodeConfig(cfg.root)
- config.BalancesList = balances.List()
- config.PackagesPathList = pkgspath
- config.TMConfig.RPC.ListenAddress = resolveUnixOrTCPAddr(cfg.nodeRPCListenerAddr)
- config.NoReplay = cfg.noReplay
- config.MaxGasPerBlock = cfg.maxGas
- config.ChainID = cfg.chainId
-
- // other listeners
- config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr
- config.TMConfig.ProxyApp = defaultDevOptions.nodeProxyAppListenerAddr
-
- return gnodev.NewDevNode(ctx, logger, remitter, config)
-}
-
-// setupGnowebServer initializes and starts the Gnoweb server.
-func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) http.Handler {
- webConfig := gnoweb.NewDefaultConfig()
- webConfig.RemoteAddr = dnode.GetRemoteAddress()
- webConfig.HelpRemote = dnode.GetRemoteAddress()
- webConfig.HelpChainID = cfg.chainId
-
- app := gnoweb.MakeApp(logger, webConfig)
- return app.Router
-}
-
-func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]dev.PackagePath, error) {
- paths := make([]dev.PackagePath, len(args))
-
- if cfg.genesisCreator == "" {
- return nil, fmt.Errorf("default genesis creator cannot be empty")
- }
-
- defaultKey, err := kb.GetByNameOrAddress(cfg.genesisCreator)
- if err != nil {
- return nil, fmt.Errorf("unable to get genesis creator %q: %w", cfg.genesisCreator, err)
- }
-
- for i, arg := range args {
- path, err := dev.ResolvePackagePathQuery(kb, arg)
- if err != nil {
- return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err)
- }
-
- // Assign a default creator if user haven't specified it.
- if path.Creator.IsZero() {
- path.Creator = defaultKey.GetAddress()
- }
-
- paths[i] = path
- }
-
- // Add examples folder if minimal is set to false
- if !cfg.minimal {
- paths = append(paths, gnodev.PackagePath{
- Path: filepath.Join(cfg.root, "examples"),
- Creator: defaultKey.GetAddress(),
- Deposit: nil,
- })
- }
-
- return paths, nil
-}
-
-func generateBalances(kb keys.Keybase, cfg *devCfg) (balances.Balances, error) {
- bls := balances.New()
- unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
-
- keys, err := kb.List()
- if err != nil {
- return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
- }
-
- // Automatically set every key from keybase to unlimited found (or pre
- // defined found if specified)
- for _, key := range keys {
- found := unlimitedFund
- if preDefinedFound, ok := cfg.additionalUsers[key.GetName()]; ok && preDefinedFound != nil {
- found = preDefinedFound
- }
-
- address := key.GetAddress()
- bls[address] = gnoland.Balance{Amount: found, Address: address}
- }
-
- if cfg.balancesFile == "" {
- return bls, nil
- }
-
- file, err := os.Open(cfg.balancesFile)
- if err != nil {
- return nil, fmt.Errorf("unable to open balance file %q: %w", cfg.balancesFile, err)
- }
-
- blsFile, err := balances.GetBalancesFromSheet(file)
- if err != nil {
- return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
- }
-
- // Left merge keybase balance into loaded file balance
- blsFile.LeftMerge(bls)
- return blsFile, nil
-}
-
-func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
- cc := make(chan rawterm.KeyPress, 1)
- go func() {
- defer close(cc)
- key, err := rt.ReadKeyPress()
- if err != nil {
- logger.Error("unable to read keypress", "err", err)
- return
- }
-
- cc <- key
- }()
-
- return cc
-}
-
-// createAccount creates a new account with the given name and adds it to the keybase.
-func createAccount(kb keys.Keybase, accountName string) (keys.Info, error) {
- entropy, err := bip39.NewEntropy(256)
- if err != nil {
- return nil, fmt.Errorf("error creating entropy: %w", err)
- }
-
- mnemonic, err := bip39.NewMnemonic(entropy)
- if err != nil {
- return nil, fmt.Errorf("error generating mnemonic: %w", err)
- }
-
- return kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
-}
-
-func resolveUnixOrTCPAddr(in string) (out string) {
- var err error
- var addr net.Addr
-
- if strings.HasPrefix(in, "unix://") {
- in = strings.TrimPrefix(in, "unix://")
- if addr, err := net.ResolveUnixAddr("unix", in); err == nil {
- return fmt.Sprintf("%s://%s", addr.Network(), addr.String())
- }
-
- err = fmt.Errorf("unable to resolve unix address `unix://%s`: %w", in, err)
- } else { // don't bother to checking prefix
- in = strings.TrimPrefix(in, "tcp://")
- if addr, err = net.ResolveTCPAddr("tcp", in); err == nil {
- return fmt.Sprintf("%s://%s", addr.Network(), addr.String())
- }
-
- err = fmt.Errorf("unable to resolve tcp address `tcp://%s`: %w", in, err)
- }
-
- panic(err)
-}
-
-func setupKeybase(cfg *devCfg, logger *slog.Logger) (keys.Keybase, error) {
- kb := keys.NewInMemory()
- if cfg.home != "" {
- // load home keybase into our inMemory keybase
- kbHome, err := keys.NewKeyBaseFromDir(cfg.home)
- if err != nil {
- return nil, fmt.Errorf("unable to load keybae from dir %q: %w", cfg.home, err)
- }
-
- keys, err := kbHome.List()
- if err != nil {
- return nil, fmt.Errorf("unable to list keys from keybase %q: %w", cfg.home, err)
- }
-
- for _, key := range keys {
- name := key.GetName()
- armor, err := kbHome.Export(key.GetName())
- if err != nil {
- return nil, fmt.Errorf("unable to export key %q: %w", name, err)
- }
-
- if err := kb.Import(name, armor); err != nil {
- return nil, fmt.Errorf("unable to import key %q: %w", name, err)
- }
- }
- }
-
- // Add additional users to our keybase
- for user := range cfg.additionalUsers {
- info, err := createAccount(kb, user)
- if err != nil {
- return nil, fmt.Errorf("unable to create user %q: %w", user, err)
- }
-
- logger.Info("additional user", "name", info.GetName(), "addr", info.GetAddress())
- }
-
- // Next, make sure that we have a default address to load packages
- var info keys.Info
- var err error
-
- info, err = kb.GetByNameOrAddress(cfg.genesisCreator)
- switch {
- case err == nil: // user already have a default user
- case keyerror.IsErrKeyNotFound(err):
- // if the key isn't found, create a default one
- creatorName := fmt.Sprintf("_default#%.10s", DefaultCreatorAddress.String())
- if ok, _ := kb.HasByName(creatorName); ok {
- // if a collision happen here, someone really want to not run.
- return nil, fmt.Errorf("unable to create creator account, delete %q from your keybase", creatorName)
- }
-
- info, err = kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0)
- if err != nil {
- return nil, fmt.Errorf("unable to create default %q account: %w", DefaultCreatorName, err)
- }
- default:
- return nil, fmt.Errorf("unable to get address %q from keybase: %w", info.GetAddress(), err)
- }
-
- logger.Info("default creator", "name", info.GetName(), "addr", info.GetAddress())
- return kb, nil
-}
-
-func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
- keys, err := kb.List()
- if err != nil {
- return fmt.Errorf("unable to get keybase keys list: %w", err)
- }
-
- accounts := make([]string, len(keys))
- for i, key := range keys {
- if key.GetName() == "" {
- continue // skip empty key name
- }
-
- address := key.GetAddress()
- qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address.String(), []byte{})
- if err != nil {
- return fmt.Errorf("unable to querry account %q: %w", address.String(), err)
- }
-
- var qret struct{ BaseAccount std.BaseAccount }
- if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil {
- return fmt.Errorf("unable to unmarshal query response: %w", err)
- }
-
- // format name - (address) -> (coins) -> (acct-num) -> (seq)
- accounts[i] = fmt.Sprintf("%s: addr(%s) coins(%s) acct_num(%d)",
- key.GetName(),
- address.String(),
- qret.BaseAccount.GetCoins().String(),
- qret.BaseAccount.GetAccountNumber())
- }
-
- logger.Info("current accounts", "balances", strings.Join(accounts, "\n"))
- return nil
-}
-
-func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger {
- level := slog.LevelInfo
- if cfg.verbose {
- level = slog.LevelDebug
- }
-
- if cfg.serverMode {
- zaplogger := logger.NewZapLogger(out, level)
- return gnolog.ZapLoggerToSlog(zaplogger)
- }
-
- // Detect term color profile
- colorProfile := termenv.DefaultOutput().Profile
- clogger := logger.NewColumnLogger(out, level, colorProfile)
-
- // Register well known group color with system colors
- clogger.RegisterGroupColor(NodeLogName, lipgloss.Color("3"))
- clogger.RegisterGroupColor(WebLogName, lipgloss.Color("4"))
- clogger.RegisterGroupColor(KeyPressLogName, lipgloss.Color("5"))
- clogger.RegisterGroupColor(EventServerLogName, lipgloss.Color("6"))
-
- return slog.New(clogger)
-}
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index fae635ed5b7..31dd4112dce 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -12,13 +12,15 @@ local instance of `gnoweb`, allowing you to see the rendering of your Gno code i
## Features
- **In-Memory Node**: Gnodev starts an in-memory node, and automatically loads
-the **examples** folder and any user-specified paths.
+ the **examples** folder and any user-specified paths.
- **Web Interface Server**: Gnodev automatically starts a `gnoweb` server on
[`localhost:8888`](https://localhost:8888).
-- **Hot Reload**: Gnodev monitors the **examples** folder and any specified for file changes,
-reloading and automatically restarting the node as needed.
+- **Balances and Keybase Customization**: Users can specify balances, load from
+ a balances file or create temporary users.
+- **Hot Reload**: Gnodev monitors the **examples** folder and any specified for
+ file changes, reloading and automatically restarting the node as needed.
- **State Maintenance**: Gnodev replays all transactions in between reloads,
-ensuring the previous node state is preserved.
+ ensuring the previous node state is preserved.
## Installation
Gnodev can be found in the `contribs` folder in the monorepo.
@@ -35,15 +37,45 @@ For hot reloading, `gnodev` watches the examples folder, as well as any specifie
gnodev ./myrealm
```
+## Keybase and Balance
+
+Gnodev will by default, load your keybase located in GNOHOME directory.
+Given all you keys an (almost unlimited found).
+
+All realm will be upload by the `-genesis-creator`, but You can also pass query
+options to realm path load a realm with specific creator and deposit:
+```
+gnodev ./myrealm?creator=foo&deposit=42ugnot``
+```
+
+### Additional User
+Use `-add-user` flag in the format (:) to add temporary users. You can repeat this to add multiple users.
+Addresses of those will be display at runtime, or by pressing `A` interactivly to display accounts.
+
+## Interactive Usage
+
While `gnodev` is running, the following shortcuts are available:
+- To see help, press `H`.
+- To display accounts, press `A`.
- To reload manually, press `R`.
- To reset the state of the node, press `CMD+R`.
-- To see help, press `H`.
- To stop `gnodev`, press `CMD+C`.
### Options
-| Flag | Effect |
-|------------|-----------------------------------------------------|
-| --minimal | Start `gnodev` without loading the examples folder. |
-| --no-watch | Disable hot reload. |
\ No newline at end of file
+| Flag | Effect |
+|---------------------|---------------------------------------------------------|
+| --minimal | Start `gnodev` without loading the examples folder. |
+| --no-watch | Disable hot reload. |
+| --add-user | Pre-add user(s) in the form (:) |
+| --balances-file | Load a balance for the user(s) from a balance file. |
+| --chain-id | Set node ChainID |
+| --genesis-creator | Name or bech32 address of the genesis creator |
+| --home | Set the path to load user's Keybase. |
+| --max-gas | Set the maximum gas per block |
+| --no-replay | Do not replay previous transactions upon reload |
+| --node-rpc-listener | listening address for GnoLand RPC node |
+| --root | gno root directory |
+| --server-mode | disable interaction, and adjust logging for server use. |
+| --verbose | enable verbose output for development |
+| --web-listener | web server listening address |
diff --git a/gno.land/pkg/balances/balances.go b/gno.land/pkg/balances/balances.go
deleted file mode 100644
index 0d6f313b00b..00000000000
--- a/gno.land/pkg/balances/balances.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package balances
-
-import (
- "bufio"
- "fmt"
- "io"
- "strings"
-
- "github.com/gnolang/gno/gno.land/pkg/gnoland"
- "github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/std"
-)
-
-type Balances map[crypto.Address]gnoland.Balance
-
-func New() Balances {
- return make(Balances)
-}
-
-func (balances Balances) Set(address crypto.Address, amount std.Coins) {
- balances[address] = gnoland.Balance{
- Address: address,
- Amount: amount,
- }
-}
-
-func (balances Balances) Get(address crypto.Address) (balance gnoland.Balance, ok bool) {
- balance, ok = balances[address]
- return
-}
-
-func (balances Balances) List() []gnoland.Balance {
- list := make([]gnoland.Balance, 0, len(balances))
- for _, balance := range balances {
- list = append(list, balance)
- }
- return list
-}
-
-// leftMerge left-merges the two maps
-func (a Balances) LeftMerge(b Balances) {
- for key, bVal := range b {
- if _, present := (a)[key]; !present {
- (a)[key] = bVal
- }
- }
-}
-
-func GetBalancesFromEntries(entries ...string) (Balances, error) {
- balances := New()
- return balances, balances.LoadFromEntries(entries...)
-}
-
-// LoadFromEntries extracts the balance entries in the form of =
-func (balances Balances) LoadFromEntries(entries ...string) error {
- for _, entry := range entries {
- var balance gnoland.Balance
- if err := balance.Parse(entry); err != nil {
- return fmt.Errorf("unable to parse balance entry: %w", err)
- }
- balances[balance.Address] = balance
- }
-
- return nil
-}
-
-func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
- balances := New()
- return balances, balances.LoadFromSheet(sheet)
-}
-
-// LoadFromSheet extracts the balance sheet from the passed in
-// balance sheet file, that has the format of =ugnot
-func (balances Balances) LoadFromSheet(sheet io.Reader) error {
- // Parse the balances
- scanner := bufio.NewScanner(sheet)
-
- for scanner.Scan() {
- entry := scanner.Text()
-
- // Remove comments
- entry = strings.Split(entry, "#")[0]
- entry = strings.TrimSpace(entry)
-
- // Skip empty lines
- if entry == "" {
- continue
- }
-
- if err := balances.LoadFromEntries(entry); err != nil {
- return fmt.Errorf("unable to load entries: %w", err)
- }
- }
-
- if err := scanner.Err(); err != nil {
- return fmt.Errorf("error encountered while scanning, %w", err)
- }
-
- return nil
-}
diff --git a/gno.land/pkg/balances/balances_test.go b/gno.land/pkg/balances/balances_test.go
deleted file mode 100644
index 7d42f4ea56c..00000000000
--- a/gno.land/pkg/balances/balances_test.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package balances
-
-import (
- "fmt"
- "math"
- "strconv"
- "strings"
- "testing"
-
- "github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
- "github.com/gnolang/gno/tm2/pkg/crypto/hd"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys/client"
- "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
- "github.com/gnolang/gno/tm2/pkg/std"
- "github.com/jaekwon/testify/require"
- "github.com/stretchr/testify/assert"
-)
-
-func TestBalances_GetBalancesFromEntries(t *testing.T) {
- t.Parallel()
-
- t.Run("valid balances", func(t *testing.T) {
- t.Parallel()
-
- // Generate dummy keys
- dummyKeys := getDummyKeys(t, 2)
- amount := std.NewCoins(std.NewCoin("ugnot", 10))
-
- entries := make([]string, len(dummyKeys))
-
- for index, key := range dummyKeys {
- entries[index] = fmt.Sprintf(
- "%s=%dugnot",
- key.Address().String(),
- amount.AmountOf("ugnot"),
- )
- }
-
- balanceMap, err := GetBalancesFromEntries(entries...)
- require.NoError(t, err)
-
- // Validate the balance map
- assert.Len(t, balanceMap, len(dummyKeys))
- for _, key := range dummyKeys {
- assert.Equal(t, amount, balanceMap[key.Address()].Amount)
- }
- })
-
- t.Run("malformed balance, invalid format", func(t *testing.T) {
- t.Parallel()
-
- entries := []string{
- "malformed balance",
- }
-
- balanceMap, err := GetBalancesFromEntries(entries...)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "malformed entry")
- })
-
- t.Run("malformed balance, invalid address", func(t *testing.T) {
- t.Parallel()
-
- balances := []string{
- "dummyaddress=10ugnot",
- }
-
- balanceMap, err := GetBalancesFromEntries(balances...)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid address")
- })
-
- t.Run("malformed balance, invalid amount", func(t *testing.T) {
- t.Parallel()
-
- dummyKey := getDummyKey(t)
-
- balances := []string{
- fmt.Sprintf(
- "%s=%sugnot",
- dummyKey.Address().String(),
- strconv.FormatUint(math.MaxUint64, 10),
- ),
- }
-
- balanceMap, err := GetBalancesFromEntries(balances...)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid amount")
- })
-}
-
-func TestBalances_GetBalancesFromSheet(t *testing.T) {
- t.Parallel()
-
- t.Run("valid balances", func(t *testing.T) {
- t.Parallel()
-
- // Generate dummy keys
- dummyKeys := getDummyKeys(t, 2)
- amount := std.NewCoins(std.NewCoin("ugnot", 10))
-
- balances := make([]string, len(dummyKeys))
-
- for index, key := range dummyKeys {
- balances[index] = fmt.Sprintf(
- "%s=%dugnot",
- key.Address().String(),
- amount.AmountOf("ugnot"),
- )
- }
-
- reader := strings.NewReader(strings.Join(balances, "\n"))
- balanceMap, err := GetBalancesFromSheet(reader)
- require.NoError(t, err)
-
- // Validate the balance map
- assert.Len(t, balanceMap, len(dummyKeys))
- for _, key := range dummyKeys {
- assert.Equal(t, amount, balanceMap[key.Address()].Amount)
- }
- })
-
- t.Run("malformed balance, invalid amount", func(t *testing.T) {
- t.Parallel()
-
- dummyKey := getDummyKey(t)
-
- balances := []string{
- fmt.Sprintf(
- "%s=%sugnot",
- dummyKey.Address().String(),
- strconv.FormatUint(math.MaxUint64, 10),
- ),
- }
-
- reader := strings.NewReader(strings.Join(balances, "\n"))
-
- balanceMap, err := GetBalancesFromSheet(reader)
-
- assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid amount")
- })
-}
-
-// XXX: this function should probably be exposed somewhere as it's duplicate of
-// cmd/genesis/...
-
-// getDummyKey generates a random public key,
-// and returns the key info
-func getDummyKey(t *testing.T) crypto.PubKey {
- t.Helper()
-
- mnemonic, err := client.GenerateMnemonic(256)
- require.NoError(t, err)
-
- seed := bip39.NewSeed(mnemonic, "")
-
- return generateKeyFromSeed(seed, 0).PubKey()
-}
-
-// getDummyKeys generates random keys for testing
-func getDummyKeys(t *testing.T, count int) []crypto.PubKey {
- t.Helper()
-
- dummyKeys := make([]crypto.PubKey, count)
-
- for i := 0; i < count; i++ {
- dummyKeys[i] = getDummyKey(t)
- }
-
- return dummyKeys
-}
-
-// generateKeyFromSeed generates a private key from
-// the provided seed and index
-func generateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey {
- pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index)
-
- masterPriv, ch := hd.ComputeMastersFromSeed(seed)
-
- //nolint:errcheck // This derivation can never error out, since the path params
- // are always going to be valid
- derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String())
-
- return secp256k1.PrivKeySecp256k1(derivedPriv)
-}
diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go
index da6bef97746..d0ac52b40b0 100644
--- a/gno.land/pkg/gnoland/types.go
+++ b/gno.land/pkg/gnoland/types.go
@@ -82,7 +82,7 @@ func (b Balance) String() string {
type Balances map[crypto.Address]Balance
-func New() Balances {
+func NewBalances() Balances {
return make(Balances)
}
@@ -116,7 +116,7 @@ func (a Balances) LeftMerge(b Balances) {
}
func GetBalancesFromEntries(entries ...string) (Balances, error) {
- balances := New()
+ balances := NewBalances()
return balances, balances.LoadFromEntries(entries...)
}
@@ -134,7 +134,7 @@ func (balances Balances) LoadFromEntries(entries ...string) error {
}
func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
- balances := New()
+ balances := NewBalances()
return balances, balances.LoadFromSheet(sheet)
}
From 5bd0b6fd2ee971e3f13e735046b60646b77b95d0 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 16 Apr 2024 16:25:06 +0200
Subject: [PATCH 04/23] fix: update docs
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
docs/gno-tooling/cli/gnodev.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index 31dd4112dce..a0b739e6df6 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -39,18 +39,18 @@ gnodev ./myrealm
## Keybase and Balance
-Gnodev will by default, load your keybase located in GNOHOME directory.
-Given all you keys an (almost unlimited found).
+Gnodev will, by default, load your keybase located in the GNOHOME directory, giving all your keys (almost) unlimited fund.
-All realm will be upload by the `-genesis-creator`, but You can also pass query
-options to realm path load a realm with specific creator and deposit:
+All realms will be by the `-genesis-creator`, but You can also pass query options to the realm path to load a
+realm with specific creator and deposit:
```
gnodev ./myrealm?creator=foo&deposit=42ugnot``
```
### Additional User
-Use `-add-user` flag in the format (:) to add temporary users. You can repeat this to add multiple users.
-Addresses of those will be display at runtime, or by pressing `A` interactivly to display accounts.
+Use `-add-user` flag in the format (:) to add temporary users. You can repeat this to add
+multiple users. Addresses of those will be displayed at runtime, or by pressing `A` interactively to display
+accounts.
## Interactive Usage
From 446fd2f7816f15be4b01ec586f82a2eddd77c12d Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 16 Apr 2024 17:50:41 +0200
Subject: [PATCH 05/23] fix: balances errors
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
gno.land/cmd/genesis/balances_add.go | 17 ++++++++---------
gno.land/cmd/genesis/balances_remove.go | 2 +-
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/gno.land/cmd/genesis/balances_add.go b/gno.land/cmd/genesis/balances_add.go
index 46fde3a7867..1497f4f6065 100644
--- a/gno.land/cmd/genesis/balances_add.go
+++ b/gno.land/cmd/genesis/balances_add.go
@@ -9,7 +9,6 @@ import (
"io"
"os"
- "github.com/gnolang/gno/gno.land/pkg/balances"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/types"
@@ -93,11 +92,11 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
return errNoBalanceSource
}
- finalBalances := balances.New()
+ finalBalances := gnoland.NewBalances()
// Get the balance sheet from the source
if singleEntriesSet {
- balances, err := balances.GetBalancesFromEntries(cfg.singleEntries...)
+ balances, err := gnoland.GetBalancesFromEntries(cfg.singleEntries...)
if err != nil {
return fmt.Errorf("unable to get balances from entries, %w", err)
}
@@ -112,7 +111,7 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e
return fmt.Errorf("unable to open balance sheet, %w", loadErr)
}
- balances, err := balances.GetBalancesFromSheet(file)
+ balances, err := gnoland.GetBalancesFromSheet(file)
if err != nil {
return fmt.Errorf("unable to get balances from balance sheet, %w", err)
}
@@ -184,8 +183,8 @@ func getBalancesFromTransactions(
ctx context.Context,
io commands.IO,
reader io.Reader,
-) (balances.Balances, error) {
- balances := balances.New()
+) (gnoland.Balances, error) {
+ balances := gnoland.NewBalances()
scanner := bufio.NewScanner(reader)
@@ -235,7 +234,7 @@ func getBalancesFromTransactions(
// This way of determining final account balances is not really valid,
// because we take into account only the ugnot transfer messages (MsgSend)
// and not other message types (like MsgCall), that can also
- // initialize accounts with some balances. Because of this,
+ // initialize accounts with some gnoland. Because of this,
// we can run into a situation where a message send amount or fee
// causes an accounts balance to go < 0. In these cases,
// we initialize the account (it is present in the balance sheet), but
@@ -286,9 +285,9 @@ func getBalancesFromTransactions(
// mapGenesisBalancesFromState extracts the initial account balances from the
// genesis app state
-func mapGenesisBalancesFromState(state gnoland.GnoGenesisState) (balances.Balances, error) {
+func mapGenesisBalancesFromState(state gnoland.GnoGenesisState) (gnoland.Balances, error) {
// Construct the initial genesis balance sheet
- genesisBalances := balances.New()
+ genesisBalances := gnoland.NewBalances()
for _, balance := range state.Balances {
genesisBalances[balance.Address] = balance
diff --git a/gno.land/cmd/genesis/balances_remove.go b/gno.land/cmd/genesis/balances_remove.go
index a752bbda4fd..5b6e74e0dcf 100644
--- a/gno.land/cmd/genesis/balances_remove.go
+++ b/gno.land/cmd/genesis/balances_remove.go
@@ -86,7 +86,7 @@ func execBalancesRemove(cfg *balancesRemoveCfg, io commands.IO) error {
delete(genesisBalances, address)
// Save the balances
- state.Balances = genesisBalances.toList()
+ state.Balances = genesisBalances.List()
genesis.AppState = state
// Save the updated genesis
From 80e4f991ffa3fb326eeecce9762fa2334fa14c2b Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Wed, 17 Apr 2024 08:47:26 +0200
Subject: [PATCH 06/23] fix: lint and remove unused
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/Makefile | 7 +-
contribs/gnodev/cmd/gnodev/main.go | 22 ++---
contribs/gnodev/pkg/dev/node.go | 107 +++++++++++-----------
contribs/gnodev/pkg/emitter/middleware.go | 1 -
contribs/gnodev/pkg/logger/colors.go | 2 +-
contribs/gnodev/pkg/logger/log_column.go | 2 +-
contribs/gnodev/pkg/watcher/watch.go | 2 +-
tm2/pkg/crypto/keys/client/export.go | 7 ++
8 files changed, 79 insertions(+), 71 deletions(-)
diff --git a/contribs/gnodev/Makefile b/contribs/gnodev/Makefile
index 7395b166a3e..b98ce0fb44b 100644
--- a/contribs/gnodev/Makefile
+++ b/contribs/gnodev/Makefile
@@ -1,9 +1,14 @@
GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../)
+GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)"
-GOBUILD_FLAGS := -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)"
+rundep := go run -modfile ../../misc/devdeps/go.mod
+golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint
install:
go install $(GOBUILD_FLAGS) ./cmd/gnodev
build:
go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gnodev
+
+lint:
+ $(golangci_lint) --config ../../.github/golangci.yml run ./...
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 648fe78b20f..945f92c9ce6 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -8,8 +8,8 @@ import (
"net/http"
"os"
"path/filepath"
+ "time"
- "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
"github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
"github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
@@ -52,7 +52,6 @@ type devCfg struct {
// Node Configuration
minimal bool
verbose bool
- hotreload bool
noWatch bool
noReplay bool
maxGas int64
@@ -235,8 +234,9 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
// Create server
mux := http.NewServeMux()
server := http.Server{
- Handler: mux,
- Addr: cfg.webListenerAddr,
+ Handler: mux,
+ Addr: cfg.webListenerAddr,
+ ReadHeaderTimeout: time.Minute,
}
defer server.Close()
@@ -291,10 +291,9 @@ func runEventLoop(
logger *slog.Logger,
kb keys.Keybase,
rt *rawterm.RawTerm,
- dnode *dev.Node,
+ dnode *gnodev.Node,
watch *watcher.PackageWatcher,
) error {
-
keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt)
for {
var err error
@@ -337,7 +336,6 @@ func runEventLoop(
if err = dnode.ReloadAll(ctx); err != nil {
logger.WithGroup(NodeLogName).
Error("unable to reload node", "err", err)
-
}
case rawterm.KeyCtrlR: // Reset
@@ -374,8 +372,8 @@ func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.
return cc
}
-func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]dev.PackagePath, error) {
- paths := make([]dev.PackagePath, len(args))
+func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]gnodev.PackagePath, error) {
+ paths := make([]gnodev.PackagePath, 0, len(args))
if cfg.genesisCreator == "" {
return nil, fmt.Errorf("default genesis creator cannot be empty")
@@ -386,8 +384,8 @@ func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([
return nil, fmt.Errorf("unable to get genesis creator %q: %w", cfg.genesisCreator, err)
}
- for i, arg := range args {
- path, err := dev.ResolvePackagePathQuery(kb, arg)
+ for _, arg := range args {
+ path, err := gnodev.ResolvePackagePathQuery(kb, arg)
if err != nil {
return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err)
}
@@ -397,7 +395,7 @@ func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([
path.Creator = defaultKey.GetAddress()
}
- paths[i] = path
+ paths = append(paths, path)
}
// Add examples folder if minimal is set to false
diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go
index 786c9349d0e..971d3010bb1 100644
--- a/contribs/gnodev/pkg/dev/node.go
+++ b/contribs/gnodev/pkg/dev/node.go
@@ -112,39 +112,39 @@ func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitte
return devnode, nil
}
-func (d *Node) getLatestBlockNumber() uint64 {
- return uint64(d.Node.BlockStore().Height())
+func (n *Node) getLatestBlockNumber() uint64 {
+ return uint64(n.Node.BlockStore().Height())
}
-func (d *Node) Close() error {
- return d.Node.Stop()
+func (n *Node) Close() error {
+ return n.Node.Stop()
}
-func (d *Node) ListPkgs() []gnomod.Pkg {
- return d.pkgs.toList()
+func (n *Node) ListPkgs() []gnomod.Pkg {
+ return n.pkgs.toList()
}
-func (d *Node) GetNodeReadiness() <-chan struct{} {
- return gnoland.GetNodeReadiness(d.Node)
+func (n *Node) GetNodeReadiness() <-chan struct{} {
+ return gnoland.GetNodeReadiness(n.Node)
}
-func (d *Node) GetRemoteAddress() string {
- return d.Node.Config().RPC.ListenAddress
+func (n *Node) GetRemoteAddress() string {
+ return n.Node.Config().RPC.ListenAddress
}
// UpdatePackages updates the currently known packages. It will be taken into
// consideration in the next reload of the node.
-func (d *Node) UpdatePackages(paths ...string) error {
- var n int
+func (n *Node) UpdatePackages(paths ...string) error {
+ var i int
for _, path := range paths {
abspath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("unable to resolve abs path of %q: %w", path, err)
}
- creator := d.config.DefaultCreator
+ creator := n.config.DefaultCreator
var deposit std.Coins
- for _, ppath := range d.config.PackagesPathList {
+ for _, ppath := range n.config.PackagesPathList {
if !strings.HasPrefix(abspath, ppath.Path) {
continue
}
@@ -161,117 +161,117 @@ func (d *Node) UpdatePackages(paths ...string) error {
// Update or add package in the current known list.
for _, pkg := range pkgslist {
- d.pkgs[pkg.Dir] = Package{
+ n.pkgs[pkg.Dir] = Package{
Pkg: pkg,
Creator: creator,
Deposit: deposit,
}
- d.logger.Debug("pkgs update", "name", pkg.Name, "path", pkg.Dir)
+ n.logger.Debug("pkgs update", "name", pkg.Name, "path", pkg.Dir)
}
- n += len(pkgslist)
+ i += len(pkgslist)
}
- d.logger.Info(fmt.Sprintf("updated %d pacakges", n))
+ n.logger.Info(fmt.Sprintf("updated %d pacakges", i))
return nil
}
// Reset stops the node, if running, and reloads it with a new genesis state,
// effectively ignoring the current state.
-func (d *Node) Reset(ctx context.Context) error {
+func (n *Node) Reset(ctx context.Context) error {
// Stop the node if it's currently running.
- if err := d.stopIfRunning(); err != nil {
+ if err := n.stopIfRunning(); err != nil {
return fmt.Errorf("unable to stop the node: %w", err)
}
// Generate a new genesis state based on the current packages
- txs, err := d.pkgs.Load(DefaultFee)
+ txs, err := n.pkgs.Load(DefaultFee)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
genesis := gnoland.GnoGenesisState{
- Balances: d.config.BalancesList,
+ Balances: n.config.BalancesList,
Txs: txs,
}
// Reset the node with the new genesis state.
- err = d.reset(ctx, genesis)
+ err = n.reset(ctx, genesis)
if err != nil {
return fmt.Errorf("unable to initialize a new node: %w", err)
}
- d.emitter.Emit(&events.Reset{})
+ n.emitter.Emit(&events.Reset{})
return nil
}
// ReloadAll updates all currently known packages and then reloads the node.
-func (d *Node) ReloadAll(ctx context.Context) error {
- pkgs := d.ListPkgs()
+func (n *Node) ReloadAll(ctx context.Context) error {
+ pkgs := n.ListPkgs()
paths := make([]string, len(pkgs))
for i, pkg := range pkgs {
paths[i] = pkg.Dir
}
- if err := d.UpdatePackages(paths...); err != nil {
+ if err := n.UpdatePackages(paths...); err != nil {
return fmt.Errorf("unable to reload packages: %w", err)
}
- return d.Reload(ctx)
+ return n.Reload(ctx)
}
// Reload saves the current state, stops the node if running, starts a new node,
// and re-apply previously saved state along with packages updated by `UpdatePackages`.
// If any transaction, including 'addpkg', fails, it will be ignored.
// Use 'Reset' to completely reset the node's state in case of persistent errors.
-func (d *Node) Reload(ctx context.Context) error {
- if d.config.NoReplay {
+func (n *Node) Reload(ctx context.Context) error {
+ if n.config.NoReplay {
// If NoReplay is true, reload as the same effect as reset
- d.logger.Warn("replay disable")
- return d.Reset(ctx)
+ n.logger.Warn("replay disable")
+ return n.Reset(ctx)
}
// Get current blockstore state
- state, err := d.getBlockStoreState(ctx)
+ state, err := n.getBlockStoreState(ctx)
if err != nil {
return fmt.Errorf("unable to save state: %s", err.Error())
}
// Stop the node if it's currently running.
- if err := d.stopIfRunning(); err != nil {
+ if err := n.stopIfRunning(); err != nil {
return fmt.Errorf("unable to stop the node: %w", err)
}
// Load genesis packages
- pkgsTxs, err := d.pkgs.Load(DefaultFee)
+ pkgsTxs, err := n.pkgs.Load(DefaultFee)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
// Create genesis with loaded pkgs + previous state
genesis := gnoland.GnoGenesisState{
- Balances: d.config.BalancesList,
+ Balances: n.config.BalancesList,
Txs: append(pkgsTxs, state...),
}
// Reset the node with the new genesis state.
- err = d.reset(ctx, genesis)
- d.logger.Info("reload done", "pkgs", len(pkgsTxs), "state applied", len(state))
+ err = n.reset(ctx, genesis)
+ n.logger.Info("reload done", "pkgs", len(pkgsTxs), "state applied", len(state))
// Update node infos
- d.loadedPackages = len(pkgsTxs)
+ n.loadedPackages = len(pkgsTxs)
- d.emitter.Emit(&events.Reload{})
+ n.emitter.Emit(&events.Reload{})
return nil
}
-func (d *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {
+func (n *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {
if res.IsErr() {
// XXX: for now, this is only way to catch the error
before, after, found := strings.Cut(res.Log, "\n")
if !found {
- d.logger.Error("unable to send tx", "err", res.Error, "log", res.Log)
+ n.logger.Error("unable to send tx", "err", res.Error, "log", res.Log)
return
}
@@ -287,20 +287,19 @@ func (d *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {
attrs = append(attrs, slog.String("err", msg))
// If debug is enable, also append stack
- if d.logger.Enabled(context.Background(), slog.LevelDebug) {
+ if n.logger.Enabled(context.Background(), slog.LevelDebug) {
attrs = append(attrs, slog.String("stack", after))
-
}
- d.logger.LogAttrs(context.Background(), slog.LevelError, "unable to deliver tx", attrs...)
+ n.logger.LogAttrs(context.Background(), slog.LevelError, "unable to deliver tx", attrs...)
}
}
// GetBlockTransactions returns the transactions contained
// within the specified block, if any
-func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) {
+func (n *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) {
int64BlockNum := int64(blockNum)
- b, err := d.client.Block(&int64BlockNum)
+ b, err := n.client.Block(&int64BlockNum)
if err != nil {
return []std.Tx{}, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) // nothing to see here
}
@@ -320,38 +319,38 @@ func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) {
// GetBlockTransactions returns the transactions contained
// within the specified block, if any
-func (d *Node) CurrentBalances(blockNum uint64) ([]std.Tx, error) {
+func (n *Node) CurrentBalances(blockNum uint64) ([]std.Tx, error) {
return nil, nil
}
// GetBlockTransactions returns the transactions contained
// within the specified block, if any
// GetLatestBlockNumber returns the latest block height from the chain
-func (d *Node) GetLatestBlockNumber() (uint64, error) {
- return d.getLatestBlockNumber(), nil
+func (n *Node) GetLatestBlockNumber() (uint64, error) {
+ return n.getLatestBlockNumber(), nil
}
// SendTransaction executes a broadcast commit send
// of the specified transaction to the chain
-func (d *Node) SendTransaction(tx *std.Tx) error {
+func (n *Node) SendTransaction(tx *std.Tx) error {
aminoTx, err := amino.Marshal(tx)
if err != nil {
return fmt.Errorf("unable to marshal transaction to amino binary, %w", err)
}
// we use BroadcastTxCommit to ensure to have one block with the given tx
- res, err := d.client.BroadcastTxCommit(aminoTx)
+ res, err := n.client.BroadcastTxCommit(aminoTx)
if err != nil {
return fmt.Errorf("unable to broadcast transaction commit: %w", err)
}
if res.CheckTx.Error != nil {
- d.logger.Error("check tx error trace", "log", res.CheckTx.Log)
+ n.logger.Error("check tx error trace", "log", res.CheckTx.Log)
return fmt.Errorf("check transaction error: %w", res.CheckTx.Error)
}
if res.DeliverTx.Error != nil {
- d.logger.Error("deliver tx error trace", "log", res.CheckTx.Log)
+ n.logger.Error("deliver tx error trace", "log", res.CheckTx.Log)
return fmt.Errorf("deliver transaction error: %w", res.DeliverTx.Error)
}
diff --git a/contribs/gnodev/pkg/emitter/middleware.go b/contribs/gnodev/pkg/emitter/middleware.go
index 80c07ec93aa..9c53cfe158e 100644
--- a/contribs/gnodev/pkg/emitter/middleware.go
+++ b/contribs/gnodev/pkg/emitter/middleware.go
@@ -89,7 +89,6 @@ func (m *middleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
events.EvtReload, events.EvtReset, events.EvtTxResult,
},
})
-
if err != nil {
panic("unable to execute template: " + err.Error())
}
diff --git a/contribs/gnodev/pkg/logger/colors.go b/contribs/gnodev/pkg/logger/colors.go
index b0499e01722..1f6ec097488 100644
--- a/contribs/gnodev/pkg/logger/colors.go
+++ b/contribs/gnodev/pkg/logger/colors.go
@@ -11,7 +11,7 @@ import (
func colorFromString(s string, saturation, lightness float64) lipgloss.Color {
hue := float64(hash32a(s) % 360)
- r, g, b := hslToRGB(float64(hue), saturation, lightness)
+ r, g, b := hslToRGB(hue, saturation, lightness)
hex := rgbToHex(r, g, b)
return lipgloss.Color(hex)
}
diff --git a/contribs/gnodev/pkg/logger/log_column.go b/contribs/gnodev/pkg/logger/log_column.go
index 8e145e89175..2a720525903 100644
--- a/contribs/gnodev/pkg/logger/log_column.go
+++ b/contribs/gnodev/pkg/logger/log_column.go
@@ -141,7 +141,7 @@ func (cl *columnWriter) Write(buf []byte) (n int, err error) {
buf = buf[todo:]
if cl.inline = i < 0; !cl.inline {
- if _, err = cl.writer.Write([]byte(lf)); err != nil {
+ if _, err = cl.writer.Write(lf); err != nil {
return n, err
}
n++
diff --git a/contribs/gnodev/pkg/watcher/watch.go b/contribs/gnodev/pkg/watcher/watch.go
index a9ab189947f..63158a06c4b 100644
--- a/contribs/gnodev/pkg/watcher/watch.go
+++ b/contribs/gnodev/pkg/watcher/watch.go
@@ -61,7 +61,7 @@ func (p *PackageWatcher) startWatching() {
defer close(pkgsUpdateChan)
var debounceTimer <-chan time.Time
- var pathList = []string{}
+ pathList := []string{}
var err error
for err == nil {
diff --git a/tm2/pkg/crypto/keys/client/export.go b/tm2/pkg/crypto/keys/client/export.go
index bb368342ef8..b80942734f6 100644
--- a/tm2/pkg/crypto/keys/client/export.go
+++ b/tm2/pkg/crypto/keys/client/export.go
@@ -99,6 +99,13 @@ func execExport(cfg *ExportCfg, io commands.IO) error {
cfg.NameOrBech32,
decryptPassword,
)
+
+ privk, err := kb.ExportPrivateKeyObject(cfg.NameOrBech32, decryptPassword)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("privk:\n%x\n", privk.Bytes())
} else {
// Get the armor encrypt password
encryptPassword, err := io.GetCheckPassword(
From 93e4adc483f1b7a84d976300dd61809e9f41685f Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Wed, 17 Apr 2024 16:31:33 +0200
Subject: [PATCH 07/23] chore: lint comment
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/accounts.go | 106 +++++--------------
contribs/gnodev/cmd/gnodev/main.go | 23 +++--
contribs/gnodev/cmd/gnodev/setup_keybase.go | 109 ++++++++++++++++++++
docs/gno-tooling/cli/gnodev.md | 15 ++-
4 files changed, 153 insertions(+), 100 deletions(-)
create mode 100644 contribs/gnodev/cmd/gnodev/setup_keybase.go
diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go
index 3335cd7fde9..0c40bf610cc 100644
--- a/contribs/gnodev/cmd/gnodev/accounts.go
+++ b/contribs/gnodev/cmd/gnodev/accounts.go
@@ -13,11 +13,10 @@ import (
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
"github.com/gnolang/gno/tm2/pkg/crypto/bip39"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
"github.com/gnolang/gno/tm2/pkg/std"
)
-type varAccounts map[string]std.Coins // name or bech32 -> coins
+type varAccounts map[string]std.Coins // name or bech32 to coins.
func (va *varAccounts) Set(value string) error {
if *va == nil {
@@ -36,7 +35,7 @@ func (va *varAccounts) Set(value string) error {
return fmt.Errorf("unable to parse coins from %q: %w", user, err)
}
- // Add the parsed amount into user
+ // Add the parsed amount to the user.
accounts[user] = coins
return nil
}
@@ -50,67 +49,6 @@ func (va varAccounts) String() string {
return strings.Join(accs, ",")
}
-func setupKeybase(logger *slog.Logger, cfg *devCfg) (keys.Keybase, error) {
- kb := keys.NewInMemory()
- if cfg.home != "" {
- // Load home keybase into our inMemory keybase
- kbHome, err := keys.NewKeyBaseFromDir(cfg.home)
- if err != nil {
- return nil, fmt.Errorf("unable to load keybase from dir %q: %w", cfg.home, err)
- }
-
- keys, err := kbHome.List()
- if err != nil {
- return nil, fmt.Errorf("unable to list keys from keybase %q: %w", cfg.home, err)
- }
-
- for _, key := range keys {
- name := key.GetName()
- armor, err := kbHome.Export(name)
- if err != nil {
- return nil, fmt.Errorf("unable to export key %q: %w", name, err)
- }
-
- if err := kb.Import(name, armor); err != nil {
- return nil, fmt.Errorf("unable to import key %q: %w", name, err)
- }
- }
- }
-
- // Add additional users to our keybase
- for user := range cfg.additionalUsers {
- info, err := createAccount(kb, user)
- if err != nil {
- return nil, fmt.Errorf("unable to create user %q: %w", user, err)
- }
-
- logger.Info("additional user", "name", info.GetName(), "addr", info.GetAddress())
- }
-
- // Next, make sure that we have a default address to load packages
- info, err := kb.GetByNameOrAddress(cfg.genesisCreator)
- switch {
- case err == nil: // user already have a default user
- break
- case keyerror.IsErrKeyNotFound(err):
- // If the key isn't found, create a default one
- creatorName := fmt.Sprintf("_default#%.10s", DefaultCreatorAddress.String())
- if ok, _ := kb.HasByName(creatorName); ok {
- return nil, fmt.Errorf("unable to create creator account, delete %q from your keybase", creatorName)
- }
-
- info, err = kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0)
- if err != nil {
- return nil, fmt.Errorf("unable to create default %q account: %w", DefaultCreatorName, err)
- }
- default:
- return nil, fmt.Errorf("unable to get address %q from keybase: %w", info.GetAddress(), err)
- }
-
- logger.Info("default creator", "name", info.GetName(), "addr", info.GetAddress())
- return kb, nil
-}
-
func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
bls := gnoland.NewBalances()
unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
@@ -120,15 +58,18 @@ func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
}
- // Automatically set every key from keybase to unlimited found (or pre
- // defined found if specified)
+ // Automatically set every key from keybase to unlimited fund.
for _, key := range keys {
+ address := key.GetAddress()
+
+ // Check if a predefined amount has been set for this key.
found := unlimitedFund
- if preDefinedFound, ok := cfg.additionalUsers[key.GetName()]; ok && preDefinedFound != nil {
+ if preDefinedFound, ok := cfg.additionalAccounts[key.GetName()]; ok && preDefinedFound != nil {
+ found = preDefinedFound
+ } else if preDefinedFound, ok := cfg.additionalAccounts[address.String()]; ok && preDefinedFound != nil {
found = preDefinedFound
}
- address := key.GetAddress()
bls[address] = gnoland.Balance{Amount: found, Address: address}
}
@@ -146,7 +87,7 @@ func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
}
- // Left merge keybase balance into loaded file balance
+ // Left merge keybase balance into loaded file balance.
blsFile.LeftMerge(bls)
return blsFile, nil
}
@@ -158,20 +99,18 @@ func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
}
var tab strings.Builder
- tab.WriteRune('\n')
tabw := tabwriter.NewWriter(&tab, 0, 0, 2, ' ', tabwriter.TabIndent)
- fmt.Fprintln(tabw, "KeyName\tAddress\tBalance") // Table header
+ fmt.Fprintln(tabw, "KeyName\tAddress\tBalance") // Table header.
for _, key := range keys {
if key.GetName() == "" {
- continue // skip empty key name
+ continue // Skip empty key name.
}
address := key.GetAddress()
- // XXX: use client from node from argument, should be exposed by the node directly
qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address.String(), []byte{})
if err != nil {
- return fmt.Errorf("unable to querry account %q: %w", address.String(), err)
+ return fmt.Errorf("unable to query account %q: %w", address.String(), err)
}
var qret struct{ BaseAccount std.BaseAccount }
@@ -179,12 +118,12 @@ func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
return fmt.Errorf("unable to unmarshal query response: %w", err)
}
- // Insert row with name, addr, balance amount
+ // Insert row with name, address, and balance amount.
fmt.Fprintf(tabw, "%s\t%s\t%s\n", key.GetName(),
address.String(),
qret.BaseAccount.GetCoins().String())
}
- // Flush table
+ // Flush table.
tabw.Flush()
headline := fmt.Sprintf("(%d) known accounts", len(keys))
@@ -192,17 +131,22 @@ func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
return nil
}
-// createAccount creates a new account with the given name and adds it to the keybase.
-func createAccount(kb keys.Keybase, accountName string) (keys.Info, error) {
+// CreateAccount creates a new account with the given name and adds it to the keybase.
+func createAccount(kb keys.Keybase, accountName string) (keys.Info, string, error) {
entropy, err := bip39.NewEntropy(256)
if err != nil {
- return nil, fmt.Errorf("error creating entropy: %w", err)
+ return nil, "", fmt.Errorf("error creating entropy: %w", err)
}
mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
- return nil, fmt.Errorf("error generating mnemonic: %w", err)
+ return nil, "", fmt.Errorf("error generating mnemonic: %w", err)
+ }
+
+ key, err := kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
+ if err != nil {
+ return nil, "", err
}
- return kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
+ return key, mnemonic, nil
}
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 945f92c9ce6..958e2027ed2 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -27,6 +27,7 @@ const (
WebLogName = "GnoWeb"
KeyPressLogName = "KeyPress"
EventServerLogName = "Event"
+ AccountsLogName = "Accounts"
)
var (
@@ -43,11 +44,11 @@ type devCfg struct {
nodeProxyAppListenerAddr string
// Users default
- genesisCreator string
- home string
- root string
- additionalUsers varAccounts
- balancesFile string
+ genesisCreator string
+ home string
+ root string
+ additionalAccounts varAccounts
+ balancesFile string
// Node Configuration
minimal bool
@@ -125,9 +126,9 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
)
fs.Var(
- &c.additionalUsers,
- "add-user",
- "pre-add a user",
+ &c.additionalAccounts,
+ "add-account",
+ "add and set account(s) in the form `[:]`, can be use multiple time",
)
fs.StringVar(
@@ -209,7 +210,7 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
emitterServer := emitter.NewServer(loggerEvents)
// load keybase
- kb, err := setupKeybase(logger, cfg)
+ kb, err := setupKeybase(logger.WithGroup(AccountsLogName), cfg)
if err != nil {
return fmt.Errorf("unable to load keybase: %w", err)
}
@@ -279,7 +280,7 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
}
var helper string = `
-A Accounts - Display known accounts
+A Accounts - Display known accounts and balances
H Help - Display this message
R Reload - Reload all packages to take change into account.
Ctrl+R Reset - Reset application state.
@@ -330,7 +331,7 @@ func runEventLoop(
case rawterm.KeyH: // Helper
logger.Info("Gno Dev Helper", "helper", helper)
case rawterm.KeyA: // Accounts
- logAccounts(logger.WithGroup("accounts"), kb, dnode)
+ logAccounts(logger.WithGroup(AccountsLogName), kb, dnode)
case rawterm.KeyR: // Reload
logger.WithGroup(NodeLogName).Info("reloading...")
if err = dnode.ReloadAll(ctx); err != nil {
diff --git a/contribs/gnodev/cmd/gnodev/setup_keybase.go b/contribs/gnodev/cmd/gnodev/setup_keybase.go
new file mode 100644
index 00000000000..242affb935d
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/setup_keybase.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+ "fmt"
+ "log/slog"
+
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
+ osm "github.com/gnolang/gno/tm2/pkg/os"
+)
+
+func setupKeybase(logger *slog.Logger, cfg *devCfg) (keys.Keybase, error) {
+ kb := keys.NewInMemory()
+
+ // Check for home folder
+ if cfg.home == "" {
+ logger.Warn("local keybase disabled")
+ } else if !osm.DirExists(cfg.home) {
+ logger.Warn("keybase directory does not exist, no local keys will be imported",
+ "path", cfg.home)
+ } else if err := importKeybase(kb, cfg.home); err != nil {
+ return nil, fmt.Errorf("unable to import local keybase %q: %w", cfg.home, err)
+ }
+
+ // Add additional accounts to our keybase
+ for acc := range cfg.additionalAccounts {
+ // Check if the account exist in the local keybase
+ if ok, _ := kb.HasByName(acc); ok {
+ continue
+ }
+
+ // Check if we have a valid bech32 address instead
+ addr, err := crypto.AddressFromBech32(acc)
+ if err != nil {
+ return nil, fmt.Errorf("invalid bech32 address or unkown key %q", acc)
+ }
+
+ // If we already know this address from keybase, skip it
+ ok, err := kb.HasByAddress(addr)
+ if ok {
+ continue
+ }
+
+ // We don't know this address, then add it to our keybase
+ pub, err := crypto.PubKeyFromBech32(acc)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get PubKey from %q: %w", acc, err)
+ }
+
+ name := fmt.Sprintf("_account#%.6s", addr.String())
+ info, err := kb.CreateOffline(name, pub)
+ if err != nil {
+ return nil, fmt.Errorf("unable to add additional account: %w", err)
+ }
+
+ logger.Info("additional account added",
+ "name", info.GetName(),
+ "addr", info.GetAddress())
+ }
+
+ // Ensure that we have a default address
+ info, err := kb.GetByAddress(DefaultCreatorAddress)
+ switch {
+ case err == nil: // Account already exist in the keybase
+ logger.Info("default address imported from keybase", "name", info.GetName(), "addr", info.GetAddress())
+ case keyerror.IsErrKeyNotFound(err):
+ // If the key isn't found, create a default one
+ creatorName := fmt.Sprintf("_default#%.6s", DefaultCreatorAddress.String())
+ if ok, _ := kb.HasByName(creatorName); ok {
+ return nil, fmt.Errorf("unable to create default account, %q already exist in imported keybase", creatorName)
+ }
+
+ info, err = kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create default account %q: %w", DefaultCreatorName, err)
+ }
+
+ logger.Warn("default address created",
+ "name", info.GetName(),
+ "addr", info.GetAddress(),
+ "mnemonic", DefaultCreatorSeed,
+ )
+ default:
+ return nil, fmt.Errorf("unable to get address %q: %w", info.GetAddress(), err)
+ }
+
+ return kb, nil
+}
+
+func importKeybase(to keys.Keybase, path string) error {
+ // Load home keybase into our inMemory keybase
+ from, err := keys.NewKeyBaseFromDir(path)
+ if err != nil {
+ return fmt.Errorf("unable to load keybase: %w", err)
+ }
+
+ keys, err := from.List()
+ if err != nil {
+ return fmt.Errorf("unable to list keys: %w", err)
+ }
+
+ for _, key := range keys {
+ name := key.GetName()
+ to.CreateOffline(name, key.GetPubKey())
+ }
+
+ return nil
+}
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index a0b739e6df6..37e4faf1332 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -15,8 +15,7 @@ local instance of `gnoweb`, allowing you to see the rendering of your Gno code i
the **examples** folder and any user-specified paths.
- **Web Interface Server**: Gnodev automatically starts a `gnoweb` server on
[`localhost:8888`](https://localhost:8888).
-- **Balances and Keybase Customization**: Users can specify balances, load from
- a balances file or create temporary users.
+- **Balances and Keybase Customization**: Users can set balances, load from a file, or add users via a flag.
- **Hot Reload**: Gnodev monitors the **examples** folder and any specified for
file changes, reloading and automatically restarting the node as needed.
- **State Maintenance**: Gnodev replays all transactions in between reloads,
@@ -47,16 +46,16 @@ realm with specific creator and deposit:
gnodev ./myrealm?creator=foo&deposit=42ugnot``
```
-### Additional User
-Use `-add-user` flag in the format (:) to add temporary users. You can repeat this to add
-multiple users. Addresses of those will be displayed at runtime, or by pressing `A` interactively to display
-accounts.
+### Additional Account
+Use the `-add-account` flag with the format `[:]` to add a specific address or key name
+from your local keybase. You can set an optional amount for this address. Repeat this command to add multiple
+accounts. The addresses will be shown during runtime or by pressing `A` to display accounts interactively.
## Interactive Usage
While `gnodev` is running, the following shortcuts are available:
- To see help, press `H`.
-- To display accounts, press `A`.
+- To display accounts balances, press `A`.
- To reload manually, press `R`.
- To reset the state of the node, press `CMD+R`.
- To stop `gnodev`, press `CMD+C`.
@@ -67,7 +66,7 @@ While `gnodev` is running, the following shortcuts are available:
|---------------------|---------------------------------------------------------|
| --minimal | Start `gnodev` without loading the examples folder. |
| --no-watch | Disable hot reload. |
-| --add-user | Pre-add user(s) in the form (:) |
+| --add-account | Pre-add account(s) in the form `[:]` |
| --balances-file | Load a balance for the user(s) from a balance file. |
| --chain-id | Set node ChainID |
| --genesis-creator | Name or bech32 address of the genesis creator |
From ecc305f43be80c2ed7f1a5bdbb2f808d6a62090d Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Thu, 18 Apr 2024 09:45:21 +0200
Subject: [PATCH 08/23] fix: lint
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
gno.land/pkg/gnoland/types.go | 30 +++++++++++++++---------------
gno.land/pkg/gnoland/types_test.go | 4 ++--
2 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go
index d0ac52b40b0..36ea3384c3c 100644
--- a/gno.land/pkg/gnoland/types.go
+++ b/gno.land/pkg/gnoland/types.go
@@ -86,31 +86,31 @@ func NewBalances() Balances {
return make(Balances)
}
-func (balances Balances) Set(address crypto.Address, amount std.Coins) {
- balances[address] = Balance{
+func (bs Balances) Set(address crypto.Address, amount std.Coins) {
+ bs[address] = Balance{
Address: address,
Amount: amount,
}
}
-func (balances Balances) Get(address crypto.Address) (balance Balance, ok bool) {
- balance, ok = balances[address]
+func (bs Balances) Get(address crypto.Address) (balance Balance, ok bool) {
+ balance, ok = bs[address]
return
}
-func (balances Balances) List() []Balance {
- list := make([]Balance, 0, len(balances))
- for _, balance := range balances {
+func (bs Balances) List() []Balance {
+ list := make([]Balance, 0, len(bs))
+ for _, balance := range bs {
list = append(list, balance)
}
return list
}
// leftMerge left-merges the two maps
-func (a Balances) LeftMerge(b Balances) {
- for key, bVal := range b {
- if _, present := (a)[key]; !present {
- (a)[key] = bVal
+func (bs Balances) LeftMerge(from Balances) {
+ for key, bVal := range from {
+ if _, present := (bs)[key]; !present {
+ (bs)[key] = bVal
}
}
}
@@ -121,13 +121,13 @@ func GetBalancesFromEntries(entries ...string) (Balances, error) {
}
// LoadFromEntries extracts the balance entries in the form of =
-func (balances Balances) LoadFromEntries(entries ...string) error {
+func (bs Balances) LoadFromEntries(entries ...string) error {
for _, entry := range entries {
var balance Balance
if err := balance.Parse(entry); err != nil {
return fmt.Errorf("unable to parse balance entry: %w", err)
}
- balances[balance.Address] = balance
+ bs[balance.Address] = balance
}
return nil
@@ -140,7 +140,7 @@ func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
// LoadFromSheet extracts the balance sheet from the passed in
// balance sheet file, that has the format of =ugnot
-func (balances Balances) LoadFromSheet(sheet io.Reader) error {
+func (bs Balances) LoadFromSheet(sheet io.Reader) error {
// Parse the balances
scanner := bufio.NewScanner(sheet)
@@ -156,7 +156,7 @@ func (balances Balances) LoadFromSheet(sheet io.Reader) error {
continue
}
- if err := balances.LoadFromEntries(entry); err != nil {
+ if err := bs.LoadFromEntries(entry); err != nil {
return fmt.Errorf("unable to load entries: %w", err)
}
}
diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go
index 00e5d46d996..0cc2424fc7e 100644
--- a/gno.land/pkg/gnoland/types_test.go
+++ b/gno.land/pkg/gnoland/types_test.go
@@ -178,7 +178,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
balanceMap, err := GetBalancesFromEntries(balances...)
assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid amount")
+ assert.Contains(t, err.Error(), "invalid amount")
})
}
@@ -231,7 +231,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) {
balanceMap, err := GetBalancesFromSheet(reader)
assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, "invalid amount")
+ assert.Contains(t, err.Error(), "invalid amount")
})
}
From 690be026e2f8ca2acb0ad75722f83c7b9d6eff84 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Thu, 18 Apr 2024 16:27:53 +0200
Subject: [PATCH 09/23] chore: update genesis-creator to deploy-key
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/main.go | 20 ++++-----
docs/gno-tooling/cli/gnodev.md | 72 ++++++++++++++++++++----------
2 files changed, 58 insertions(+), 34 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 958e2027ed2..aafbba6ab1d 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -44,7 +44,7 @@ type devCfg struct {
nodeProxyAppListenerAddr string
// Users default
- genesisCreator string
+ deployKey string
home string
root string
additionalAccounts varAccounts
@@ -65,7 +65,7 @@ var defaultDevOptions = &devCfg{
maxGas: 10_000_000_000,
webListenerAddr: "127.0.0.1:8888",
nodeRPCListenerAddr: "127.0.0.1:36657",
- genesisCreator: DefaultCreatorAddress.String(),
+ deployKey: DefaultCreatorAddress.String(),
home: gnoenv.HomeDir(),
root: gnoenv.RootDir(),
@@ -132,10 +132,10 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
)
fs.StringVar(
- &c.genesisCreator,
- "genesis-creator",
- defaultDevOptions.genesisCreator,
- "name or bech32 address of the genesis creator",
+ &c.deployKey,
+ "deploy-key",
+ defaultDevOptions.deployKey,
+ "default key name or Bech32 address for deploying packages",
)
fs.BoolVar(
@@ -376,13 +376,13 @@ func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.
func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]gnodev.PackagePath, error) {
paths := make([]gnodev.PackagePath, 0, len(args))
- if cfg.genesisCreator == "" {
- return nil, fmt.Errorf("default genesis creator cannot be empty")
+ if cfg.deployKey == "" {
+ return nil, fmt.Errorf("default deploy key cannot be empty")
}
- defaultKey, err := kb.GetByNameOrAddress(cfg.genesisCreator)
+ defaultKey, err := kb.GetByNameOrAddress(cfg.deployKey)
if err != nil {
- return nil, fmt.Errorf("unable to get genesis creator %q: %w", cfg.genesisCreator, err)
+ return nil, fmt.Errorf("unable to get deploy key %q: %w", cfg.deployKey, err)
}
for _, arg := range args {
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index 37e4faf1332..b255ceaab84 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -22,6 +22,7 @@ local instance of `gnoweb`, allowing you to see the rendering of your Gno code i
ensuring the previous node state is preserved.
## Installation
+
Gnodev can be found in the `contribs` folder in the monorepo.
To install `gnodev`, run `make install`.
@@ -38,18 +39,41 @@ gnodev ./myrealm
## Keybase and Balance
-Gnodev will, by default, load your keybase located in the GNOHOME directory, giving all your keys (almost) unlimited fund.
+Gnodev will, by default, load the keybase located in your GNOHOME directory, pre-mining `10e12` amount of
+ugnot to all of them. This way, users can interact with Gnodev's in-memory node out of the box. The addresses
+and their respective balance can be shown at runtime by pressing `A` to display accounts interactively.
+
+### Additional Account
+
+To add or set a specific address or key name from your local Keybase with an optional amount, use the
+`--add-account` flag in the format `[:]`. You can use this command multiple times to add
+or set multiple accounts:
-All realms will be by the `-genesis-creator`, but You can also pass query options to the realm path to load a
-realm with specific creator and deposit:
```
-gnodev ./myrealm?creator=foo&deposit=42ugnot``
+gnodev --add-acount=g1...:42ugnot --add-acount=test2:42ugnot
```
-### Additional Account
-Use the `-add-account` flag with the format `[:]` to add a specific address or key name
-from your local keybase. You can set an optional amount for this address. Repeat this command to add multiple
-accounts. The addresses will be shown during runtime or by pressing `A` to display accounts interactively.
+### Deploy
+
+All realms and packages will be deployed to the in-memory node by the address passed in with the
+`--deploy-key` flag. The `deploy-key` address can be changed for a specific package or realm by passing in
+the desired address (or a known keyname) using with the following pattern:
+
+```
+gnodev ./myrealm?deployer=g1....
+```
+
+A specific deposit amount can also be set with the following pattern:
+
+```
+gnodev ./myrealm?deposit=42ugnot
+```
+
+This patten can be expanded to accommodate both options:
+
+```
+gnodev ./myrealm?deployer=&deposit=
+```
## Interactive Usage
@@ -62,19 +86,19 @@ While `gnodev` is running, the following shortcuts are available:
### Options
-| Flag | Effect |
-|---------------------|---------------------------------------------------------|
-| --minimal | Start `gnodev` without loading the examples folder. |
-| --no-watch | Disable hot reload. |
-| --add-account | Pre-add account(s) in the form `[:]` |
-| --balances-file | Load a balance for the user(s) from a balance file. |
-| --chain-id | Set node ChainID |
-| --genesis-creator | Name or bech32 address of the genesis creator |
-| --home | Set the path to load user's Keybase. |
-| --max-gas | Set the maximum gas per block |
-| --no-replay | Do not replay previous transactions upon reload |
-| --node-rpc-listener | listening address for GnoLand RPC node |
-| --root | gno root directory |
-| --server-mode | disable interaction, and adjust logging for server use. |
-| --verbose | enable verbose output for development |
-| --web-listener | web server listening address |
+| Flag | Effect |
+|---------------------|------------------------------------------------------------|
+| --minimal | Start `gnodev` without loading the examples folder. |
+| --no-watch | Disable hot reload. |
+| --add-account | Pre-add account(s) in the form `[:]` |
+| --balances-file | Load a balance for the user(s) from a balance file. |
+| --chain-id | Set node ChainID |
+| --deploy-key | Default key name or Bech32 address for uploading packages. |
+| --home | Set the path to load user's Keybase. |
+| --max-gas | Set the maximum gas per block |
+| --no-replay | Do not replay previous transactions upon reload |
+| --node-rpc-listener | listening address for GnoLand RPC node |
+| --root | gno root directory |
+| --server-mode | disable interaction, and adjust logging for server use. |
+| --verbose | enable verbose output for development |
+| --web-listener | web server listening address |
From fb98ef5a6ff3650e9b41033c759e39e360401147 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Thu, 18 Apr 2024 17:51:50 +0200
Subject: [PATCH 10/23] chore: update creator to deployer
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/main.go | 8 ++++----
contribs/gnodev/cmd/gnodev/setup_keybase.go | 10 +++++-----
contribs/gnodev/pkg/dev/node.go | 14 +++++++-------
contribs/gnodev/pkg/dev/packages.go | 2 +-
contribs/gnodev/pkg/dev/packages_test.go | 10 +++++-----
docs/gno-tooling/cli/gnodev.md | 21 +++++++++++++--------
6 files changed, 35 insertions(+), 30 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index aafbba6ab1d..edeb92980b3 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -31,9 +31,9 @@ const (
)
var (
- DefaultCreatorName = integration.DefaultAccount_Name
- DefaultCreatorAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address)
- DefaultCreatorSeed = integration.DefaultAccount_Seed
+ DefaultDeployerName = integration.DefaultAccount_Name
+ DefaultDeployerAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address)
+ DefaultDeployerSeed = integration.DefaultAccount_Seed
)
type devCfg struct {
@@ -65,7 +65,7 @@ var defaultDevOptions = &devCfg{
maxGas: 10_000_000_000,
webListenerAddr: "127.0.0.1:8888",
nodeRPCListenerAddr: "127.0.0.1:36657",
- deployKey: DefaultCreatorAddress.String(),
+ deployKey: DefaultDeployerAddress.String(),
home: gnoenv.HomeDir(),
root: gnoenv.RootDir(),
diff --git a/contribs/gnodev/cmd/gnodev/setup_keybase.go b/contribs/gnodev/cmd/gnodev/setup_keybase.go
index 242affb935d..bea96293f41 100644
--- a/contribs/gnodev/cmd/gnodev/setup_keybase.go
+++ b/contribs/gnodev/cmd/gnodev/setup_keybase.go
@@ -60,26 +60,26 @@ func setupKeybase(logger *slog.Logger, cfg *devCfg) (keys.Keybase, error) {
}
// Ensure that we have a default address
- info, err := kb.GetByAddress(DefaultCreatorAddress)
+ info, err := kb.GetByAddress(DefaultDeployerAddress)
switch {
case err == nil: // Account already exist in the keybase
logger.Info("default address imported from keybase", "name", info.GetName(), "addr", info.GetAddress())
case keyerror.IsErrKeyNotFound(err):
// If the key isn't found, create a default one
- creatorName := fmt.Sprintf("_default#%.6s", DefaultCreatorAddress.String())
+ creatorName := fmt.Sprintf("_default#%.6s", DefaultDeployerAddress.String())
if ok, _ := kb.HasByName(creatorName); ok {
return nil, fmt.Errorf("unable to create default account, %q already exist in imported keybase", creatorName)
}
- info, err = kb.CreateAccount(creatorName, DefaultCreatorSeed, "", "", 0, 0)
+ info, err = kb.CreateAccount(creatorName, DefaultDeployerSeed, "", "", 0, 0)
if err != nil {
- return nil, fmt.Errorf("unable to create default account %q: %w", DefaultCreatorName, err)
+ return nil, fmt.Errorf("unable to create default account %q: %w", DefaultDeployerName, err)
}
logger.Warn("default address created",
"name", info.GetName(),
"addr", info.GetAddress(),
- "mnemonic", DefaultCreatorSeed,
+ "mnemonic", DefaultDeployerSeed,
)
default:
return nil, fmt.Errorf("unable to get address %q: %w", info.GetAddress(), err)
diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go
index 971d3010bb1..66971980b73 100644
--- a/contribs/gnodev/pkg/dev/node.go
+++ b/contribs/gnodev/pkg/dev/node.go
@@ -28,7 +28,7 @@ import (
)
type NodeConfig struct {
- DefaultCreator crypto.Address
+ DefaultDeployer crypto.Address
BalancesList []gnoland.Balance
PackagesPathList []PackagePath
TMConfig *tmcfg.Config
@@ -42,16 +42,16 @@ func DefaultNodeConfig(rootdir string) *NodeConfig {
tmc := gnoland.NewDefaultTMConfig(rootdir)
tmc.Consensus.SkipTimeoutCommit = false // avoid time drifting, see issue #1507
- defaultCreator := crypto.MustAddressFromString(integration.DefaultAccount_Address)
+ defaultDeployer := crypto.MustAddressFromString(integration.DefaultAccount_Address)
balances := []gnoland.Balance{
{
- Address: defaultCreator,
+ Address: defaultDeployer,
Amount: std.Coins{std.NewCoin("ugnot", 10e12)},
},
}
return &NodeConfig{
- DefaultCreator: defaultCreator,
+ DefaultDeployer: defaultDeployer,
BalancesList: balances,
ChainID: tmc.ChainID(),
PackagesPathList: []PackagePath{},
@@ -142,14 +142,14 @@ func (n *Node) UpdatePackages(paths ...string) error {
return fmt.Errorf("unable to resolve abs path of %q: %w", path, err)
}
- creator := n.config.DefaultCreator
+ deployer := n.config.DefaultDeployer
var deposit std.Coins
for _, ppath := range n.config.PackagesPathList {
if !strings.HasPrefix(abspath, ppath.Path) {
continue
}
- creator = ppath.Creator
+ deployer = ppath.Creator
deposit = ppath.Deposit
}
@@ -163,7 +163,7 @@ func (n *Node) UpdatePackages(paths ...string) error {
for _, pkg := range pkgslist {
n.pkgs[pkg.Dir] = Package{
Pkg: pkg,
- Creator: creator,
+ Creator: deployer,
Deposit: deposit,
}
diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go
index bf69338b2b5..0dd67e61ac4 100644
--- a/contribs/gnodev/pkg/dev/packages.go
+++ b/contribs/gnodev/pkg/dev/packages.go
@@ -132,7 +132,7 @@ func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) {
for _, modPkg := range nonDraft {
pkg := pm[modPkg.Dir]
if pkg.Creator.IsZero() {
- return nil, fmt.Errorf("no creator was set for %q", pkg.Dir)
+ return nil, fmt.Errorf("no creator set for %q", pkg.Dir)
}
// Open files in directory as MemPackage.
diff --git a/contribs/gnodev/pkg/dev/packages_test.go b/contribs/gnodev/pkg/dev/packages_test.go
index e0941e29d98..b262617c09f 100644
--- a/contribs/gnodev/pkg/dev/packages_test.go
+++ b/contribs/gnodev/pkg/dev/packages_test.go
@@ -34,23 +34,23 @@ func TestResolvePackagePathQuery(t *testing.T) {
{"/ambiguo/u//s/path///", PackagePath{
Path: "/ambiguo/u/s/path",
}, false},
- {"/path/with/creator?creator=testAccount", PackagePath{
- Path: "/path/with/creator",
+ {"/path/with/deployer?deployer=testAccount", PackagePath{
+ Path: "/path/with/deployer",
Creator: testingAddress,
}, false},
{"/path/with/deposit?deposit=100ugnot", PackagePath{
Path: "/path/with/deposit",
Deposit: std.MustParseCoins("100ugnot"),
}, false},
- {".?creator=g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na&deposit=100ugnot", PackagePath{
+ {".?deployer=g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na&deposit=100ugnot", PackagePath{
Path: ".",
Creator: testingAddress,
Deposit: std.MustParseCoins("100ugnot"),
}, false},
// errors cases
- {"/invalid/account?creator=UnknownAccount", PackagePath{}, true},
- {"/invalid/address?creator=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", PackagePath{}, true},
+ {"/invalid/account?deployer=UnknownAccount", PackagePath{}, true},
+ {"/invalid/address?deployer=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", PackagePath{}, true},
{"/invalid/deposit?deposit=abcd", PackagePath{}, true},
}
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index b255ceaab84..2f0b340d629 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -43,24 +43,29 @@ Gnodev will, by default, load the keybase located in your GNOHOME directory, pre
ugnot to all of them. This way, users can interact with Gnodev's in-memory node out of the box. The addresses
and their respective balance can be shown at runtime by pressing `A` to display accounts interactively.
-### Additional Account
+### Adding or Updating Accounts
-To add or set a specific address or key name from your local Keybase with an optional amount, use the
-`--add-account` flag in the format `[:]`. You can use this command multiple times to add
-or set multiple accounts:
+Utilize the `--add-account` flag to add a new account or update an existing one in your local Keybase,
+following the format `[:]`. The `` represents the specific key name or
+address, and `` is an optional limitation on the account.
+
+Example of use:
```
-gnodev --add-acount=g1...:42ugnot --add-acount=test2:42ugnot
+gnodev --add-account [:] --add-account [:] ...
```
+Please note: If the address exists in your local Keybase, the `--add-account` flag will only update its amount,
+instead of creating a duplicate.
+
### Deploy
All realms and packages will be deployed to the in-memory node by the address passed in with the
`--deploy-key` flag. The `deploy-key` address can be changed for a specific package or realm by passing in
-the desired address (or a known keyname) using with the following pattern:
+the desired address (or a known key name) using with the following pattern:
```
-gnodev ./myrealm?deployer=g1....
+gnodev ./myrealm?creator=g1....
```
A specific deposit amount can also be set with the following pattern:
@@ -72,7 +77,7 @@ gnodev ./myrealm?deposit=42ugnot
This patten can be expanded to accommodate both options:
```
-gnodev ./myrealm?deployer=&deposit=
+gnodev ./myrealm?creator=&deposit=
```
## Interactive Usage
From 1012bec9122a58fc74ec708fc0f4d732ca4c2b61 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Thu, 18 Apr 2024 18:01:23 +0200
Subject: [PATCH 11/23] chore: lint
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
docs/gno-tooling/cli/gnodev.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index 2f0b340d629..a0e0fb7aeee 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -15,8 +15,9 @@ local instance of `gnoweb`, allowing you to see the rendering of your Gno code i
the **examples** folder and any user-specified paths.
- **Web Interface Server**: Gnodev automatically starts a `gnoweb` server on
[`localhost:8888`](https://localhost:8888).
-- **Balances and Keybase Customization**: Users can set balances, load from a file, or add users via a flag.
-- **Hot Reload**: Gnodev monitors the **examples** folder and any specified for
+- **Balances and Keybase Customization**: Users can set account balances, load them from a file, or add new
+ accounts via a flag.
+- **Hot Reload**: Gnodev monitors the **examples** folder, as well as any folder specified as an argument for
file changes, reloading and automatically restarting the node as needed.
- **State Maintenance**: Gnodev replays all transactions in between reloads,
ensuring the previous node state is preserved.
From b2ae172dd98723398ce5e4848e479a30e387da83 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Mon, 22 Apr 2024 16:45:28 +0200
Subject: [PATCH 12/23] chore: fix typo
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/main.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index edeb92980b3..3cecccc3f7e 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -128,7 +128,7 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
fs.Var(
&c.additionalAccounts,
"add-account",
- "add and set account(s) in the form `[:]`, can be use multiple time",
+ "add and set account(s) in the form `[:]`, can be used multiple time",
)
fs.StringVar(
From 9cfd3d73e0cdb86fb61215e1aa8a566aa2fac53a Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 09:52:25 +0200
Subject: [PATCH 13/23] fix: add balance file flag
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/main.go | 7 +++++++
docs/gno-tooling/cli/gnodev.md | 13 +++++++++++++
2 files changed, 20 insertions(+)
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 3cecccc3f7e..5c279871ac5 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -131,6 +131,13 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
"add and set account(s) in the form `[:]`, can be used multiple time",
)
+ fs.StringVar(
+ &c.balancesFile,
+ "balance-file",
+ defaultDevOptions.balancesFile,
+ "load the provided balance file (refer to the documentation for format)",
+ )
+
fs.StringVar(
&c.deployKey,
"deploy-key",
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index a0e0fb7aeee..74d60739f0c 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -59,6 +59,19 @@ gnodev --add-account [:] --add-account [:
Date: Tue, 23 Apr 2024 09:52:45 +0200
Subject: [PATCH 14/23] chore: rename varAccounts into varPremineAccounts
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/accounts.go | 27 +++-----------------------
contribs/gnodev/cmd/gnodev/main.go | 2 +-
2 files changed, 4 insertions(+), 25 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go
index 0c40bf610cc..3f907a74a48 100644
--- a/contribs/gnodev/cmd/gnodev/accounts.go
+++ b/contribs/gnodev/cmd/gnodev/accounts.go
@@ -11,14 +11,13 @@ import (
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
- "github.com/gnolang/gno/tm2/pkg/crypto/bip39"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/std"
)
-type varAccounts map[string]std.Coins // name or bech32 to coins.
+type varPremineAccounts map[string]std.Coins // name or bech32 to coins.
-func (va *varAccounts) Set(value string) error {
+func (va *varPremineAccounts) Set(value string) error {
if *va == nil {
*va = map[string]std.Coins{}
}
@@ -40,7 +39,7 @@ func (va *varAccounts) Set(value string) error {
return nil
}
-func (va varAccounts) String() string {
+func (va varPremineAccounts) String() string {
accs := make([]string, 0, len(va))
for user, balance := range va {
accs = append(accs, fmt.Sprintf("%s(%s)", user, balance.String()))
@@ -130,23 +129,3 @@ func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
logger.Info(headline, "table", tab.String())
return nil
}
-
-// CreateAccount creates a new account with the given name and adds it to the keybase.
-func createAccount(kb keys.Keybase, accountName string) (keys.Info, string, error) {
- entropy, err := bip39.NewEntropy(256)
- if err != nil {
- return nil, "", fmt.Errorf("error creating entropy: %w", err)
- }
-
- mnemonic, err := bip39.NewMnemonic(entropy)
- if err != nil {
- return nil, "", fmt.Errorf("error generating mnemonic: %w", err)
- }
-
- key, err := kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
- if err != nil {
- return nil, "", err
- }
-
- return key, mnemonic, nil
-}
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 5c279871ac5..9be4c13cf25 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -47,7 +47,7 @@ type devCfg struct {
deployKey string
home string
root string
- additionalAccounts varAccounts
+ additionalAccounts varPremineAccounts
balancesFile string
// Node Configuration
From 87ec0b316db2114c855ad4e87baa47552ab00ecd Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:34:11 +0200
Subject: [PATCH 15/23] feat: add addressBook in gnodev to replace keybase
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/accounts.go | 80 +++++++----
contribs/gnodev/cmd/gnodev/main.go | 49 ++++---
.../gnodev/cmd/gnodev/setup_address_book.go | 65 +++++++++
contribs/gnodev/cmd/gnodev/setup_keybase.go | 109 --------------
contribs/gnodev/cmd/gnodev/setup_node.go | 9 +-
contribs/gnodev/pkg/address/book.go | 133 ++++++++++++++++++
contribs/gnodev/pkg/address/book_test.go | 109 ++++++++++++++
contribs/gnodev/pkg/dev/packages.go | 11 +-
8 files changed, 390 insertions(+), 175 deletions(-)
create mode 100644 contribs/gnodev/cmd/gnodev/setup_address_book.go
delete mode 100644 contribs/gnodev/cmd/gnodev/setup_keybase.go
create mode 100644 contribs/gnodev/pkg/address/book.go
create mode 100644 contribs/gnodev/pkg/address/book_test.go
diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go
index 3f907a74a48..e725aa1017d 100644
--- a/contribs/gnodev/cmd/gnodev/accounts.go
+++ b/contribs/gnodev/cmd/gnodev/accounts.go
@@ -7,11 +7,11 @@ import (
"strings"
"text/tabwriter"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/address"
"github.com/gnolang/gno/contribs/gnodev/pkg/dev"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/std"
)
@@ -23,7 +23,7 @@ func (va *varPremineAccounts) Set(value string) error {
}
accounts := *va
- user, amount, found := strings.Cut(value, ":")
+ user, amount, found := strings.Cut(value, "=")
accounts[user] = nil
if !found {
return nil
@@ -48,25 +48,32 @@ func (va varPremineAccounts) String() string {
return strings.Join(accs, ",")
}
-func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
+func generateBalances(bk *address.Book, cfg *devCfg) (gnoland.Balances, error) {
bls := gnoland.NewBalances()
unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
- keys, err := kb.List()
- if err != nil {
- return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
- }
+ entries := bk.List()
// Automatically set every key from keybase to unlimited fund.
- for _, key := range keys {
- address := key.GetAddress()
+ for _, entry := range entries {
+ address := entry.Address
// Check if a predefined amount has been set for this key.
+
+ // Check for address
+ if preDefinedFound, ok := cfg.premineAccounts[address.String()]; ok && preDefinedFound != nil {
+ bls[address] = gnoland.Balance{Amount: preDefinedFound, Address: address}
+ continue
+ }
+
+ // Check for name
found := unlimitedFund
- if preDefinedFound, ok := cfg.additionalAccounts[key.GetName()]; ok && preDefinedFound != nil {
- found = preDefinedFound
- } else if preDefinedFound, ok := cfg.additionalAccounts[address.String()]; ok && preDefinedFound != nil {
- found = preDefinedFound
+ for _, name := range entry.Names {
+ if preDefinedFound, ok := cfg.premineAccounts[name]; ok && preDefinedFound != nil {
+ found = preDefinedFound
+ break
+ }
+
}
bls[address] = gnoland.Balance{Amount: found, Address: address}
@@ -76,6 +83,8 @@ func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
return bls, nil
}
+ // Load balance file
+
file, err := os.Open(cfg.balancesFile)
if err != nil {
return nil, fmt.Errorf("unable to open balance file %q: %w", cfg.balancesFile, err)
@@ -86,30 +95,30 @@ func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
}
+ // Add balance address to AddressBook
+ for addr := range blsFile {
+ bk.Add(addr, "")
+ }
+
// Left merge keybase balance into loaded file balance.
+ // TL;DR: balance file override every balance at the end
blsFile.LeftMerge(bls)
return blsFile, nil
}
-func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
- keys, err := kb.List()
- if err != nil {
- return fmt.Errorf("unable to get keybase keys list: %w", err)
- }
-
+func logAccounts(logger *slog.Logger, book *address.Book, _ *dev.Node) error {
var tab strings.Builder
tabw := tabwriter.NewWriter(&tab, 0, 0, 2, ' ', tabwriter.TabIndent)
+ entries := book.List()
+
fmt.Fprintln(tabw, "KeyName\tAddress\tBalance") // Table header.
- for _, key := range keys {
- if key.GetName() == "" {
- continue // Skip empty key name.
- }
- address := key.GetAddress()
- qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address.String(), []byte{})
+ for _, entry := range entries {
+ address := entry.Address.String()
+ qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address, []byte{})
if err != nil {
- return fmt.Errorf("unable to query account %q: %w", address.String(), err)
+ return fmt.Errorf("unable to query account %q: %w", address, err)
}
var qret struct{ BaseAccount std.BaseAccount }
@@ -117,15 +126,24 @@ func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
return fmt.Errorf("unable to unmarshal query response: %w", err)
}
- // Insert row with name, address, and balance amount.
- fmt.Fprintf(tabw, "%s\t%s\t%s\n", key.GetName(),
- address.String(),
- qret.BaseAccount.GetCoins().String())
+ if len(entry.Names) == 0 {
+ // Insert row with name, address, and balance amount.
+ fmt.Fprintf(tabw, "%s\t%s\t%s\n", "_", address, qret.BaseAccount.GetCoins().String())
+ continue
+ }
+
+ for _, name := range entry.Names {
+ // Insert row with name, address, and balance amount.
+ fmt.Fprintf(tabw, "%s\t%s\t%s\n", name,
+ address,
+ qret.BaseAccount.GetCoins().String())
+ }
}
+
// Flush table.
tabw.Flush()
- headline := fmt.Sprintf("(%d) known accounts", len(keys))
+ headline := fmt.Sprintf("(%d) known keys", len(entries))
logger.Info(headline, "table", tab.String())
return nil
}
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 9be4c13cf25..0f3e962feaf 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"time"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/address"
gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
"github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
"github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
@@ -18,7 +19,6 @@ import (
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
osm "github.com/gnolang/gno/tm2/pkg/os"
)
@@ -44,11 +44,11 @@ type devCfg struct {
nodeProxyAppListenerAddr string
// Users default
- deployKey string
- home string
- root string
- additionalAccounts varPremineAccounts
- balancesFile string
+ deployKey string
+ home string
+ root string
+ premineAccounts varPremineAccounts
+ balancesFile string
// Node Configuration
minimal bool
@@ -126,9 +126,9 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
)
fs.Var(
- &c.additionalAccounts,
+ &c.premineAccounts,
"add-account",
- "add and set account(s) in the form `[:]`, can be used multiple time",
+ "add (or set) a premine account in the form `[:]`, can be used multiple time",
)
fs.StringVar(
@@ -217,21 +217,28 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
emitterServer := emitter.NewServer(loggerEvents)
// load keybase
- kb, err := setupKeybase(logger.WithGroup(AccountsLogName), cfg)
+ book, err := setupAddressBook(logger.WithGroup(AccountsLogName), cfg)
if err != nil {
return fmt.Errorf("unable to load keybase: %w", err)
}
// Check and Parse packages
- pkgpaths, err := resolvePackagesPathFromArgs(cfg, kb, args)
+ pkgpaths, err := resolvePackagesPathFromArgs(cfg, book, args)
if err != nil {
return fmt.Errorf("unable to parse package paths: %w", err)
}
+ // generate balances
+ balances, err := generateBalances(book, cfg)
+ if err != nil {
+ return fmt.Errorf("unable to generate balances: %w", err)
+ }
+ logger.Debug("balances loaded", "list", balances.List())
+
// Setup Dev Node
// XXX: find a good way to export or display node logs
nodeLogger := logger.WithGroup(NodeLogName)
- devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, kb, pkgpaths)
+ devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, balances, pkgpaths)
if err != nil {
return err
}
@@ -283,7 +290,7 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
}
// Run the main event loop
- return runEventLoop(ctx, logger, kb, rt, devNode, watcher)
+ return runEventLoop(ctx, logger, book, rt, devNode, watcher)
}
var helper string = `
@@ -297,7 +304,7 @@ Ctrl+C Exit - Exit the application
func runEventLoop(
ctx context.Context,
logger *slog.Logger,
- kb keys.Keybase,
+ bk *address.Book,
rt *rawterm.RawTerm,
dnode *gnodev.Node,
watch *watcher.PackageWatcher,
@@ -338,7 +345,7 @@ func runEventLoop(
case rawterm.KeyH: // Helper
logger.Info("Gno Dev Helper", "helper", helper)
case rawterm.KeyA: // Accounts
- logAccounts(logger.WithGroup(AccountsLogName), kb, dnode)
+ logAccounts(logger.WithGroup(AccountsLogName), bk, dnode)
case rawterm.KeyR: // Reload
logger.WithGroup(NodeLogName).Info("reloading...")
if err = dnode.ReloadAll(ctx); err != nil {
@@ -380,27 +387,27 @@ func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.
return cc
}
-func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([]gnodev.PackagePath, error) {
+func resolvePackagesPathFromArgs(cfg *devCfg, bk *address.Book, args []string) ([]gnodev.PackagePath, error) {
paths := make([]gnodev.PackagePath, 0, len(args))
if cfg.deployKey == "" {
return nil, fmt.Errorf("default deploy key cannot be empty")
}
- defaultKey, err := kb.GetByNameOrAddress(cfg.deployKey)
- if err != nil {
- return nil, fmt.Errorf("unable to get deploy key %q: %w", cfg.deployKey, err)
+ defaultKey, _, ok := bk.GetFromNameOrAddress(cfg.deployKey)
+ if !ok {
+ return nil, fmt.Errorf("unable to get deploy key %q", cfg.deployKey)
}
for _, arg := range args {
- path, err := gnodev.ResolvePackagePathQuery(kb, arg)
+ path, err := gnodev.ResolvePackagePathQuery(bk, arg)
if err != nil {
return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err)
}
// Assign a default creator if user haven't specified it.
if path.Creator.IsZero() {
- path.Creator = defaultKey.GetAddress()
+ path.Creator = defaultKey
}
paths = append(paths, path)
@@ -410,7 +417,7 @@ func resolvePackagesPathFromArgs(cfg *devCfg, kb keys.Keybase, args []string) ([
if !cfg.minimal {
paths = append(paths, gnodev.PackagePath{
Path: filepath.Join(cfg.root, "examples"),
- Creator: defaultKey.GetAddress(),
+ Creator: defaultKey,
Deposit: nil,
})
}
diff --git a/contribs/gnodev/cmd/gnodev/setup_address_book.go b/contribs/gnodev/cmd/gnodev/setup_address_book.go
new file mode 100644
index 00000000000..cf3389fb8d3
--- /dev/null
+++ b/contribs/gnodev/cmd/gnodev/setup_address_book.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+ "fmt"
+ "log/slog"
+
+ "github.com/gnolang/gno/contribs/gnodev/pkg/address"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ osm "github.com/gnolang/gno/tm2/pkg/os"
+)
+
+func setupAddressBook(logger *slog.Logger, cfg *devCfg) (*address.Book, error) {
+ book := address.NewBook()
+
+ // Check for home folder
+ if cfg.home == "" {
+ logger.Warn("home not specified, no keybase will be loaded")
+ } else if !osm.DirExists(cfg.home) {
+ logger.Warn("keybase directory does not exist, no local keys will be imported",
+ "path", cfg.home)
+ } else if err := book.ImportKeybase(cfg.home); err != nil {
+ return nil, fmt.Errorf("unable to import local keybase %q: %w", cfg.home, err)
+ }
+
+ // Add additional accounts to our keybase
+ for acc := range cfg.premineAccounts {
+ if _, ok := book.GetByName(acc); ok {
+ continue // we already know this account from keybase
+ }
+
+ // Check if we have a valid bech32 address instead
+ addr, err := crypto.AddressFromBech32(acc)
+ if err != nil {
+ return nil, fmt.Errorf("invalid bech32 address or unkown keyname %q", acc)
+ }
+
+ book.Add(addr, "") // add addr to the book with no name
+
+ logger.Info("additional account added", "addr", addr.String())
+ }
+
+ // Ensure that we have a default address
+ names, ok := book.GetByAddress(DefaultDeployerAddress)
+ if ok {
+ // Account already exist in the keybase
+ if len(names) > 0 && names[0] != "" {
+ logger.Info("default address imported", "name", names[0], "addr", DefaultDeployerAddress.String())
+ } else {
+ logger.Info("default address imported", "addr", DefaultDeployerAddress.String())
+ }
+ return book, nil
+ }
+
+ // If the key isn't found, create a default one
+ creatorName := fmt.Sprintf("_default#%.6s", DefaultDeployerAddress.String())
+ book.Add(DefaultDeployerAddress, creatorName)
+
+ logger.Warn("default address created",
+ "name", creatorName,
+ "addr", DefaultDeployerAddress.String(),
+ "mnemonic", DefaultDeployerSeed,
+ )
+
+ return book, nil
+}
diff --git a/contribs/gnodev/cmd/gnodev/setup_keybase.go b/contribs/gnodev/cmd/gnodev/setup_keybase.go
deleted file mode 100644
index bea96293f41..00000000000
--- a/contribs/gnodev/cmd/gnodev/setup_keybase.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package main
-
-import (
- "fmt"
- "log/slog"
-
- "github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
- osm "github.com/gnolang/gno/tm2/pkg/os"
-)
-
-func setupKeybase(logger *slog.Logger, cfg *devCfg) (keys.Keybase, error) {
- kb := keys.NewInMemory()
-
- // Check for home folder
- if cfg.home == "" {
- logger.Warn("local keybase disabled")
- } else if !osm.DirExists(cfg.home) {
- logger.Warn("keybase directory does not exist, no local keys will be imported",
- "path", cfg.home)
- } else if err := importKeybase(kb, cfg.home); err != nil {
- return nil, fmt.Errorf("unable to import local keybase %q: %w", cfg.home, err)
- }
-
- // Add additional accounts to our keybase
- for acc := range cfg.additionalAccounts {
- // Check if the account exist in the local keybase
- if ok, _ := kb.HasByName(acc); ok {
- continue
- }
-
- // Check if we have a valid bech32 address instead
- addr, err := crypto.AddressFromBech32(acc)
- if err != nil {
- return nil, fmt.Errorf("invalid bech32 address or unkown key %q", acc)
- }
-
- // If we already know this address from keybase, skip it
- ok, err := kb.HasByAddress(addr)
- if ok {
- continue
- }
-
- // We don't know this address, then add it to our keybase
- pub, err := crypto.PubKeyFromBech32(acc)
- if err != nil {
- return nil, fmt.Errorf("unable to get PubKey from %q: %w", acc, err)
- }
-
- name := fmt.Sprintf("_account#%.6s", addr.String())
- info, err := kb.CreateOffline(name, pub)
- if err != nil {
- return nil, fmt.Errorf("unable to add additional account: %w", err)
- }
-
- logger.Info("additional account added",
- "name", info.GetName(),
- "addr", info.GetAddress())
- }
-
- // Ensure that we have a default address
- info, err := kb.GetByAddress(DefaultDeployerAddress)
- switch {
- case err == nil: // Account already exist in the keybase
- logger.Info("default address imported from keybase", "name", info.GetName(), "addr", info.GetAddress())
- case keyerror.IsErrKeyNotFound(err):
- // If the key isn't found, create a default one
- creatorName := fmt.Sprintf("_default#%.6s", DefaultDeployerAddress.String())
- if ok, _ := kb.HasByName(creatorName); ok {
- return nil, fmt.Errorf("unable to create default account, %q already exist in imported keybase", creatorName)
- }
-
- info, err = kb.CreateAccount(creatorName, DefaultDeployerSeed, "", "", 0, 0)
- if err != nil {
- return nil, fmt.Errorf("unable to create default account %q: %w", DefaultDeployerName, err)
- }
-
- logger.Warn("default address created",
- "name", info.GetName(),
- "addr", info.GetAddress(),
- "mnemonic", DefaultDeployerSeed,
- )
- default:
- return nil, fmt.Errorf("unable to get address %q: %w", info.GetAddress(), err)
- }
-
- return kb, nil
-}
-
-func importKeybase(to keys.Keybase, path string) error {
- // Load home keybase into our inMemory keybase
- from, err := keys.NewKeyBaseFromDir(path)
- if err != nil {
- return fmt.Errorf("unable to load keybase: %w", err)
- }
-
- keys, err := from.List()
- if err != nil {
- return fmt.Errorf("unable to list keys: %w", err)
- }
-
- for _, key := range keys {
- name := key.GetName()
- to.CreateOffline(name, key.GetPubKey())
- }
-
- return nil
-}
diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go
index dde94c843d2..c79ab9d18bf 100644
--- a/contribs/gnodev/cmd/gnodev/setup_node.go
+++ b/contribs/gnodev/cmd/gnodev/setup_node.go
@@ -10,7 +10,6 @@ import (
gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
"github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
)
// setupDevNode initializes and returns a new DevNode.
@@ -19,15 +18,9 @@ func setupDevNode(
logger *slog.Logger,
cfg *devCfg,
remitter emitter.Emitter,
- kb keys.Keybase,
+ balances gnoland.Balances,
pkgspath []gnodev.PackagePath,
) (*gnodev.Node, error) {
- balances, err := generateBalances(kb, cfg)
- if err != nil {
- return nil, fmt.Errorf("unable to generate balances: %w", err)
- }
- logger.Debug("balances loaded", "list", balances.List())
-
config := setupDevNodeConfig(cfg, balances, pkgspath)
return gnodev.NewDevNode(ctx, logger, remitter, config)
}
diff --git a/contribs/gnodev/pkg/address/book.go b/contribs/gnodev/pkg/address/book.go
new file mode 100644
index 00000000000..e6ad0d75f5e
--- /dev/null
+++ b/contribs/gnodev/pkg/address/book.go
@@ -0,0 +1,133 @@
+package address
+
+import (
+ "fmt"
+ "sort"
+
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+)
+
+type Entry struct {
+ crypto.Address
+ Names []string
+}
+
+type Book struct {
+ addrsToNames map[crypto.Address][]string // address -> []names
+ namesToAddrs map[string]crypto.Address // name -> address
+}
+
+func NewBook() *Book {
+ return &Book{
+ addrsToNames: map[crypto.Address][]string{},
+ namesToAddrs: map[string]crypto.Address{},
+ }
+}
+
+func remove(s []string, i int) []string {
+ s[len(s)-1], s[i] = s[i], s[len(s)-1]
+ return s[:len(s)-1]
+}
+
+func (bk *Book) Add(addr crypto.Address, name string) {
+ // Check and register address if it wasn't existing
+ names, ok := bk.addrsToNames[addr]
+ if !ok {
+ bk.addrsToNames[addr] = []string{}
+ }
+
+ // If name is empty, stop here
+ if name == "" {
+ return
+ }
+
+ oldAddr, ok := bk.namesToAddrs[name]
+ if !ok {
+ bk.namesToAddrs[name] = addr
+ bk.addrsToNames[addr] = append(names, name)
+ return
+ }
+
+ // Check if the association already exist
+ if oldAddr.Compare(addr) == 0 {
+ return // nothing to do
+ }
+
+ // If the name is associated with a different address, remove the old association
+ oldNames := bk.addrsToNames[oldAddr]
+ for i, oldName := range oldNames {
+ if oldName == name {
+ bk.addrsToNames[oldAddr] = remove(oldNames, i)
+ break
+ }
+ }
+
+ // Add the new association
+ bk.namesToAddrs[name] = addr
+ bk.addrsToNames[addr] = append(names, name)
+}
+
+func (bk Book) List() []Entry {
+ entries := make([]Entry, 0, len(bk.addrsToNames))
+ for addr, names := range bk.addrsToNames {
+ namesCopy := make([]string, len(names))
+ copy(namesCopy, names)
+
+ newEntry := Entry{
+ Address: addr,
+ Names: namesCopy,
+ }
+
+ // Find the correct place to insert newEntry using binary search.
+ i := sort.Search(len(entries), func(i int) bool {
+ return entries[i].Address.Compare(newEntry.Address) >= 0
+ })
+
+ entries = append(entries[:i], append([]Entry{newEntry}, entries[i:]...)...)
+ }
+
+ return entries
+}
+
+func (bk Book) GetByAddress(addr crypto.Address) (names []string, ok bool) {
+ names, ok = bk.addrsToNames[addr]
+ return
+}
+
+func (bk Book) GetByName(name string) (addr crypto.Address, ok bool) {
+ addr, ok = bk.namesToAddrs[name]
+ return
+}
+
+func (bk Book) GetFromNameOrAddress(addrOrName string) (addr crypto.Address, names []string, ok bool) {
+ var err error
+ if addr, ok = bk.namesToAddrs[addrOrName]; ok {
+ names = []string{addrOrName}
+ } else if addr, err = crypto.AddressFromBech32(addrOrName); err == nil {
+ // addr is valid, now check if we have it
+ names, ok = bk.addrsToNames[addr]
+ }
+
+ return
+}
+
+func (bk Book) ImportKeybase(path string) error {
+ kb, err := keys.NewKeyBaseFromDir(path)
+ if err != nil {
+ return fmt.Errorf("unable to load keybase: %w", err)
+ }
+ defer kb.CloseDB()
+
+ keys, err := kb.List()
+ if err != nil {
+ return fmt.Errorf("unable to list keys: %w", err)
+ }
+
+ for _, key := range keys {
+ name := key.GetName()
+ bk.Add(key.GetAddress(), name)
+ }
+
+ return nil
+}
diff --git a/contribs/gnodev/pkg/address/book_test.go b/contribs/gnodev/pkg/address/book_test.go
new file mode 100644
index 00000000000..45dca7df836
--- /dev/null
+++ b/contribs/gnodev/pkg/address/book_test.go
@@ -0,0 +1,109 @@
+package address
+
+import (
+ "testing"
+
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/jaekwon/testify/require"
+ "github.com/stretchr/testify/assert"
+)
+
+var testAddr = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
+
+func TestNewBook(t *testing.T) {
+ t.Parallel()
+
+ bk := NewBook()
+ assert.Empty(t, bk.addrsToNames)
+ assert.Empty(t, bk.namesToAddrs)
+}
+
+func TestAddEmptyName(t *testing.T) {
+ t.Parallel()
+
+ bk := NewBook()
+
+ // Add address
+ bk.Add(testAddr, "")
+
+ names, ok := bk.GetByAddress(testAddr)
+ require.True(t, ok)
+ require.Equal(t, 0, len(names))
+}
+
+func TestAdd(t *testing.T) {
+ t.Parallel()
+
+ bk := NewBook()
+
+ // Add address
+ bk.Add(testAddr, "testname")
+
+ t.Run("get by address", func(t *testing.T) {
+ names, ok := bk.GetByAddress(testAddr)
+ require.True(t, ok)
+ require.Equal(t, 1, len(names))
+ assert.Equal(t, "testname", names[0])
+ })
+
+ t.Run("get by name", func(t *testing.T) {
+ addrFromName, ok := bk.GetByName("testname")
+ assert.True(t, ok)
+ assert.True(t, addrFromName.Compare(testAddr) == 0)
+ })
+
+ // Add same address with a new name
+ bk.Add(testAddr, "testname2")
+
+ t.Run("get two names with same address", func(t *testing.T) {
+ // Get by name
+ addr1, ok := bk.GetByName("testname")
+ require.True(t, ok)
+ addr2, ok := bk.GetByName("testname2")
+ require.True(t, ok)
+ assert.True(t, addr1.Compare(addr2) == 0)
+ })
+}
+
+func TestList(t *testing.T) {
+ t.Parallel()
+
+ bk := NewBook()
+
+ bk.Add(testAddr, "testname")
+
+ entries := bk.List()
+
+ assert.Equal(t, 1, len(entries))
+ entry := entries[0]
+
+ assert.True(t, testAddr.Compare(entry.Address) == 0)
+ assert.Equal(t, 1, len(entries[0].Names))
+ assert.Equal(t, "testname", entries[0].Names[0])
+}
+
+func TestGetFromNameOrAddress(t *testing.T) {
+ t.Parallel()
+
+ bk := NewBook()
+
+ t.Run("failure", func(t *testing.T) {
+ resultAddr, names, ok := bk.GetFromNameOrAddress("unkown_key")
+ assert.False(t, ok)
+ assert.True(t, resultAddr.IsZero())
+ assert.Len(t, names, 0)
+ })
+
+ // Add address
+ bk.Add(testAddr, "testname")
+
+ for _, addrOrName := range []string{"testname", testAddr.String()} {
+ t.Run(addrOrName, func(t *testing.T) {
+ resultAddr, names, ok := bk.GetFromNameOrAddress("testname")
+ require.True(t, ok)
+ require.Len(t, names, 1)
+ assert.Equal(t, "testname", names[0])
+ assert.True(t, resultAddr.Compare(testAddr) == 0)
+ })
+ }
+}
diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go
index 0dd67e61ac4..f58775277e9 100644
--- a/contribs/gnodev/pkg/dev/packages.go
+++ b/contribs/gnodev/pkg/dev/packages.go
@@ -6,11 +6,11 @@ import (
"net/url"
"path/filepath"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/address"
vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/std"
)
@@ -20,7 +20,7 @@ type PackagePath struct {
Deposit std.Coins
}
-func ResolvePackagePathQuery(kb keys.Keybase, path string) (PackagePath, error) {
+func ResolvePackagePathQuery(bk *address.Book, path string) (PackagePath, error) {
var ppath PackagePath
upath, err := url.Parse(path)
@@ -34,12 +34,11 @@ func ResolvePackagePathQuery(kb keys.Keybase, path string) (PackagePath, error)
if creator != "" {
address, err := crypto.AddressFromBech32(creator)
if err != nil {
- info, nameErr := kb.GetByName(creator)
- if nameErr != nil {
+ var ok bool
+ address, ok = bk.GetByName(creator)
+ if !ok {
return ppath, fmt.Errorf("invalid name or address for creator %q", creator)
}
-
- address = info.GetAddress()
}
ppath.Creator = address
From f2593b02c73915a1014f87c29c2b442203d9ed47 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:38:42 +0200
Subject: [PATCH 16/23] fix: lint error
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/accounts.go | 1 -
contribs/gnodev/cmd/gnodev/setup_address_book.go | 2 +-
contribs/gnodev/pkg/address/book_test.go | 10 +++++++++-
contribs/gnodev/pkg/dev/packages_test.go | 13 ++++++-------
4 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go
index e725aa1017d..1b695d6e1d0 100644
--- a/contribs/gnodev/cmd/gnodev/accounts.go
+++ b/contribs/gnodev/cmd/gnodev/accounts.go
@@ -73,7 +73,6 @@ func generateBalances(bk *address.Book, cfg *devCfg) (gnoland.Balances, error) {
found = preDefinedFound
break
}
-
}
bls[address] = gnoland.Balance{Amount: found, Address: address}
diff --git a/contribs/gnodev/cmd/gnodev/setup_address_book.go b/contribs/gnodev/cmd/gnodev/setup_address_book.go
index cf3389fb8d3..a1a1c8f58ac 100644
--- a/contribs/gnodev/cmd/gnodev/setup_address_book.go
+++ b/contribs/gnodev/cmd/gnodev/setup_address_book.go
@@ -31,7 +31,7 @@ func setupAddressBook(logger *slog.Logger, cfg *devCfg) (*address.Book, error) {
// Check if we have a valid bech32 address instead
addr, err := crypto.AddressFromBech32(acc)
if err != nil {
- return nil, fmt.Errorf("invalid bech32 address or unkown keyname %q", acc)
+ return nil, fmt.Errorf("invalid bech32 address or unknown keyname %q", acc)
}
book.Add(addr, "") // add addr to the book with no name
diff --git a/contribs/gnodev/pkg/address/book_test.go b/contribs/gnodev/pkg/address/book_test.go
index 45dca7df836..de177a2a714 100644
--- a/contribs/gnodev/pkg/address/book_test.go
+++ b/contribs/gnodev/pkg/address/book_test.go
@@ -40,6 +40,8 @@ func TestAdd(t *testing.T) {
bk.Add(testAddr, "testname")
t.Run("get by address", func(t *testing.T) {
+ t.Parallel()
+
names, ok := bk.GetByAddress(testAddr)
require.True(t, ok)
require.Equal(t, 1, len(names))
@@ -47,6 +49,8 @@ func TestAdd(t *testing.T) {
})
t.Run("get by name", func(t *testing.T) {
+ t.Parallel()
+
addrFromName, ok := bk.GetByName("testname")
assert.True(t, ok)
assert.True(t, addrFromName.Compare(testAddr) == 0)
@@ -56,6 +60,8 @@ func TestAdd(t *testing.T) {
bk.Add(testAddr, "testname2")
t.Run("get two names with same address", func(t *testing.T) {
+ t.Parallel()
+
// Get by name
addr1, ok := bk.GetByName("testname")
require.True(t, ok)
@@ -88,7 +94,9 @@ func TestGetFromNameOrAddress(t *testing.T) {
bk := NewBook()
t.Run("failure", func(t *testing.T) {
- resultAddr, names, ok := bk.GetFromNameOrAddress("unkown_key")
+ t.Parallel()
+
+ resultAddr, names, ok := bk.GetFromNameOrAddress("unknown_key")
assert.False(t, ok)
assert.True(t, resultAddr.IsZero())
assert.Len(t, names, 0)
diff --git a/contribs/gnodev/pkg/dev/packages_test.go b/contribs/gnodev/pkg/dev/packages_test.go
index b262617c09f..760d3cdef80 100644
--- a/contribs/gnodev/pkg/dev/packages_test.go
+++ b/contribs/gnodev/pkg/dev/packages_test.go
@@ -3,8 +3,8 @@ package dev
import (
"testing"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/address"
"github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -12,13 +12,12 @@ import (
func TestResolvePackagePathQuery(t *testing.T) {
var (
- testingName = "testAccount"
- testingMnemonic = `special hip mail knife manual boy essay certain broccoli group token exchange problem subject garbage chaos program monitor happy magic upgrade kingdom cluster enemy`
- testingAddress = crypto.MustAddressFromString("g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na")
+ testingName = "testAccount"
+ testingAddress = crypto.MustAddressFromString("g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na")
)
- kb := keys.NewInMemory()
- kb.CreateAccount(testingName, testingMnemonic, "", "", 0, 0)
+ book := address.NewBook()
+ book.Add(testingAddress, testingName)
cases := []struct {
Path string
@@ -56,7 +55,7 @@ func TestResolvePackagePathQuery(t *testing.T) {
for _, tc := range cases {
t.Run(tc.Path, func(t *testing.T) {
- result, err := ResolvePackagePathQuery(kb, tc.Path)
+ result, err := ResolvePackagePathQuery(book, tc.Path)
if tc.ShouldFail {
assert.Error(t, err)
return
From 0abe9266c7fcd00673bb8dc93939617a99f20701 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:42:43 +0200
Subject: [PATCH 17/23] chore: move balance types in its own file
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
gno.land/pkg/gnoland/balance.go | 150 ++++++++++++++++++
.../{types_test.go => balance_test.go} | 0
gno.land/pkg/gnoland/types.go | 144 -----------------
3 files changed, 150 insertions(+), 144 deletions(-)
create mode 100644 gno.land/pkg/gnoland/balance.go
rename gno.land/pkg/gnoland/{types_test.go => balance_test.go} (100%)
diff --git a/gno.land/pkg/gnoland/balance.go b/gno.land/pkg/gnoland/balance.go
new file mode 100644
index 00000000000..807da4cf41f
--- /dev/null
+++ b/gno.land/pkg/gnoland/balance.go
@@ -0,0 +1,150 @@
+package gnoland
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strings"
+
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+type Balance struct {
+ Address bft.Address
+ Amount std.Coins
+}
+
+func (b *Balance) Verify() error {
+ if b.Address.IsZero() {
+ return ErrBalanceEmptyAddress
+ }
+
+ if b.Amount.Len() == 0 {
+ return ErrBalanceEmptyAmount
+ }
+
+ return nil
+}
+
+func (b *Balance) Parse(entry string) error {
+ parts := strings.Split(strings.TrimSpace(entry), "=") // =
+ if len(parts) != 2 {
+ return fmt.Errorf("malformed entry: %q", entry)
+ }
+
+ var err error
+
+ b.Address, err = crypto.AddressFromBech32(parts[0])
+ if err != nil {
+ return fmt.Errorf("invalid address %q: %w", parts[0], err)
+ }
+
+ b.Amount, err = std.ParseCoins(parts[1])
+ if err != nil {
+ return fmt.Errorf("invalid amount %q: %w", parts[1], err)
+ }
+
+ return nil
+}
+
+func (b *Balance) UnmarshalAmino(rep string) error {
+ return b.Parse(rep)
+}
+
+func (b Balance) MarshalAmino() (string, error) {
+ return b.String(), nil
+}
+
+func (b Balance) String() string {
+ return fmt.Sprintf("%s=%s", b.Address.String(), b.Amount.String())
+}
+
+type Balances map[crypto.Address]Balance
+
+func NewBalances() Balances {
+ return make(Balances)
+}
+
+func (bs Balances) Set(address crypto.Address, amount std.Coins) {
+ bs[address] = Balance{
+ Address: address,
+ Amount: amount,
+ }
+}
+
+func (bs Balances) Get(address crypto.Address) (balance Balance, ok bool) {
+ balance, ok = bs[address]
+ return
+}
+
+func (bs Balances) List() []Balance {
+ list := make([]Balance, 0, len(bs))
+ for _, balance := range bs {
+ list = append(list, balance)
+ }
+ return list
+}
+
+// LeftMerge left-merges the two maps
+func (bs Balances) LeftMerge(from Balances) {
+ for key, bVal := range from {
+ if _, present := (bs)[key]; !present {
+ (bs)[key] = bVal
+ }
+ }
+}
+
+func GetBalancesFromEntries(entries ...string) (Balances, error) {
+ balances := NewBalances()
+ return balances, balances.LoadFromEntries(entries...)
+}
+
+// LoadFromEntries extracts the balance entries in the form of =
+func (bs Balances) LoadFromEntries(entries ...string) error {
+ for _, entry := range entries {
+ var balance Balance
+ if err := balance.Parse(entry); err != nil {
+ return fmt.Errorf("unable to parse balance entry: %w", err)
+ }
+ bs[balance.Address] = balance
+ }
+
+ return nil
+}
+
+func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
+ balances := NewBalances()
+ return balances, balances.LoadFromSheet(sheet)
+}
+
+// LoadFromSheet extracts the balance sheet from the passed in
+// balance sheet file, that has the format of =ugnot
+func (bs Balances) LoadFromSheet(sheet io.Reader) error {
+ // Parse the balances
+ scanner := bufio.NewScanner(sheet)
+
+ for scanner.Scan() {
+ entry := scanner.Text()
+
+ // Remove comments
+ entry = strings.Split(entry, "#")[0]
+ entry = strings.TrimSpace(entry)
+
+ // Skip empty lines
+ if entry == "" {
+ continue
+ }
+
+ if err := bs.LoadFromEntries(entry); err != nil {
+ return fmt.Errorf("unable to load entries: %w", err)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("error encountered while scanning, %w", err)
+ }
+
+ return nil
+}
diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/balance_test.go
similarity index 100%
rename from gno.land/pkg/gnoland/types_test.go
rename to gno.land/pkg/gnoland/balance_test.go
diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go
index 36ea3384c3c..016f3279dbd 100644
--- a/gno.land/pkg/gnoland/types.go
+++ b/gno.land/pkg/gnoland/types.go
@@ -1,14 +1,8 @@
package gnoland
import (
- "bufio"
"errors"
- "fmt"
- "io"
- "strings"
- bft "github.com/gnolang/gno/tm2/pkg/bft/types"
- "github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/std"
)
@@ -29,141 +23,3 @@ type GnoGenesisState struct {
Balances []Balance `json:"balances"`
Txs []std.Tx `json:"txs"`
}
-
-type Balance struct {
- Address bft.Address
- Amount std.Coins
-}
-
-func (b *Balance) Verify() error {
- if b.Address.IsZero() {
- return ErrBalanceEmptyAddress
- }
-
- if b.Amount.Len() == 0 {
- return ErrBalanceEmptyAmount
- }
-
- return nil
-}
-
-func (b *Balance) Parse(entry string) error {
- parts := strings.Split(strings.TrimSpace(entry), "=") // =
- if len(parts) != 2 {
- return fmt.Errorf("malformed entry: %q", entry)
- }
-
- var err error
-
- b.Address, err = crypto.AddressFromBech32(parts[0])
- if err != nil {
- return fmt.Errorf("invalid address %q: %w", parts[0], err)
- }
-
- b.Amount, err = std.ParseCoins(parts[1])
- if err != nil {
- return fmt.Errorf("invalid amount %q: %w", parts[1], err)
- }
-
- return nil
-}
-
-func (b *Balance) UnmarshalAmino(rep string) error {
- return b.Parse(rep)
-}
-
-func (b Balance) MarshalAmino() (string, error) {
- return b.String(), nil
-}
-
-func (b Balance) String() string {
- return fmt.Sprintf("%s=%s", b.Address.String(), b.Amount.String())
-}
-
-type Balances map[crypto.Address]Balance
-
-func NewBalances() Balances {
- return make(Balances)
-}
-
-func (bs Balances) Set(address crypto.Address, amount std.Coins) {
- bs[address] = Balance{
- Address: address,
- Amount: amount,
- }
-}
-
-func (bs Balances) Get(address crypto.Address) (balance Balance, ok bool) {
- balance, ok = bs[address]
- return
-}
-
-func (bs Balances) List() []Balance {
- list := make([]Balance, 0, len(bs))
- for _, balance := range bs {
- list = append(list, balance)
- }
- return list
-}
-
-// leftMerge left-merges the two maps
-func (bs Balances) LeftMerge(from Balances) {
- for key, bVal := range from {
- if _, present := (bs)[key]; !present {
- (bs)[key] = bVal
- }
- }
-}
-
-func GetBalancesFromEntries(entries ...string) (Balances, error) {
- balances := NewBalances()
- return balances, balances.LoadFromEntries(entries...)
-}
-
-// LoadFromEntries extracts the balance entries in the form of =
-func (bs Balances) LoadFromEntries(entries ...string) error {
- for _, entry := range entries {
- var balance Balance
- if err := balance.Parse(entry); err != nil {
- return fmt.Errorf("unable to parse balance entry: %w", err)
- }
- bs[balance.Address] = balance
- }
-
- return nil
-}
-
-func GetBalancesFromSheet(sheet io.Reader) (Balances, error) {
- balances := NewBalances()
- return balances, balances.LoadFromSheet(sheet)
-}
-
-// LoadFromSheet extracts the balance sheet from the passed in
-// balance sheet file, that has the format of =ugnot
-func (bs Balances) LoadFromSheet(sheet io.Reader) error {
- // Parse the balances
- scanner := bufio.NewScanner(sheet)
-
- for scanner.Scan() {
- entry := scanner.Text()
-
- // Remove comments
- entry = strings.Split(entry, "#")[0]
- entry = strings.TrimSpace(entry)
-
- // Skip empty lines
- if entry == "" {
- continue
- }
-
- if err := bs.LoadFromEntries(entry); err != nil {
- return fmt.Errorf("unable to load entries: %w", err)
- }
- }
-
- if err := scanner.Err(); err != nil {
- return fmt.Errorf("error encountered while scanning, %w", err)
- }
-
- return nil
-}
From e4ac3c118a6089c4781ac268117a0f9182188749 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:43:26 +0200
Subject: [PATCH 18/23] chore: add parallel to some tests
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/pkg/dev/packages_test.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/contribs/gnodev/pkg/dev/packages_test.go b/contribs/gnodev/pkg/dev/packages_test.go
index 760d3cdef80..dbae99b4484 100644
--- a/contribs/gnodev/pkg/dev/packages_test.go
+++ b/contribs/gnodev/pkg/dev/packages_test.go
@@ -11,6 +11,8 @@ import (
)
func TestResolvePackagePathQuery(t *testing.T) {
+ t.Parallel()
+
var (
testingName = "testAccount"
testingAddress = crypto.MustAddressFromString("g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na")
From 5e248a3f51ffaaa9ea6d09539201426b2f3d3273 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:50:29 +0200
Subject: [PATCH 19/23] chore: update doc
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/main.go | 2 +-
docs/gno-tooling/cli/gnodev.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 0f3e962feaf..652b4a862a3 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -128,7 +128,7 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
fs.Var(
&c.premineAccounts,
"add-account",
- "add (or set) a premine account in the form `[:]`, can be used multiple time",
+ "add (or set) a premine account in the form `[=]`, can be used multiple time",
)
fs.StringVar(
diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md
index 74d60739f0c..97a4cc2e5ce 100644
--- a/docs/gno-tooling/cli/gnodev.md
+++ b/docs/gno-tooling/cli/gnodev.md
@@ -109,7 +109,7 @@ While `gnodev` is running, the following shortcuts are available:
|---------------------|------------------------------------------------------------|
| --minimal | Start `gnodev` without loading the examples folder. |
| --no-watch | Disable hot reload. |
-| --add-account | Pre-add account(s) in the form `[:]` |
+| --add-account | Pre-add account(s) in the form `[=]` |
| --balances-file | Load a balance for the user(s) from a balance file. |
| --chain-id | Set node ChainID |
| --deploy-key | Default key name or Bech32 address for uploading packages. |
From 2442f1ea04743c899f2a3ac9d02f16a65ceb3939 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:55:21 +0200
Subject: [PATCH 20/23] chore: remove testify jaekwon deps from bad merge
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/go.mod | 2 +-
contribs/gnodev/pkg/address/book_test.go | 2 +-
gno.land/pkg/gnoland/balance_test.go | 4 ++--
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod
index 90bef4b2b13..8b66f72d288 100644
--- a/contribs/gnodev/go.mod
+++ b/contribs/gnodev/go.mod
@@ -11,6 +11,7 @@ require (
github.com/gnolang/gno v0.0.0-00010101000000-000000000000
github.com/gorilla/websocket v1.5.1
github.com/muesli/termenv v0.15.2
+ github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
golang.org/x/term v0.18.0
)
@@ -48,7 +49,6 @@ require (
github.com/rivo/uniseg v0.4.3 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.10.1 // indirect
- github.com/stretchr/testify v1.9.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
diff --git a/contribs/gnodev/pkg/address/book_test.go b/contribs/gnodev/pkg/address/book_test.go
index de177a2a714..80249762455 100644
--- a/contribs/gnodev/pkg/address/book_test.go
+++ b/contribs/gnodev/pkg/address/book_test.go
@@ -4,8 +4,8 @@ import (
"testing"
"github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/jaekwon/testify/require"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var testAddr = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
diff --git a/gno.land/pkg/gnoland/balance_test.go b/gno.land/pkg/gnoland/balance_test.go
index 0cc2424fc7e..63e80e3b41b 100644
--- a/gno.land/pkg/gnoland/balance_test.go
+++ b/gno.land/pkg/gnoland/balance_test.go
@@ -15,8 +15,8 @@ import (
"github.com/gnolang/gno/tm2/pkg/crypto/keys/client"
"github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
"github.com/gnolang/gno/tm2/pkg/std"
- "github.com/jaekwon/testify/assert"
- "github.com/jaekwon/testify/require"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestBalance_Verify(t *testing.T) {
From 766585ca13a3be1ec88562a5def286a07481ed18 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 17:06:16 +0200
Subject: [PATCH 21/23] fix: balances tests
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
gno.land/pkg/gnoland/balance_test.go | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/gno.land/pkg/gnoland/balance_test.go b/gno.land/pkg/gnoland/balance_test.go
index 63e80e3b41b..59dffcc4333 100644
--- a/gno.land/pkg/gnoland/balance_test.go
+++ b/gno.land/pkg/gnoland/balance_test.go
@@ -142,9 +142,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
}
balanceMap, err := GetBalancesFromEntries(entries...)
- require.NoError(t, err)
-
- assert.Nil(t, balanceMap)
+ assert.Len(t, balanceMap, 0)
assert.Contains(t, err.Error(), "malformed entry")
})
@@ -156,10 +154,8 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
}
balanceMap, err := GetBalancesFromEntries(balances...)
- require.NoError(t, err)
-
- assert.Nil(t, balanceMap)
- assert.Contains(t, err.Error(), "invalid address")
+ assert.Len(t, balanceMap, 0)
+ assert.ErrorContains(t, err, "invalid address")
})
t.Run("malformed balance, invalid amount", func(t *testing.T) {
@@ -176,9 +172,8 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
}
balanceMap, err := GetBalancesFromEntries(balances...)
-
- assert.Nil(t, balanceMap)
- assert.Contains(t, err.Error(), "invalid amount")
+ assert.Len(t, balanceMap, 0)
+ assert.ErrorContains(t, err, "invalid amount")
})
}
@@ -230,7 +225,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) {
balanceMap, err := GetBalancesFromSheet(reader)
- assert.Nil(t, balanceMap)
+ assert.Len(t, balanceMap, 0)
assert.Contains(t, err.Error(), "invalid amount")
})
}
From 1f52b90ace633319e30dfbe04ec4eb2fb4a13f14 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Tue, 23 Apr 2024 17:23:03 +0200
Subject: [PATCH 22/23] chore: rename unlimitedFund -> premineBalance
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/accounts.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go
index 1b695d6e1d0..b263cc44f70 100644
--- a/contribs/gnodev/cmd/gnodev/accounts.go
+++ b/contribs/gnodev/cmd/gnodev/accounts.go
@@ -50,7 +50,7 @@ func (va varPremineAccounts) String() string {
func generateBalances(bk *address.Book, cfg *devCfg) (gnoland.Balances, error) {
bls := gnoland.NewBalances()
- unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
+ premineBalance := std.Coins{std.NewCoin("ugnot", 10e12)}
entries := bk.List()
@@ -67,7 +67,7 @@ func generateBalances(bk *address.Book, cfg *devCfg) (gnoland.Balances, error) {
}
// Check for name
- found := unlimitedFund
+ found := premineBalance
for _, name := range entry.Names {
if preDefinedFound, ok := cfg.premineAccounts[name]; ok && preDefinedFound != nil {
found = preDefinedFound
From 55b863f64a5644f0249a15750e6429174834019c Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Wed, 24 Apr 2024 10:34:07 +0200
Subject: [PATCH 23/23] chore: improve book readability
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/pkg/address/book.go | 31 +++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/contribs/gnodev/pkg/address/book.go b/contribs/gnodev/pkg/address/book.go
index e6ad0d75f5e..983fced5882 100644
--- a/contribs/gnodev/pkg/address/book.go
+++ b/contribs/gnodev/pkg/address/book.go
@@ -8,11 +8,8 @@ import (
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
)
-type Entry struct {
- crypto.Address
- Names []string
-}
-
+// Book reference a list of addresses optionally associated with a name
+// It is not thread safe.
type Book struct {
addrsToNames map[crypto.Address][]string // address -> []names
namesToAddrs map[string]crypto.Address // name -> address
@@ -25,12 +22,16 @@ func NewBook() *Book {
}
}
-func remove(s []string, i int) []string {
- s[len(s)-1], s[i] = s[i], s[len(s)-1]
- return s[:len(s)-1]
-}
-
+// Add inserts a new address into the address book linked to the specified name.
+// An address can be associated with multiple names, yet each name can only
+// belong to one address. Hence, if a name is reused, it will replace the
+// reference to the previous address.
+// Adding an address without a name is permissible.
func (bk *Book) Add(addr crypto.Address, name string) {
+ if addr.IsZero() {
+ panic("empty address not allowed")
+ }
+
// Check and register address if it wasn't existing
names, ok := bk.addrsToNames[addr]
if !ok {
@@ -68,6 +69,11 @@ func (bk *Book) Add(addr crypto.Address, name string) {
bk.addrsToNames[addr] = append(names, name)
}
+type Entry struct {
+ crypto.Address
+ Names []string
+}
+
func (bk Book) List() []Entry {
entries := make([]Entry, 0, len(bk.addrsToNames))
for addr, names := range bk.addrsToNames {
@@ -131,3 +137,8 @@ func (bk Book) ImportKeybase(path string) error {
return nil
}
+
+func remove(s []string, i int) []string {
+ s[len(s)-1], s[i] = s[i], s[len(s)-1]
+ return s[:len(s)-1]
+}