From e52c149a55f58fb5e07668cc296e705517776d37 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 28 Aug 2023 13:55:08 +0200 Subject: [PATCH 1/2] chore: Add temp copy of gnoclient. Use it in execCall. Signed-off-by: Jeff Thompson --- framework/call.go | 184 ++++---------------------------------------- framework/client.go | 162 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 167 deletions(-) create mode 100644 framework/client.go diff --git a/framework/call.go b/framework/call.go index 8f4f149c..f8675b34 100644 --- a/framework/call.go +++ b/framework/call.go @@ -1,7 +1,6 @@ package gnomobile import ( - "encoding/json" "fmt" "github.com/gnolang/gno/tm2/pkg/amino" @@ -10,7 +9,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -40,29 +38,9 @@ type makeTxCfg struct { chainID string } -func (m *makeTxCfg) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - RootCfg *baseCfg - - GasWanted int64 - GasFee string - Memo string - - Broadcast bool - ChainID string - }{ - RootCfg: m.rootCfg, - GasWanted: m.gasWanted, - GasFee: m.gasFee, - Memo: m.memo, - Broadcast: m.broadcast, - ChainID: m.chainID, - }) -} - // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/query.go type queryCfg struct { - rootCfg *baseCfg + remote string data string height int64 @@ -74,7 +52,7 @@ type queryCfg struct { // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/sign.go type signCfg struct { - rootCfg *baseCfg + kb keys.Keybase txPath string chainID string @@ -90,7 +68,7 @@ type signCfg struct { // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/broadcast.go type broadcastCfg struct { - rootCfg *baseCfg + remote string dryRun bool @@ -110,149 +88,26 @@ type callCfg struct { // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/call.go func execCall(cfg *callCfg, nameOrBech32 string, password string) error { - if cfg.pkgPath == "" { - return errors.New("pkgpath not specified") - } - if cfg.funcName == "" { - return errors.New("func not specified") - } - if cfg.rootCfg.gasWanted == 0 { - return errors.New("gas-wanted not specified") - } - if cfg.rootCfg.gasFee == "" { - return errors.New("gas-fee not specified") - } - - // read statement. - fnc := cfg.funcName - - // read account pubkey. - kb, err := keys.NewKeyBaseFromDir(cfg.rootCfg.rootCfg.Home) - if err != nil { - return err - } - info, err := kb.GetByNameOrAddress(nameOrBech32) - if err != nil { - return err - } - caller := info.GetAddress() - // info.GetPubKey() - - // Parse send amount. - send, err := std.ParseCoins(cfg.send) - if err != nil { - return errors.Wrap(err, "parsing send coins") - } - - // parse gas wanted & fee. - gaswanted := cfg.rootCfg.gasWanted - gasfee, err := std.ParseCoin(cfg.rootCfg.gasFee) - if err != nil { - return errors.Wrap(err, "parsing gas fee coin") - } - - // construct msg & tx and marshal. - msg := vm.MsgCall{ - Caller: caller, - Send: send, - PkgPath: cfg.pkgPath, - Func: fnc, - Args: cfg.args, - } - tx := std.Tx{ - Msgs: []std.Msg{msg}, - Fee: std.NewFee(gaswanted, gasfee), - Signatures: nil, - Memo: cfg.rootCfg.memo, - } - - if cfg.rootCfg.broadcast { - err := signAndBroadcast(cfg.rootCfg, tx, kb, nameOrBech32, password) - if err != nil { - return err - } - } else { - errors.New(string(amino.MustMarshalJSON(tx))) - } - return nil -} - -// From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/addpkg.go -func signAndBroadcast( - cfg *makeTxCfg, - tx std.Tx, - kb keys.Keybase, - nameOrBech32 string, - password string, -) error { - baseopts := cfg.rootCfg - txopts := cfg - - // query account - kb, err := keys.NewKeyBaseFromDir(baseopts.Home) - if err != nil { - return err - } - info, err := kb.GetByNameOrAddress(nameOrBech32) - if err != nil { - return err - } - accountAddr := info.GetAddress() - - qopts := &queryCfg{ - rootCfg: baseopts, - path: fmt.Sprintf("auth/accounts/%s", accountAddr), - } - qres, err := queryHandler(qopts) - if err != nil { - return errors.Wrap(err, "query account") - } - var qret struct{ BaseAccount std.BaseAccount } - err = amino.UnmarshalJSON(qres.Response.Data, &qret) - if err != nil { + client := NewClient(cfg.rootCfg.rootCfg.Remote, cfg.rootCfg.chainID) + client.SetAccount(nameOrBech32, password) + if err := client.SetKeyBaseFromDir(cfg.rootCfg.rootCfg.Home); err != nil { return err } - - // sign tx - accountNumber := qret.BaseAccount.AccountNumber - sequence := qret.BaseAccount.Sequence - sopts := &signCfg{ - rootCfg: baseopts, - sequence: sequence, - accountNumber: accountNumber, - chainID: txopts.chainID, - nameOrBech32: nameOrBech32, - txJSON: amino.MustMarshalJSON(tx), - pass: password, - } - - signedTx, err := SignHandler(sopts) - if err != nil { - return errors.Wrap(err, "sign tx") - } - - // broadcast signed tx - bopts := &broadcastCfg{ - rootCfg: baseopts, - tx: signedTx, - } - bres, err := broadcastHandler(bopts) - if err != nil { - return errors.Wrap(err, "broadcast tx") - } - if bres.CheckTx.IsErr() { - return errors.Wrap(bres.CheckTx.Error, "check transaction failed: log:%s", bres.CheckTx.Log) - } - if bres.DeliverTx.IsErr() { - return errors.Wrap(bres.DeliverTx.Error, "deliver transaction failed: log:%s", bres.DeliverTx.Log) + r := client.NewRequest("call") + r.StringOption("pkgpath", cfg.pkgPath) + r.StringOption("func", cfg.funcName) + r.StringOption("gas-fee", cfg.rootCfg.gasFee) + r.Int64Option("gas-wanted", cfg.rootCfg.gasWanted) + for _, arg := range cfg.args { + r.Argument(arg) } - return nil + return r.Send() } // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/query.go func queryHandler(cfg *queryCfg) (*ctypes.ResultABCIQuery, error) { - remote := cfg.rootCfg.Remote + remote := cfg.remote if remote == "" || remote == "y" { return nil, errors.New("missing remote url") } @@ -278,7 +133,7 @@ func broadcastHandler(cfg *broadcastCfg) (*ctypes.ResultBroadcastTxCommit, error return nil, errors.New("invalid tx") } - remote := cfg.rootCfg.Remote + remote := cfg.remote if remote == "" || remote == "y" { return nil, errors.New("missing remote url") } @@ -314,11 +169,6 @@ func SignHandler(cfg *signCfg) (*std.Tx, error) { return nil, errors.New("invalid tx content") } - kb, err := keys.NewKeyBaseFromDir(cfg.rootCfg.Home) - if err != nil { - return nil, err - } - err = amino.UnmarshalJSON(cfg.txJSON, &tx) if err != nil { return nil, err @@ -351,7 +201,7 @@ func SignHandler(cfg *signCfg) (*std.Tx, error) { return nil, nil } - sig, pub, err := kb.Sign(cfg.nameOrBech32, cfg.pass, signbz) + sig, pub, err := cfg.kb.Sign(cfg.nameOrBech32, cfg.pass, signbz) if err != nil { return nil, err } diff --git a/framework/client.go b/framework/client.go new file mode 100644 index 00000000..6e1d9d14 --- /dev/null +++ b/framework/client.go @@ -0,0 +1,162 @@ +// This is a temporary shadow of the Client struct to be provided by https://github.com/gnolang/gno/pull/1047 . + +package gnomobile + +import ( + "fmt" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/std" +) + +// Client represents the Gno.land RPC API client. +type Client struct { + remote string + chainID string + keybase keys.Keybase + nameOrBech32 string + password string +} + +func NewClient() *Client { + // Set defaults. + return &Client{ + remote: "127.0.0.1:26657", + chainID: "dev", + } +} + +func (c *Client) SetRemote(remote string, chainID string) { + c.remote = remote + c.chainID = chainID +} + +func (c *Client) SetAccount(nameOrBech32 string, password string) { + c.nameOrBech32 = nameOrBech32 + c.password = password +} + +// Create an account using the nameOrBech32 and password from SetAccount. +func (c *Client) CreateAccount(mnemonic string, bip39Passwd string, account int, index int) error { + _, err := c.keybase.CreateAccount(c.nameOrBech32, mnemonic, bip39Passwd, c.password, uint32(account), uint32(index)) + return err +} + +func (c *Client) SetKeyBaseFromDir(rootDir string) error { + var err error + if c.keybase, err = keys.NewKeyBaseFromDir(rootDir); err != nil { + return err + } + return nil +} + +func (c *Client) GetKeyCount() (int, error) { + keyList, err := c.keybase.List() + if err != nil { + return 0, err + } + return len(keyList), nil +} + +// TODO: port existing code, i.e. faucet? +// TODO: create right now a tm2 generic go client and a gnovm generic go client? +// TODO: Command: Send +// TODO: Command: AddPkg +// TODO: Command: Query +// TODO: Command: Eval +// TODO: Command: Exec +// TODO: Command: Package +// TODO: Command: QFile +// TODO: examples and unit tests +// TODO: Mock +// TODO: alternative configuration (pass existing websocket?) +// TODO: minimal go.mod to make it light to import + +func (c *Client) Call(pkgPath string, fnc string, args commands.StringArr, gasFee string, gasWanted int64, send string) error { + info, err := c.keybase.GetByNameOrAddress(c.nameOrBech32) + if err != nil { + return err + } + caller := info.GetAddress() + + // Parse send amount. + sendCoins, err := std.ParseCoins(send) + if err != nil { + return errors.Wrap(err, "parsing send coins") + } + + // parse gas wanted & fee. + gasFeeCoins, err := std.ParseCoin(gasFee) + if err != nil { + return errors.Wrap(err, "parsing gas fee coin") + } + + // construct msg & tx and marshal. + msg := vm.MsgCall{ + Caller: caller, + Send: sendCoins, + PkgPath: pkgPath, + Func: fnc, + Args: args, + } + tx := std.Tx{ + Msgs: []std.Msg{msg}, + Fee: std.NewFee(gasWanted, gasFeeCoins), + Signatures: nil, + Memo: "", + } + + qopts := &queryCfg{ + remote: c.remote, + path: fmt.Sprintf("auth/accounts/%s", caller), + } + qres, err := queryHandler(qopts) + if err != nil { + return errors.Wrap(err, "query account") + } + var qret struct{ BaseAccount std.BaseAccount } + err = amino.UnmarshalJSON(qres.Response.Data, &qret) + if err != nil { + return err + } + + // sign tx + accountNumber := qret.BaseAccount.AccountNumber + sequence := qret.BaseAccount.Sequence + sopts := &signCfg{ + kb: c.keybase, + sequence: sequence, + accountNumber: accountNumber, + chainID: c.chainID, + nameOrBech32: c.nameOrBech32, + txJSON: amino.MustMarshalJSON(tx), + pass: c.password, + } + + signedTx, err := SignHandler(sopts) + if err != nil { + return errors.Wrap(err, "sign tx") + } + + // broadcast signed tx + bopts := &broadcastCfg{ + remote: c.remote, + tx: signedTx, + } + bres, err := broadcastHandler(bopts) + if err != nil { + return errors.Wrap(err, "broadcast tx") + } + if bres.CheckTx.IsErr() { + return errors.Wrap(bres.CheckTx.Error, "check transaction failed: log:%s", bres.CheckTx.Log) + } + if bres.DeliverTx.IsErr() { + return errors.Wrap(bres.DeliverTx.Error, "deliver transaction failed: log:%s", bres.DeliverTx.Log) + } + + return nil +} From 4e284dd46ce290b640be2cf41ee8c1034788057a Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 31 Aug 2023 07:42:35 +0200 Subject: [PATCH 2/2] chore: Make callCreateReply use Client.NewRequest directly. Remove execCall. Signed-off-by: Jeff Thompson --- framework/call.go | 56 ----------------------------- framework/gnokey.go | 85 ++++++++++++++++++--------------------------- 2 files changed, 34 insertions(+), 107 deletions(-) diff --git a/framework/call.go b/framework/call.go index f8675b34..85c2e890 100644 --- a/framework/call.go +++ b/framework/call.go @@ -6,38 +6,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" rpc_client "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" - "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/std" ) -// From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/common.go -type BaseOptions struct { - Home string - Remote string - Quiet bool - InsecurePasswordStdin bool - Config string -} - -// From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/root.go -type baseCfg struct { - BaseOptions -} - -// From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/maketx.go -type makeTxCfg struct { - rootCfg *baseCfg - - gasWanted int64 - gasFee string - memo string - - broadcast bool - chainID string -} - // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/query.go type queryCfg struct { remote string @@ -76,35 +49,6 @@ type broadcastCfg struct { tx *std.Tx } -// From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/call.go -type callCfg struct { - rootCfg *makeTxCfg - - send string - pkgPath string - funcName string - args commands.StringArr -} - -// From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/call.go -func execCall(cfg *callCfg, nameOrBech32 string, password string) error { - client := NewClient(cfg.rootCfg.rootCfg.Remote, cfg.rootCfg.chainID) - client.SetAccount(nameOrBech32, password) - if err := client.SetKeyBaseFromDir(cfg.rootCfg.rootCfg.Home); err != nil { - return err - } - r := client.NewRequest("call") - r.StringOption("pkgpath", cfg.pkgPath) - r.StringOption("func", cfg.funcName) - r.StringOption("gas-fee", cfg.rootCfg.gasFee) - r.Int64Option("gas-wanted", cfg.rootCfg.gasWanted) - for _, arg := range cfg.args { - r.Argument(arg) - } - - return r.Send() -} - // From https://github.com/gnolang/gno/blob/master/tm2/pkg/crypto/keys/client/query.go func queryHandler(cfg *queryCfg) (*ctypes.ResultABCIQuery, error) { remote := cfg.remote diff --git a/framework/gnokey.go b/framework/gnokey.go index 3774b247..820aaa14 100644 --- a/framework/gnokey.go +++ b/framework/gnokey.go @@ -3,8 +3,6 @@ package gnomobile import ( "encoding/json" "fmt" - - "github.com/gnolang/gno/tm2/pkg/crypto/keys" ) type PromiseBlock interface { @@ -13,37 +11,32 @@ type PromiseBlock interface { } type accountAndTxCfg struct { - TxCfg *makeTxCfg + Client *Client - KeyName string - Password string - Mnemonic string + GasWanted int64 + GasFee string + Mnemonic string } func CreateDefaultAccount(rootDir string) error { - cfg := getAccountAndTxCfg(rootDir) - - kb, err := keys.NewKeyBaseFromDir(cfg.TxCfg.rootCfg.Home) + cfg, err := getAccountAndTxCfg(rootDir) if err != nil { return err } - keyList, err := kb.List() + + keyCount, err := cfg.Client.GetKeyCount() if err != nil { return err } - if len(keyList) > 0 { + if keyCount > 0 { // Assume the account with cfg.KeyName is already created. return nil } - _, err = kb.CreateAccount(cfg.KeyName, cfg.Mnemonic, "", cfg.Password, uint32(0), uint32(0)) - if err != nil { - return err - } - return nil + return cfg.Client.CreateAccount(cfg.Mnemonic, "", 0, 0) } -func getAccountAndTxCfg(rootDir string) *accountAndTxCfg { +func getAccountAndTxCfg(rootDir string) (*accountAndTxCfg, error) { dataDir := rootDir + "/data" remote := "testnet.gno.berty.io:26657" chainID := "dev" @@ -51,48 +44,35 @@ func getAccountAndTxCfg(rootDir string) *accountAndTxCfg { password := "password" mnemonic := "enable until hover project know foam join table educate room better scrub clever powder virus army pitch ranch fix try cupboard scatter dune fee" - return &accountAndTxCfg{ - TxCfg: &makeTxCfg{ - rootCfg: &baseCfg{ - BaseOptions: BaseOptions{ - Home: dataDir, - Remote: remote, - }, - }, - gasWanted: 2000000, - gasFee: "1000000ugnot", - - broadcast: true, - chainID: chainID, - }, - KeyName: keyName, - Password: password, - Mnemonic: mnemonic, + client := NewClient() + client.SetRemote(remote, chainID) + client.SetAccount(keyName, password) + if err := client.SetKeyBaseFromDir(dataDir); err != nil { + return nil, err } + + return &accountAndTxCfg{ + Client: client, + GasWanted: 2000000, + GasFee: "1000000ugnot", + Mnemonic: mnemonic, + }, nil } func callCreateThread(cfg *accountAndTxCfg, boardId string, title string, body string) error { - callCfg := &callCfg{ - rootCfg: cfg.TxCfg, - pkgPath: "gno.land/r/demo/boards", - funcName: "CreateThread", - args: []string{boardId, title, body}, - } - return execCall(callCfg, cfg.KeyName, cfg.Password) + return cfg.Client.Call("gno.land/r/demo/boards", "CreateThread", []string{boardId, title, body}, cfg.GasFee, cfg.GasWanted, "") } func callCreateReply(cfg *accountAndTxCfg, boardId string, threadId string, postId string, body string) error { - callCfg := &callCfg{ - rootCfg: cfg.TxCfg, - pkgPath: "gno.land/r/demo/boards", - funcName: "CreateReply", - args: []string{boardId, threadId, postId, body}, - } - return execCall(callCfg, cfg.KeyName, cfg.Password) + return cfg.Client.Call("gno.land/r/demo/boards", "CreateReply", []string{boardId, threadId, postId, body}, cfg.GasFee, cfg.GasWanted, "") } func ExportJsonConfig(rootDir string) string { - config, err := json.Marshal(getAccountAndTxCfg(rootDir)) + cfg, err := getAccountAndTxCfg(rootDir) + if err != nil { + return fmt.Sprintf("Error: unable make config: %s", err.Error()) + } + config, err := json.Marshal(cfg) if err != nil { return fmt.Sprintf("Error: unable load config: %s", err.Error()) } @@ -100,9 +80,12 @@ func ExportJsonConfig(rootDir string) string { } func CreateReply(message string, rootDir string) string { - cfg := getAccountAndTxCfg(rootDir) + cfg, err := getAccountAndTxCfg(rootDir) + if err != nil { + return fmt.Sprintf("Error: unable to get config: %s", err.Error()) + } - err := callCreateReply(cfg, "2", "1", "1", message) + err = callCreateReply(cfg, "2", "1", "1", message) if err != nil { return fmt.Sprintf("Error: unable to exec call command: %s", err.Error())