forked from TrueCloudLab/neoneo-go
cli/wallet: allow to testinvoke transaction before signing
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
e2a93f3e41
commit
bf2ca35453
5 changed files with 124 additions and 4 deletions
|
@ -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+`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
51
cli/util/dump.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue