diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go index 2038dc647e7..45a47e00b5b 100644 --- a/contribs/gnodev/cmd/gnodev/main.go +++ b/contribs/gnodev/cmd/gnodev/main.go @@ -49,6 +49,7 @@ type devCfg struct { root string premineAccounts varPremineAccounts balancesFile string + txsFile string // Node Configuration minimal bool @@ -136,6 +137,13 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { "load the provided balance file (refer to the documentation for format)", ) + fs.StringVar( + &c.txsFile, + "txs-file", + defaultDevOptions.txsFile, + "load the provided transactions file (refer to the documentation for format)", + ) + fs.StringVar( &c.deployKey, "deploy-key", diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go index c79ab9d18bf..55da1a596aa 100644 --- a/contribs/gnodev/cmd/gnodev/setup_node.go +++ b/contribs/gnodev/cmd/gnodev/setup_node.go @@ -10,6 +10,7 @@ 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" ) // setupDevNode initializes and returns a new DevNode. @@ -21,12 +22,23 @@ func setupDevNode( balances gnoland.Balances, pkgspath []gnodev.PackagePath, ) (*gnodev.Node, error) { - config := setupDevNodeConfig(cfg, balances, pkgspath) + // Load transactions. + txs, err := parseTxs(cfg.txsFile) + if err != nil { + return nil, fmt.Errorf("unable to load transactions: %w", err) + } + + config := setupDevNodeConfig(cfg, balances, pkgspath, txs) 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 { +func setupDevNodeConfig( + cfg *devCfg, + balances gnoland.Balances, + pkgspath []gnodev.PackagePath, + txs []std.Tx, +) *gnodev.NodeConfig { config := gnodev.DefaultNodeConfig(cfg.root) config.BalancesList = balances.List() config.PackagesPathList = pkgspath @@ -34,6 +46,7 @@ func setupDevNodeConfig(cfg *devCfg, balances gnoland.Balances, pkgspath []gnode config.NoReplay = cfg.noReplay config.MaxGasPerBlock = cfg.maxGas config.ChainID = cfg.chainId + config.Txs = txs // other listeners config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr diff --git a/contribs/gnodev/cmd/gnodev/txs.go b/contribs/gnodev/cmd/gnodev/txs.go new file mode 100644 index 00000000000..0be33b68702 --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/txs.go @@ -0,0 +1,23 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/gnolang/gno/tm2/pkg/std" +) + +func parseTxs(txFile string) ([]std.Tx, error) { + if txFile == "" { + return nil, nil + } + + file, loadErr := os.Open(txFile) + if loadErr != nil { + return nil, fmt.Errorf("unable to open tx file %s: %w", txFile, loadErr) + } + defer file.Close() + + return std.ParseTxs(context.Background(), file) +} diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 30ad262b1b0..d839091d328 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -37,6 +37,7 @@ type NodeConfig struct { NoReplay bool MaxGasPerBlock int64 ChainID string + Txs []std.Tx } func DefaultNodeConfig(rootdir string) *NodeConfig { @@ -107,6 +108,8 @@ func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitte Txs: pkgsTxs, } + genesis.Txs = append(genesis.Txs, cfg.Txs...) + if err := devnode.rebuildNode(ctx, genesis); err != nil { return nil, fmt.Errorf("unable to initialize the node: %w", err) } diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md index 97a4cc2e5ce..fd19922dff2 100644 --- a/docs/gno-tooling/cli/gnodev.md +++ b/docs/gno-tooling/cli/gnodev.md @@ -72,6 +72,21 @@ g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=10000000000000ugnot # test2 # ... ``` +### Transactions file + +You can specify a transactions file using `--txs-file`. The file should contain a list of signed transactions +that will be applied when starting the in-memory node. +``` +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} +``` + +#### Construction a transaction +`gnokey maketx ... >> "tx-file.json"` + +#### Signing the transaction +`gnokey sign -tx-path tx-file.json ...` + ### Deploy All realms and packages will be deployed to the in-memory node by the address passed in with the diff --git a/gno.land/cmd/gnoland/genesis_txs_add.go b/gno.land/cmd/gnoland/genesis_txs_add.go index 06019f7ef59..4bb9887a7ae 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add.go +++ b/gno.land/cmd/gnoland/genesis_txs_add.go @@ -1,15 +1,12 @@ package main import ( - "bufio" "context" "errors" "fmt" - "io" "os" "github.com/gnolang/gno/gno.land/pkg/gnoland" - "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" @@ -18,7 +15,6 @@ import ( var ( errInvalidTxsFile = errors.New("unable to open transactions file") errNoTxsFileSpecified = errors.New("no txs file specified") - errTxsParsingAborted = errors.New("transaction parsing aborted") ) // newTxsAddCmd creates the genesis txs add subcommand @@ -61,7 +57,7 @@ func execTxsAdd( return fmt.Errorf("%w, %w", errInvalidTxsFile, loadErr) } - txs, err := getTransactionsFromFile(ctx, file) + txs, err := std.ParseTxs(ctx, file) if err != nil { return fmt.Errorf("unable to read file, %w", err) } @@ -102,40 +98,3 @@ func execTxsAdd( return nil } - -// getTransactionsFromFile fetches the transactions from the -// specified reader -func getTransactionsFromFile(ctx context.Context, reader io.Reader) ([]std.Tx, error) { - txs := make([]std.Tx, 0) - - scanner := bufio.NewScanner(reader) - - for scanner.Scan() { - select { - case <-ctx.Done(): - return nil, errTxsParsingAborted - default: - // Parse the amino JSON - var tx std.Tx - - if err := amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { - return nil, fmt.Errorf( - "unable to unmarshal amino JSON, %w", - err, - ) - } - - txs = append(txs, tx) - } - } - - // Check for scanning errors - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf( - "error encountered while reading file, %w", - err, - ) - } - - return txs, nil -} diff --git a/tm2/pkg/std/tx.go b/tm2/pkg/std/tx.go index 3068022ae03..4fd7af4a641 100644 --- a/tm2/pkg/std/tx.go +++ b/tm2/pkg/std/tx.go @@ -1,14 +1,22 @@ package std import ( + "bufio" + "context" "fmt" + "io" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/multisig" + "github.com/gnolang/gno/tm2/pkg/errors" ) -var maxGasWanted = int64((1 << 60) - 1) // something smaller than math.MaxInt64 +var ( + maxGasWanted = int64((1 << 60) - 1) // something smaller than math.MaxInt64 + + ErrTxsLoadingAborted = errors.New("transaction loading aborted") +) // Tx is a standard way to wrap a Msg with Fee and Signatures. // NOTE: the first signature is the fee payer (Signatures must not be nil). @@ -136,3 +144,36 @@ func (fee Fee) Bytes() []byte { } return bz } + +func ParseTxs(ctx context.Context, reader io.Reader) ([]Tx, error) { + var txs []Tx + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil, ErrTxsLoadingAborted + default: + // Parse the amino JSON + var tx Tx + if err := amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { + return nil, fmt.Errorf( + "unable to unmarshal amino JSON, %w", + err, + ) + } + + txs = append(txs, tx) + } + } + + // Check for scanning errors + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf( + "error encountered while reading file, %w", + err, + ) + } + + return txs, nil +}