cli/wallet: allow to testinvoke transaction before signing

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2021-09-14 15:02:54 +03:00
parent e2a93f3e41
commit bf2ca35453
5 changed files with 124 additions and 4 deletions

View file

@ -2,6 +2,8 @@ package main
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt"
"math/big" "math/big"
"os" "os"
"path" "path"
@ -10,7 +12,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -92,6 +96,27 @@ func TestSignMultisigTx(t *testing.T) {
"--wallet", wallet2Path, "--wallet", wallet2Path,
"--in", txPath, "--out", txPath) "--in", txPath, "--out", txPath)
t.Run("test invoke", func(t *testing.T) {
t.Run("missing file", func(t *testing.T) {
e.RunWithError(t, "neo-go", "util", "txdump")
fmt.Println(e.Out.String())
})
t.Run("no invoke", func(t *testing.T) {
e.Run(t, "neo-go", "util", "txdump", txPath)
e.checkTxTestInvokeOutput(t, 11)
e.checkEOF(t)
})
e.Run(t, "neo-go", "util", "txdump",
"--rpc-endpoint", "http://"+e.RPC.Addr,
txPath)
e.checkTxTestInvokeOutput(t, 11)
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State, res.FaultException)
})
e.In.WriteString("pass\r") e.In.WriteString("pass\r")
e.Run(t, "neo-go", "wallet", "sign", e.Run(t, "neo-go", "wallet", "sign",
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
@ -153,3 +178,21 @@ func TestSignMultisigTx(t *testing.T) {
require.Equal(t, big.NewInt(2), b) require.Equal(t, big.NewInt(2), b)
}) })
} }
func (e *executor) checkTxTestInvokeOutput(t *testing.T, scriptSize int) {
e.checkNextLine(t, `Hash:\s+`)
e.checkNextLine(t, `OnChain:\s+false`)
e.checkNextLine(t, `ValidUntil:\s+\d+`)
e.checkNextLine(t, `Signer:\s+\w+`)
e.checkNextLine(t, `SystemFee:\s+(\d|\.)+`)
e.checkNextLine(t, `NetworkFee:\s+(\d|\.)+`)
e.checkNextLine(t, `Script:\s+\w+`)
e.checkScriptDump(t, scriptSize)
}
func (e *executor) checkScriptDump(t *testing.T, scriptSize int) {
e.checkNextLine(t, `INDEX\s+`)
for i := 0; i < scriptSize; i++ {
e.checkNextLine(t, `\d+\s+\w+`)
}
}

View file

@ -14,6 +14,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
@ -102,12 +103,16 @@ func queryTx(ctx *cli.Context) error {
} }
} }
dumpApplicationLog(ctx, res, txOut) DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose"))
return nil return nil
} }
func dumpApplicationLog(ctx *cli.Context, res *result.ApplicationLog, tx *result.TransactionOutputRaw) { func DumpApplicationLog(
verbose := ctx.Bool("verbose") ctx *cli.Context,
res *result.ApplicationLog,
tx *transaction.Transaction,
txMeta *result.TransactionMetadata,
verbose bool) {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
// Ignore the errors below because `Write` to buffer doesn't return error. // Ignore the errors below because `Write` to buffer doesn't return error.
@ -117,7 +122,9 @@ func dumpApplicationLog(ctx *cli.Context, res *result.ApplicationLog, tx *result
if res == nil { if res == nil {
_, _ = tw.Write([]byte("ValidUntil:\t" + strconv.FormatUint(uint64(tx.ValidUntilBlock), 10) + "\n")) _, _ = tw.Write([]byte("ValidUntil:\t" + strconv.FormatUint(uint64(tx.ValidUntilBlock), 10) + "\n"))
} else { } else {
_, _ = tw.Write([]byte("BlockHash:\t" + tx.Blockhash.StringLE() + "\n")) if txMeta != nil {
_, _ = tw.Write([]byte("BlockHash:\t" + txMeta.Blockhash.StringLE() + "\n"))
}
if len(res.Executions) != 1 { if len(res.Executions) != 1 {
_, _ = tw.Write([]byte("Success:\tunknown (no execution data)\n")) _, _ = tw.Write([]byte("Success:\tunknown (no execution data)\n"))
} else { } else {
@ -133,6 +140,9 @@ func dumpApplicationLog(ctx *cli.Context, res *result.ApplicationLog, tx *result
_, _ = tw.Write([]byte("SystemFee:\t" + fixedn.Fixed8(tx.SystemFee).String() + " GAS\n")) _, _ = tw.Write([]byte("SystemFee:\t" + fixedn.Fixed8(tx.SystemFee).String() + " GAS\n"))
_, _ = tw.Write([]byte("NetworkFee:\t" + fixedn.Fixed8(tx.NetworkFee).String() + " GAS\n")) _, _ = tw.Write([]byte("NetworkFee:\t" + fixedn.Fixed8(tx.NetworkFee).String() + " GAS\n"))
_, _ = tw.Write([]byte("Script:\t" + base64.StdEncoding.EncodeToString(tx.Script) + "\n")) _, _ = tw.Write([]byte("Script:\t" + base64.StdEncoding.EncodeToString(tx.Script) + "\n"))
v := vm.New()
v.Load(tx.Script)
v.PrintOps(tw)
if res != nil { if res != nil {
for _, e := range res.Executions { for _, e := range res.Executions {
if e.VMState != vm.HaltState { if e.VMState != vm.HaltState {

View file

@ -125,6 +125,13 @@ func (e *executor) compareQueryTxVerbose(t *testing.T, tx *transaction.Transacti
e.checkNextLine(t, `SystemFee:\s+`+fixedn.Fixed8(tx.SystemFee).String()+" GAS$") e.checkNextLine(t, `SystemFee:\s+`+fixedn.Fixed8(tx.SystemFee).String()+" GAS$")
e.checkNextLine(t, `NetworkFee:\s+`+fixedn.Fixed8(tx.NetworkFee).String()+" GAS$") e.checkNextLine(t, `NetworkFee:\s+`+fixedn.Fixed8(tx.NetworkFee).String()+" GAS$")
e.checkNextLine(t, `Script:\s+`+regexp.QuoteMeta(base64.StdEncoding.EncodeToString(tx.Script))) e.checkNextLine(t, `Script:\s+`+regexp.QuoteMeta(base64.StdEncoding.EncodeToString(tx.Script)))
c := vm.NewContext(tx.Script)
n := 0
for ; c.NextIP() < c.LenInstr(); _, _, err = c.Next() {
require.NoError(t, err)
n++
}
e.checkScriptDump(t, n)
if res[0].Execution.VMState != vm.HaltState { if res[0].Execution.VMState != vm.HaltState {
e.checkNextLine(t, `Exception:\s+`+regexp.QuoteMeta(res[0].Execution.FaultException)) e.checkNextLine(t, `Exception:\s+`+regexp.QuoteMeta(res[0].Execution.FaultException))

View file

@ -3,12 +3,14 @@ package util
import ( import (
"fmt" "fmt"
"github.com/nspcc-dev/neo-go/cli/options"
vmcli "github.com/nspcc-dev/neo-go/pkg/vm/cli" vmcli "github.com/nspcc-dev/neo-go/pkg/vm/cli"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// NewCommands returns util commands for neo-go CLI. // NewCommands returns util commands for neo-go CLI.
func NewCommands() []cli.Command { func NewCommands() []cli.Command {
txDumpFlags := append([]cli.Flag{}, options.RPC...)
return []cli.Command{ return []cli.Command{
{ {
Name: "util", Name: "util",
@ -23,6 +25,13 @@ func NewCommands() []cli.Command {
and converted to other formats. Strings are escaped and output in quotes.`, and converted to other formats. Strings are escaped and output in quotes.`,
Action: handleParse, Action: handleParse,
}, },
{
Name: "txdump",
Usage: "Dump transaction stored in file",
UsageText: "txdump [-r <endpoint>] <file.in>",
Action: txDump,
Flags: txDumpFlags,
},
}, },
}, },
} }

51
cli/util/dump.go Normal file
View file

@ -0,0 +1,51 @@
package util
import (
"encoding/json"
"fmt"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/cli/paramcontext"
"github.com/nspcc-dev/neo-go/cli/query"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/urfave/cli"
)
func txDump(ctx *cli.Context) error {
if len(ctx.Args()) == 0 {
return cli.NewExitError("missing input file", 1)
}
c, err := paramcontext.Read(ctx.Args()[0])
if err != nil {
return cli.NewExitError(err, 1)
}
tx, ok := c.Verifiable.(*transaction.Transaction)
if !ok {
return cli.NewExitError("verifiable item is not a transaction", 1)
}
query.DumpApplicationLog(ctx, nil, tx, nil, true)
if ctx.String(options.RPCEndpointFlag) != "" {
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
var err error
cl, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}
res, err := cl.InvokeScript(tx.Script, tx.Signers)
if err != nil {
return cli.NewExitError(err, 1)
}
resS, err := json.MarshalIndent(res, "", " ")
if err != nil {
return cli.NewExitError(err, 1)
}
fmt.Fprintln(ctx.App.Writer, string(resS))
}
return nil
}