From 10550df6684ce7bab4dd5aa90fee6e0f85cc0d0e Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 29 Jul 2024 15:50:03 +0200 Subject: [PATCH] chore: In gnoclient, refactor signAndBroadcastTxCommit into SignTx and BroadcastTxCommit Also add MakeCallTx, MakeRunTx, MakeSendTx and MakeAddPackageTx to support signing separately. Signed-off-by: Jeff Thompson --- gno.land/pkg/gnoclient/client_test.go | 47 ++++++ gno.land/pkg/gnoclient/client_txs.go | 180 +++++++++++++++------ gno.land/pkg/gnoclient/integration_test.go | 85 ++++++++++ 3 files changed, 267 insertions(+), 45 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index 8aef07451d6..25961e1bd9a 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -107,6 +107,18 @@ func TestCallSingle(t *testing.T) { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, string(res.DeliverTx.Data), "it works!") + + // Test signing separately + tx, err := client.MakeCallTx(cfg, msg...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "it works!") } func TestCallMultiple(t *testing.T) { @@ -177,6 +189,17 @@ func TestCallMultiple(t *testing.T) { res, err := client.Call(cfg, msg...) assert.NoError(t, err) assert.NotNil(t, res) + + // Test signing separately + tx, err := client.MakeCallTx(cfg, msg...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) } func TestCallErrors(t *testing.T) { @@ -618,6 +641,18 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) + + // Test signing separately + tx, err := client.MakeRunTx(cfg, msg) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) } func TestRunMultiple(t *testing.T) { @@ -697,6 +732,18 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, "hi gnoclient!\nhi gnoclient!\n", string(res.DeliverTx.Data)) + + // Test signing separately + tx, err := client.MakeRunTx(cfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, "hi gnoclient!\nhi gnoclient!\n", string(res.DeliverTx.Data)) } func TestRunErrors(t *testing.T) { diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index a32a6899abe..baaa6a964e4 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -24,11 +24,12 @@ var ( // BaseTxCfg defines the base transaction configuration, shared by all message types type BaseTxCfg struct { - GasFee string // Gas fee - GasWanted int64 // Gas wanted - AccountNumber uint64 // Account number - SequenceNumber uint64 // Sequence number - Memo string // Memo + GasFee string // Gas fee + GasWanted int64 // Gas wanted + AccountNumber uint64 // Account number + SequenceNumber uint64 // Sequence number + Memo string // Memo + CallerAddress crypto.Address // The caller Address if known } // MsgCall - syntax sugar for vm.MsgCall @@ -60,12 +61,27 @@ type MsgAddPackage struct { // Call executes one or more MsgCall calls on the blockchain func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { + // MakeCallTx calls validateSigner(). + if err := c.validateRPCClient(); err != nil { return nil, err } - if err := c.validateRPCClient(); err != nil { + + tx, err := c.MakeCallTx(cfg, msgs...) + if err != nil { return nil, err } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// MakeCallTx makes an unsigned transaction from one or more MsgCall. +// If cfg.CallerAddress.IsZero() then get it from c.Signer. +func (c *Client) MakeCallTx(cfg BaseTxCfg, msgs ...MsgCall) (*std.Tx, error) { + if cfg.CallerAddress.IsZero() { + // Validate required client fields + if err := c.validateSigner(); err != nil { + return nil, err + } + } // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { @@ -86,14 +102,18 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx return nil, err } - caller, err := c.Signer.Info() - if err != nil { - return nil, err + callerAddress := cfg.CallerAddress + if callerAddress.IsZero() { + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + callerAddress = caller.GetAddress() } // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgCall{ - Caller: caller.GetAddress(), + Caller: callerAddress, PkgPath: msg.PkgPath, Func: msg.FuncName, Args: msg.Args, @@ -108,25 +128,38 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // Run executes one or more MsgRun calls on the blockchain func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { + // MakeRunTx calls validateSigner(). + if err := c.validateRPCClient(); err != nil { return nil, err } - if err := c.validateRPCClient(); err != nil { + + tx, err := c.MakeRunTx(cfg, msgs...) + if err != nil { return nil, err } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// MakeRunTx makes an unsigned transaction from one or more MsgRun. +// If cfg.CallerAddress.IsZero() then get it from c.Signer. +func (c *Client) MakeRunTx(cfg BaseTxCfg, msgs ...MsgRun) (*std.Tx, error) { + if cfg.CallerAddress.IsZero() { + // Validate required client fields + if err := c.validateSigner(); err != nil { + return nil, err + } + } // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { @@ -147,9 +180,13 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo return nil, err } - caller, err := c.Signer.Info() - if err != nil { - return nil, err + callerAddress := cfg.CallerAddress + if callerAddress.IsZero() { + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + callerAddress = caller.GetAddress() } msg.Package.Name = "main" @@ -157,7 +194,7 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgRun{ - Caller: caller.GetAddress(), + Caller: callerAddress, Package: msg.Package, Send: send, })) @@ -170,25 +207,38 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // Send executes one or more MsgSend calls on the blockchain func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { + // MakeSendTx calls validateSigner(). + if err := c.validateRPCClient(); err != nil { return nil, err } - if err := c.validateRPCClient(); err != nil { + + tx, err := c.MakeSendTx(cfg, msgs...) + if err != nil { return nil, err } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// MakeSendTx makes an unsigned transaction from one or more MsgSend. +// If cfg.CallerAddress.IsZero() then get it from c.Signer. +func (c *Client) MakeSendTx(cfg BaseTxCfg, msgs ...MsgSend) (*std.Tx, error) { + if cfg.CallerAddress.IsZero() { + // Validate required client fields + if err := c.validateSigner(); err != nil { + return nil, err + } + } // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { @@ -209,14 +259,18 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx return nil, err } - caller, err := c.Signer.Info() - if err != nil { - return nil, err + callerAddress := cfg.CallerAddress + if callerAddress.IsZero() { + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + callerAddress = caller.GetAddress() } // Unwrap syntax sugar to vm.MsgSend slice vmMsgs = append(vmMsgs, std.Msg(bank.MsgSend{ - FromAddress: caller.GetAddress(), + FromAddress: callerAddress, ToAddress: msg.ToAddress, Amount: send, })) @@ -229,25 +283,38 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // AddPackage executes one or more AddPackage calls on the blockchain func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. - if err := c.validateSigner(); err != nil { + // MakeAddPackageTx calls validateSigner(). + if err := c.validateRPCClient(); err != nil { return nil, err } - if err := c.validateRPCClient(); err != nil { + + tx, err := c.MakeAddPackageTx(cfg, msgs...) + if err != nil { return nil, err } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// MakeAddPackageTx makes an unsigned transaction from one or more MsgAddPackage. +// If cfg.CallerAddress.IsZero() then get it from c.Signer. +func (c *Client) MakeAddPackageTx(cfg BaseTxCfg, msgs ...MsgAddPackage) (*std.Tx, error) { + if cfg.CallerAddress.IsZero() { + // Validate required client fields + if err := c.validateSigner(); err != nil { + return nil, err + } + } // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { @@ -268,14 +335,18 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul return nil, err } - caller, err := c.Signer.Info() - if err != nil { - return nil, err + callerAddress := cfg.CallerAddress + if callerAddress.IsZero() { + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + callerAddress = caller.GetAddress() } // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgAddPackage{ - Creator: caller.GetAddress(), + Creator: callerAddress, Package: msg.Package, Deposit: deposit, })) @@ -288,18 +359,29 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { + signedTx, err := c.SignTx(tx, accountNumber, sequenceNumber) + if err != nil { + return nil, err + } + return c.BroadcastTxCommit(signedTx) +} + +// SignTx signs a transaction and returns a signed tx ready for broadcasting. +// If accountNumber or sequenceNumber is 0 then query the blockchain for the value. +func (c *Client) SignTx(tx std.Tx, accountNumber, sequenceNumber uint64) (*std.Tx, error) { + if err := c.validateSigner(); err != nil { + return nil, err + } caller, err := c.Signer.Info() if err != nil { return nil, err @@ -323,7 +405,15 @@ func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumb if err != nil { return nil, errors.Wrap(err, "sign") } + return signedTx, nil +} +// BroadcastTxCommit marshals and broadcasts the signed transaction, returning the result. +// If the result has a delivery error, then return a wrapped error. +func (c *Client) BroadcastTxCommit(signedTx *std.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + if err := c.validateRPCClient(); err != nil { + return nil, err + } bz, err := amino.Marshal(signedTx) if err != nil { return nil, errors.Wrap(err, "marshaling tx binary bytes") diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index 06360244b7f..7d0ffc9bba7 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -59,6 +59,18 @@ func TestCallSingle_Integration(t *testing.T) { got := string(res.DeliverTx.Data) assert.Equal(t, expected, got) + + // Test signing separately + tx, err := client.MakeCallTx(baseCfg, msg) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, 0, 0) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + require.NoError(t, err) + got = string(res.DeliverTx.Data) + assert.Equal(t, expected, got) } func TestCallMultiple_Integration(t *testing.T) { @@ -111,6 +123,18 @@ func TestCallMultiple_Integration(t *testing.T) { got := string(res.DeliverTx.Data) assert.Equal(t, expected, got) + + // Test signing separately + tx, err := client.MakeCallTx(baseCfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, 0, 0) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + require.NoError(t, err) + got = string(res.DeliverTx.Data) + assert.Equal(t, expected, got) } func TestSendSingle_Integration(t *testing.T) { @@ -160,6 +184,24 @@ func TestSendSingle_Integration(t *testing.T) { got := account.GetCoins() assert.Equal(t, expected, got) + + // Test signing separately + tx, err := client.MakeSendTx(baseCfg, msg) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, 0, 0) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + require.NoError(t, err) + assert.Equal(t, "", string(res.DeliverTx.Data)) + + // Get the new account balance + account, _, err = client.QueryAccount(toAddress) + require.NoError(t, err) + expected2 := std.Coins{{"ugnot", int64(2 * amount)}} + got = account.GetCoins() + assert.Equal(t, expected2, got) } func TestSendMultiple_Integration(t *testing.T) { @@ -216,6 +258,24 @@ func TestSendMultiple_Integration(t *testing.T) { got := account.GetCoins() assert.Equal(t, expected, got) + + // Test signing separately + tx, err := client.MakeSendTx(baseCfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, 0, 0) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + require.NoError(t, err) + assert.Equal(t, "", string(res.DeliverTx.Data)) + + // Get the new account balance + account, _, err = client.QueryAccount(toAddress) + require.NoError(t, err) + expected2 := std.Coins{{"ugnot", int64(2 * (amount1 + amount2))}} + got = account.GetCoins() + assert.Equal(t, expected2, got) } // Run tests @@ -274,6 +334,18 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, string(res.DeliverTx.Data), "- before: 0\n- after: 10\n") + + // Test signing separately + tx, err := client.MakeRunTx(baseCfg, msg) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, 0, 0) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "- before: 10\n- after: 20\n") } // Run tests @@ -354,6 +426,19 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, expected, string(res.DeliverTx.Data)) + + // Test signing separately + tx, err := client.MakeRunTx(baseCfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, 0, 0) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err = client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + expected2 := "- before: 10\n- after: 20\nhi gnoclient!\n" + assert.Equal(t, expected2, string(res.DeliverTx.Data)) } func TestAddPackageSingle_Integration(t *testing.T) {