Skip to content

Commit

Permalink
feat(gnodev): txs manipulation ability (#2286)
Browse files Browse the repository at this point in the history
  • Loading branch information
gfanton authored Jul 8, 2024
1 parent 62fc9b4 commit c5a79ab
Show file tree
Hide file tree
Showing 11 changed files with 585 additions and 84 deletions.
102 changes: 94 additions & 8 deletions contribs/gnodev/cmd/gnodev/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"log/slog"
Expand Down Expand Up @@ -30,6 +31,8 @@ const (
AccountsLogName = "Accounts"
)

var ErrConflictingFileArgs = errors.New("cannot specify `balances-file` or `txs-file` along with `genesis-file`")

var (
DefaultDeployerName = integration.DefaultAccount_Name
DefaultDeployerAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address)
Expand All @@ -47,8 +50,11 @@ type devCfg struct {
home string
root string
premineAccounts varPremineAccounts
balancesFile string
txsFile string

// Files
balancesFile string
genesisFile string
txsFile string

// Web Configuration
webListenerAddr string
Expand Down Expand Up @@ -155,6 +161,13 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
"load the provided transactions file (refer to the documentation for format)",
)

fs.StringVar(
&c.genesisFile,
"genesis",
defaultDevOptions.genesisFile,
"load the given genesis file",
)

fs.StringVar(
&c.deployKey,
"deploy-key",
Expand Down Expand Up @@ -219,10 +232,22 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
)
}

func (c *devCfg) validateConfigFlags() error {
if (c.balancesFile != "" || c.txsFile != "") && c.genesisFile != "" {
return ErrConflictingFileArgs
}

return nil
}

func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
ctx, cancel := context.WithCancelCause(context.Background())
defer cancel(nil)

if err := cfg.validateConfigFlags(); err != nil {
return fmt.Errorf("validate error: %w", err)
}

// Setup Raw Terminal
rt, restore, err := setupRawTerm(cfg, io)
if err != nil {
Expand Down Expand Up @@ -262,7 +287,8 @@ 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
nodeLogger := logger.WithGroup(NodeLogName)
devNode, err := setupDevNode(ctx, nodeLogger, cfg, emitterServer, balances, pkgpaths)
nodeCfg := setupDevNodeConfig(cfg, logger, emitterServer, balances, pkgpaths)
devNode, err := setupDevNode(ctx, cfg, nodeCfg)
if err != nil {
return err
}
Expand Down Expand Up @@ -337,11 +363,15 @@ func execDev(cfg *devCfg, args []string, io commands.IO) (err error) {
var helper string = `For more in-depth documentation, visit the GNO Tooling CLI documentation:
https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev
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
Ctrl+C Exit - Exit the application
P Previous TX - Go to the previous tx
N Next TX - Go to the next tx
E Export - Export the current state as genesis doc
A Accounts - Display known accounts and balances
H Help - Display this message
R Reload - Reload all packages to take change into account.
Ctrl+S Save State - Save the current state
Ctrl+R Reset - Reset application to it's initial/save state.
Ctrl+C Exit - Exit the application
`

func runEventLoop(
Expand All @@ -352,6 +382,20 @@ func runEventLoop(
dnode *gnodev.Node,
watch *watcher.PackageWatcher,
) error {
// XXX: move this in above, but we need to have a proper struct first
// XXX: make this configurable
var exported uint
path, err := os.MkdirTemp("", "gnodev-export")
if err != nil {
return fmt.Errorf("unable to create `export` directory: %w", err)
}

defer func() {
if exported == 0 {
_ = os.RemoveAll(path)
}
}()

keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt)
for {
var err error
Expand Down Expand Up @@ -387,8 +431,10 @@ func runEventLoop(
switch key.Upper() {
case rawterm.KeyH: // Helper
logger.Info("Gno Dev Helper", "helper", helper)

case rawterm.KeyA: // Accounts
logAccounts(logger.WithGroup(AccountsLogName), bk, dnode)

case rawterm.KeyR: // Reload
logger.WithGroup(NodeLogName).Info("reloading...")
if err = dnode.ReloadAll(ctx); err != nil {
Expand All @@ -403,8 +449,48 @@ func runEventLoop(
Error("unable to reset node state", "err", err)
}

case rawterm.KeyCtrlS: // Save
logger.WithGroup(NodeLogName).Info("saving state...")
if err := dnode.SaveCurrentState(ctx); err != nil {
logger.WithGroup(NodeLogName).
Error("unable to save node state", "err", err)
}

case rawterm.KeyE:
logger.WithGroup(NodeLogName).Info("exporting state...")
doc, err := dnode.ExportStateAsGenesis(ctx)
if err != nil {
logger.WithGroup(NodeLogName).
Error("unable to export node state", "err", err)
continue
}

docfile := filepath.Join(path, fmt.Sprintf("export_%d.jsonl", exported))
if err := doc.SaveAs(docfile); err != nil {
logger.WithGroup(NodeLogName).
Error("unable to save genesis", "err", err)
}
exported++

logger.WithGroup(NodeLogName).Info("node state exported", "file", docfile)

case rawterm.KeyN: // Next tx
logger.Info("moving forward...")
if err := dnode.MoveToNextTX(ctx); err != nil {
logger.WithGroup(NodeLogName).
Error("unable to move forward", "err", err)
}

case rawterm.KeyP: // Next tx
logger.Info("moving backward...")
if err := dnode.MoveToPreviousTX(ctx); err != nil {
logger.WithGroup(NodeLogName).
Error("unable to move backward", "err", err)
}

case rawterm.KeyCtrlC: // Exit
return nil

default:
}

Expand Down
56 changes: 42 additions & 14 deletions contribs/gnodev/cmd/gnodev/setup_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,57 @@ 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/std"
"github.com/gnolang/gno/tm2/pkg/bft/types"
)

// setupDevNode initializes and returns a new DevNode.
func setupDevNode(
ctx context.Context,
logger *slog.Logger,
cfg *devCfg,
remitter emitter.Emitter,
balances gnoland.Balances,
pkgspath []gnodev.PackagePath,
devCfg *devCfg,
nodeConfig *gnodev.NodeConfig,
) (*gnodev.Node, error) {
// Load transactions.
txs, err := parseTxs(cfg.txsFile)
if err != nil {
return nil, fmt.Errorf("unable to load transactions: %w", err)
logger := nodeConfig.Logger

if devCfg.txsFile != "" { // Load txs files
var err error
nodeConfig.InitialTxs, err = parseTxs(devCfg.txsFile)
if err != nil {
return nil, fmt.Errorf("unable to load transactions: %w", err)
}
} else if devCfg.genesisFile != "" { // Load genesis file
state, err := extractAppStateFromGenesisFile(devCfg.genesisFile)
if err != nil {
return nil, fmt.Errorf("unable to load genesis file %q: %w", devCfg.genesisFile, err)
}

// Override balances and txs
nodeConfig.BalancesList = state.Balances
nodeConfig.InitialTxs = state.Txs

logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(nodeConfig.InitialTxs))
}

config := setupDevNodeConfig(cfg, balances, pkgspath, txs)
return gnodev.NewDevNode(ctx, logger, remitter, config)
return gnodev.NewDevNode(ctx, nodeConfig)
}

// setupDevNodeConfig creates and returns a new dev.NodeConfig.
func setupDevNodeConfig(
cfg *devCfg,
logger *slog.Logger,
emitter emitter.Emitter,
balances gnoland.Balances,
pkgspath []gnodev.PackagePath,
txs []std.Tx,
) *gnodev.NodeConfig {
config := gnodev.DefaultNodeConfig(cfg.root)

config.Logger = logger
config.Emitter = emitter
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
config.Txs = txs

// other listeners
config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr
Expand All @@ -55,6 +69,20 @@ func setupDevNodeConfig(
return config
}

func extractAppStateFromGenesisFile(path string) (*gnoland.GnoGenesisState, error) {
doc, err := types.GenesisDocFromFile(path)
if err != nil {
return nil, fmt.Errorf("unable to parse doc file: %w", err)
}

state, ok := doc.AppState.(gnoland.GnoGenesisState)
if !ok {
return nil, fmt.Errorf("invalid `GnoGenesisState` app state")
}

return &state, nil
}

func resolveUnixOrTCPAddr(in string) (out string) {
var err error
var addr net.Addr
Expand Down
2 changes: 1 addition & 1 deletion contribs/gnodev/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ require (
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.11.0 // indirect
github.com/rs/xid v1.5.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contribs/gnodev/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions contribs/gnodev/internal/mock/server_emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
"github.com/gnolang/gno/contribs/gnodev/pkg/events"
)

// Define empty event for NextEvent empty queue
var (
eventNull = events.Custom("NULL")
EvtNull = eventNull.Type()
)

// ServerEmitter is an `emitter.Emitter`
var _ emitter.Emitter = (*ServerEmitter)(nil)

Expand All @@ -23,6 +29,8 @@ func (m *ServerEmitter) Emit(evt events.Event) {
}

func (m *ServerEmitter) NextEvent() (evt events.Event) {
evt = eventNull

m.muEvents.Lock()
defer m.muEvents.Unlock()

Expand Down
Loading

0 comments on commit c5a79ab

Please sign in to comment.