diff --git a/cli/multisig_test.go b/cli/multisig_test.go index e189f1518..4919f1e96 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -2,6 +2,8 @@ package main import ( "encoding/hex" + "encoding/json" + "fmt" "math/big" "os" "path" @@ -10,7 +12,9 @@ import ( "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/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/vm" "github.com/stretchr/testify/require" ) @@ -92,6 +96,27 @@ func TestSignMultisigTx(t *testing.T) { "--wallet", wallet2Path, "--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.Run(t, "neo-go", "wallet", "sign", "--rpc-endpoint", "http://"+e.RPC.Addr, @@ -153,3 +178,21 @@ func TestSignMultisigTx(t *testing.T) { 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+`) + } +} diff --git a/cli/query/query.go b/cli/query/query.go index 8d15e121e..8561c2f17 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -14,6 +14,7 @@ import ( "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/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/fixedn" "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 } -func dumpApplicationLog(ctx *cli.Context, res *result.ApplicationLog, tx *result.TransactionOutputRaw) { - verbose := ctx.Bool("verbose") +func DumpApplicationLog( + ctx *cli.Context, + res *result.ApplicationLog, + tx *transaction.Transaction, + txMeta *result.TransactionMetadata, + verbose bool) { buf := bytes.NewBuffer(nil) // 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 { _, _ = tw.Write([]byte("ValidUntil:\t" + strconv.FormatUint(uint64(tx.ValidUntilBlock), 10) + "\n")) } 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 { _, _ = tw.Write([]byte("Success:\tunknown (no execution data)\n")) } 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("NetworkFee:\t" + fixedn.Fixed8(tx.NetworkFee).String() + " GAS\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 { for _, e := range res.Executions { if e.VMState != vm.HaltState { diff --git a/cli/query_test.go b/cli/query_test.go index f0fc761fb..32c658939 100644 --- a/cli/query_test.go +++ b/cli/query_test.go @@ -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, `NetworkFee:\s+`+fixedn.Fixed8(tx.NetworkFee).String()+" GAS$") 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 { e.checkNextLine(t, `Exception:\s+`+regexp.QuoteMeta(res[0].Execution.FaultException)) diff --git a/cli/util/convert.go b/cli/util/convert.go index aecc98b43..7fbd7a5bc 100644 --- a/cli/util/convert.go +++ b/cli/util/convert.go @@ -3,12 +3,14 @@ package util import ( "fmt" + "github.com/nspcc-dev/neo-go/cli/options" vmcli "github.com/nspcc-dev/neo-go/pkg/vm/cli" "github.com/urfave/cli" ) // NewCommands returns util commands for neo-go CLI. func NewCommands() []cli.Command { + txDumpFlags := append([]cli.Flag{}, options.RPC...) return []cli.Command{ { Name: "util", @@ -23,6 +25,13 @@ func NewCommands() []cli.Command { and converted to other formats. Strings are escaped and output in quotes.`, Action: handleParse, }, + { + Name: "txdump", + Usage: "Dump transaction stored in file", + UsageText: "txdump [-r ] ", + Action: txDump, + Flags: txDumpFlags, + }, }, }, } diff --git a/cli/util/dump.go b/cli/util/dump.go new file mode 100644 index 000000000..427bfb732 --- /dev/null +++ b/cli/util/dump.go @@ -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 +}