Skip to content

Commit

Permalink
chore: Add Run to gnoclient (#1574)
Browse files Browse the repository at this point in the history
gnoclient [already has
`Call`](https://github.com/gnolang/gno/blob/7339bdcde85de886bf7069cc4adaf31e6d48f9cd/gno.land/pkg/gnoclient/client_txs.go#L25)
which does the same operation as `gnokey maketx call`. This PR adds
`Run` to do the same thing as `gnokey maketx run`. It closely follows
the [implementation in
gnokey](https://github.com/gnolang/gno/blob/7339bdcde85de886bf7069cc4adaf31e6d48f9cd/gno.land/pkg/keyscli/run.go#L111-L139).

(When this PR is merged, in Gno Native Kit we will add the thin wrapper
for Run, similar to the wrapper for Call. We need this so that a
command-line app can run a test script on the blockchain to, for
example, stress test by adding hundreds of messages to r/demo/boards in
a single call. It would be too slow to make hundreds of separate calls.)

To test, I called `Run` with the test script in [this
gist](https://gist.github.com/moul/ccf1e2aff64e7a1f0c5ca5e2d98d7e9a). As
expected, the result is:
```
# HELLO WORLD!
## users.Render("")
 * [test_1](/r/demo/users:test_1)


## for i < 10 {print "hey hey hey!"}
0. hey hey hey!
1. hey hey hey!
2. hey hey hey!
3. hey hey hey!
4. hey hey hey!
5. hey hey hey!
6. hey hey hey!
7. hey hey hey!
8. hey hey hey!
9. hey hey hey!

## tests.* & std.*
- `tests.CurrentRealmPath` gno.land/r/demo/tests
- `tests.IsOriginCall` false
- `tests.GetPrevRealm` (struct{("g1a9r05e34s7dxe7wh8v00lnewnxtfnfq02gn5ws" std.Address),("gno.land/r/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/run" string)} std.Realm)
- `std.GetOrigCaller` g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5
- `std.PrevRealm` (struct{("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" std.Address),("" string)} std.Realm)

## stateful check
- before: 0
- after: 10

## bf
source:  ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.
result:  Hello World

## complex types
- before:  foobar
- after:  foobar_modified
```

---------

Signed-off-by: Jeff Thompson <jeff@thefirst.org>
  • Loading branch information
jefft0 authored Jan 25, 2024
1 parent 098771e commit f398c56
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
48 changes: 48 additions & 0 deletions gno.land/pkg/gnoclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/log"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/jaekwon/testify/require"
)

Expand Down Expand Up @@ -50,3 +51,50 @@ func TestClient_Request(t *testing.T) {

// XXX: need more test
}

func TestClient_Run(t *testing.T) {
config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config)
defer node.Stop()

signer := newInMemorySigner(t, config.TMConfig.ChainID())

client := Client{
Signer: signer,
RPCClient: rpcclient.NewHTTP(remoteAddr, "/websocket"),
}

code := `package main
import (
"std"
"gno.land/p/demo/ufmt"
"gno.land/r/demo/tests"
)
func main() {
println(ufmt.Sprintf("- before: %d", tests.Counter()))
for i := 0; i < 10; i++ {
tests.IncCounter()
}
println(ufmt.Sprintf("- after: %d", tests.Counter()))
}`
memPkg := &std.MemPackage{
Files: []*std.MemFile{
{
Name: "main.gno",
Body: code,
},
},
}
res, err := client.Run(RunCfg{
Package: memPkg,
GasFee: "1ugnot",
GasWanted: 100000000,
})
require.NoError(t, err)
require.NotNil(t, res)
require.NotEmpty(t, res.DeliverTx.Data)
require.Equal(t, string(res.DeliverTx.Data), "- before: 0\n- after: 10\n")
}
66 changes: 66 additions & 0 deletions gno.land/pkg/gnoclient/client_txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gnoclient

import (
"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/tm2/pkg/amino"
ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
"github.com/gnolang/gno/tm2/pkg/errors"
Expand All @@ -21,6 +22,16 @@ type CallCfg struct {
Memo string // Memo
}

// RunCfg contains configuration options for running a temporary package on the blockchain.
type RunCfg struct {
Package *std.MemPackage
GasFee string // Gas fee
GasWanted int64 // Gas wanted
AccountNumber uint64 // Account number
SequenceNumber uint64 // Sequence number
Memo string // Memo
}

// Call executes a contract call on the blockchain.
func (c *Client) Call(cfg CallCfg) (*ctypes.ResultBroadcastTxCommit, error) {
// Validate required client fields.
Expand Down Expand Up @@ -81,6 +92,61 @@ func (c *Client) Call(cfg CallCfg) (*ctypes.ResultBroadcastTxCommit, error) {
return c.signAndBroadcastTxCommit(tx, accountNumber, sequenceNumber)
}

// Temporarily load cfg.Package on the blockchain and run main() which can
// call realm functions and use println() to output to the "console".
// This returns bres where string(bres.DeliverTx.Data) is the "console" output.
func (c *Client) Run(cfg RunCfg) (*ctypes.ResultBroadcastTxCommit, error) {
// Validate required client fields.
if err := c.validateSigner(); err != nil {
return nil, errors.Wrap(err, "validate signer")
}
if err := c.validateRPCClient(); err != nil {
return nil, errors.Wrap(err, "validate RPC client")
}

memPkg := cfg.Package
gasWanted := cfg.GasWanted
gasFee := cfg.GasFee
sequenceNumber := cfg.SequenceNumber
accountNumber := cfg.AccountNumber
memo := cfg.Memo

// Validate config.
if memPkg.IsEmpty() {
return nil, errors.New("found an empty package " + memPkg.Path)
}

// Parse gas wanted & fee.
gasFeeCoins, err := std.ParseCoin(gasFee)
if err != nil {
return nil, errors.Wrap(err, "parsing gas fee coin")
}

caller := c.Signer.Info().GetAddress()

// precompile and validate syntax
err = gno.PrecompileAndCheckMempkg(memPkg)
if err != nil {
return nil, errors.Wrap(err, "precompile and check")
}
memPkg.Name = "main"
memPkg.Path = "gno.land/r/" + caller.String() + "/run"

// Construct message & transaction and marshal.
msg := vm.MsgRun{
Caller: caller,
Package: memPkg,
}
tx := std.Tx{
Msgs: []std.Msg{msg},
Fee: std.NewFee(gasWanted, gasFeeCoins),
Signatures: nil,
Memo: memo,
}

return c.signAndBroadcastTxCommit(tx, accountNumber, sequenceNumber)
}

// signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result.
func (c Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) {
caller := c.Signer.Info().GetAddress()
Expand Down

0 comments on commit f398c56

Please sign in to comment.