forked from TrueCloudLab/neoneo-go
Merge pull request #2816 from nspcc-dev/fix-pointer-serialization
Fix pointer serialization
This commit is contained in:
commit
0039615ae3
4 changed files with 86 additions and 22 deletions
|
@ -60,15 +60,22 @@ const (
|
||||||
const (
|
const (
|
||||||
verboseFlagFullName = "verbose"
|
verboseFlagFullName = "verbose"
|
||||||
historicFlagFullName = "historic"
|
historicFlagFullName = "historic"
|
||||||
|
gasFlagFullName = "gas"
|
||||||
backwardsFlagFullName = "backwards"
|
backwardsFlagFullName = "backwards"
|
||||||
diffFlagFullName = "diff"
|
diffFlagFullName = "diff"
|
||||||
)
|
)
|
||||||
|
|
||||||
var historicFlag = cli.IntFlag{
|
var (
|
||||||
Name: historicFlagFullName,
|
historicFlag = cli.IntFlag{
|
||||||
Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " +
|
Name: historicFlagFullName,
|
||||||
"Assuming that block N-th is specified as an argument, the historic invocation is based on the storage state of height N and fake currently-accepting block with index N+1.",
|
Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " +
|
||||||
}
|
"Assuming that block N-th is specified as an argument, the historic invocation is based on the storage state of height N and fake currently-accepting block with index N+1.",
|
||||||
|
}
|
||||||
|
gasFlag = cli.Int64Flag{
|
||||||
|
Name: gasFlagFullName,
|
||||||
|
Usage: "GAS limit for this execution (integer number, satoshi).",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var commands = []cli.Command{
|
var commands = []cli.Command{
|
||||||
{
|
{
|
||||||
|
@ -143,8 +150,8 @@ Example:
|
||||||
{
|
{
|
||||||
Name: "loadnef",
|
Name: "loadnef",
|
||||||
Usage: "Load a NEF-consistent script into the VM optionally attaching to it provided signers with scopes",
|
Usage: "Load a NEF-consistent script into the VM optionally attaching to it provided signers with scopes",
|
||||||
UsageText: `loadnef [--historic <height>] <file> <manifest> [<signer-with-scope>, ...]`,
|
UsageText: `loadnef [--historic <height>] [--gas <int>] <file> <manifest> [<signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag},
|
||||||
Description: `<file> and <manifest> parameters are mandatory.
|
Description: `<file> and <manifest> parameters are mandatory.
|
||||||
|
|
||||||
` + cmdargs.SignersParsingDoc + `
|
` + cmdargs.SignersParsingDoc + `
|
||||||
|
@ -156,8 +163,8 @@ Example:
|
||||||
{
|
{
|
||||||
Name: "loadbase64",
|
Name: "loadbase64",
|
||||||
Usage: "Load a base64-encoded script string into the VM optionally attaching to it provided signers with scopes",
|
Usage: "Load a base64-encoded script string into the VM optionally attaching to it provided signers with scopes",
|
||||||
UsageText: `loadbase64 [--historic <height>] <string> [<signer-with-scope>, ...]`,
|
UsageText: `loadbase64 [--historic <height>] [--gas <int>] <string> [<signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag},
|
||||||
Description: `<string> is mandatory parameter.
|
Description: `<string> is mandatory parameter.
|
||||||
|
|
||||||
` + cmdargs.SignersParsingDoc + `
|
` + cmdargs.SignersParsingDoc + `
|
||||||
|
@ -169,8 +176,8 @@ Example:
|
||||||
{
|
{
|
||||||
Name: "loadhex",
|
Name: "loadhex",
|
||||||
Usage: "Load a hex-encoded script string into the VM optionally attaching to it provided signers with scopes",
|
Usage: "Load a hex-encoded script string into the VM optionally attaching to it provided signers with scopes",
|
||||||
UsageText: `loadhex [--historic <height>] <string> [<signer-with-scope>, ...]`,
|
UsageText: `loadhex [--historic <height>] [--gas <int>] <string> [<signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag},
|
||||||
Description: `<string> is mandatory parameter.
|
Description: `<string> is mandatory parameter.
|
||||||
|
|
||||||
` + cmdargs.SignersParsingDoc + `
|
` + cmdargs.SignersParsingDoc + `
|
||||||
|
@ -182,8 +189,8 @@ Example:
|
||||||
{
|
{
|
||||||
Name: "loadgo",
|
Name: "loadgo",
|
||||||
Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes",
|
Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes",
|
||||||
UsageText: `loadgo [--historic <height>] <file> [<signer-with-scope>, ...]`,
|
UsageText: `loadgo [--historic <height>] [--gas <int>] <file> [<signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag},
|
||||||
Description: `<file> is mandatory parameter.
|
Description: `<file> is mandatory parameter.
|
||||||
|
|
||||||
` + cmdargs.SignersParsingDoc + `
|
` + cmdargs.SignersParsingDoc + `
|
||||||
|
@ -195,10 +202,13 @@ Example:
|
||||||
{
|
{
|
||||||
Name: "loadtx",
|
Name: "loadtx",
|
||||||
Usage: "Load transaction into the VM from chain or from parameter context file",
|
Usage: "Load transaction into the VM from chain or from parameter context file",
|
||||||
UsageText: `loadtx [--historic <height>] <file-or-hash>`,
|
UsageText: `loadtx [--historic <height>] [--gas <int>] <file-or-hash>`,
|
||||||
Flags: []cli.Flag{historicFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag},
|
||||||
Description: `Load transaction into the VM from chain or from parameter context file.
|
Description: `Load transaction into the VM from chain or from parameter context file.
|
||||||
The transaction script will be loaded into VM; the resulting execution context will use the provided transaction as script container including its signers, hash and nonce.
|
The transaction script will be loaded into VM; the resulting execution context
|
||||||
|
will use the provided transaction as script container including its signers,
|
||||||
|
hash and nonce. It'll also use transaction's system fee value as GAS limit if
|
||||||
|
--gas option is not used.
|
||||||
|
|
||||||
<file-or-hash> is mandatory parameter.
|
<file-or-hash> is mandatory parameter.
|
||||||
|
|
||||||
|
@ -209,8 +219,8 @@ Example:
|
||||||
{
|
{
|
||||||
Name: "loaddeployed",
|
Name: "loaddeployed",
|
||||||
Usage: "Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes",
|
Usage: "Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes",
|
||||||
UsageText: `loaddeployed [--historic <height>] <hash-or-address-or-id> [<signer-with-scope>, ...]`,
|
UsageText: `loaddeployed [--historic <height>] [--gas <int>] <hash-or-address-or-id> [<signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag},
|
||||||
Description: `Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes.
|
Description: `Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes.
|
||||||
If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded.
|
If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded.
|
||||||
|
|
||||||
|
@ -640,12 +650,22 @@ func handleSlots(c *cli.Context) error {
|
||||||
// prepareVM retrieves --historic flag from context (if set) and resets app state
|
// prepareVM retrieves --historic flag from context (if set) and resets app state
|
||||||
// (to the specified historic height if given).
|
// (to the specified historic height if given).
|
||||||
func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
|
func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
|
||||||
|
var err error
|
||||||
if c.IsSet(historicFlagFullName) {
|
if c.IsSet(historicFlagFullName) {
|
||||||
height := c.Int(historicFlagFullName)
|
height := c.Int(historicFlagFullName)
|
||||||
return resetState(c.App, tx, uint32(height))
|
err = resetState(c.App, tx, uint32(height))
|
||||||
|
} else {
|
||||||
|
err = resetState(c.App, tx)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
return resetState(c.App, tx)
|
return err
|
||||||
|
}
|
||||||
|
if c.IsSet(gasFlagFullName) {
|
||||||
|
gas := c.Int64(gasFlagFullName)
|
||||||
|
v := getVMFromContext(c.App)
|
||||||
|
v.GasLimit = gas
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLoadNEF(c *cli.Context) error {
|
func handleLoadNEF(c *cli.Context) error {
|
||||||
|
@ -812,6 +832,9 @@ func handleLoadTx(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v := getVMFromContext(c.App)
|
v := getVMFromContext(c.App)
|
||||||
|
if v.GasLimit == -1 {
|
||||||
|
v.GasLimit = tx.SystemFee
|
||||||
|
}
|
||||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||||
changePrompt(c.App)
|
changePrompt(c.App)
|
||||||
return nil
|
return nil
|
||||||
|
@ -856,7 +879,9 @@ func handleLoadDeployed(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ic = getInteropContextFromContext(c.App) // fetch newly-created IC.
|
ic = getInteropContextFromContext(c.App) // fetch newly-created IC.
|
||||||
ic.ReuseVM(ic.VM) // clear previously loaded program and context.
|
gasLimit := ic.VM.GasLimit
|
||||||
|
ic.ReuseVM(ic.VM) // clear previously loaded program and context.
|
||||||
|
ic.VM.GasLimit = gasLimit
|
||||||
ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
||||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr())
|
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr())
|
||||||
setManifestInContext(c.App, &cs.Manifest)
|
setManifestInContext(c.App, &cs.Manifest)
|
||||||
|
|
|
@ -1118,6 +1118,8 @@ func TestLoadtx(t *testing.T) {
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadtx "+tx.Hash().StringLE(), // hash LE
|
"loadtx "+tx.Hash().StringLE(), // hash LE
|
||||||
"run",
|
"run",
|
||||||
|
"loadtx --gas 10000 "+tx.Hash().StringLE(), // with GAS
|
||||||
|
"run",
|
||||||
"loadtx 0x"+tx.Hash().StringLE(), // hash LE with 0x prefix
|
"loadtx 0x"+tx.Hash().StringLE(), // hash LE with 0x prefix
|
||||||
"run",
|
"run",
|
||||||
"loadtx '"+tmp+"'", // Tx from parameter context file.
|
"loadtx '"+tmp+"'", // Tx from parameter context file.
|
||||||
|
@ -1128,6 +1130,8 @@ func TestLoadtx(t *testing.T) {
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, 1)
|
e.checkStack(t, 1)
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
|
e.checkError(t, errors.New("at instruction 3 (PACK): gas limit is exceeded"))
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, 1)
|
e.checkStack(t, 1)
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, 1)
|
e.checkStack(t, 1)
|
||||||
|
@ -1147,6 +1151,8 @@ func TestLoaddeployed(t *testing.T) {
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loaddeployed "+h.StringLE(), // hash LE
|
"loaddeployed "+h.StringLE(), // hash LE
|
||||||
"run get 1",
|
"run get 1",
|
||||||
|
"loaddeployed --gas 420000 "+h.StringLE(), // gas-limited
|
||||||
|
"run get 1",
|
||||||
"loaddeployed 0x"+h.StringLE(), // hash LE with 0x prefix
|
"loaddeployed 0x"+h.StringLE(), // hash LE with 0x prefix
|
||||||
"run get 1",
|
"run get 1",
|
||||||
"loaddeployed 1", // contract ID
|
"loaddeployed 1", // contract ID
|
||||||
|
@ -1172,6 +1178,8 @@ func TestLoaddeployed(t *testing.T) {
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, []byte{2})
|
e.checkStack(t, []byte{2})
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
|
e.checkError(t, errors.New("at instruction 63 (SYSCALL): failed to invoke syscall 837311890: insufficient amount of gas"))
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, []byte{2})
|
e.checkStack(t, []byte{2})
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, []byte{2})
|
e.checkStack(t, []byte{2})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxDeserialized is the maximum number one deserialized item can contain
|
// MaxDeserialized is the maximum number one deserialized item can contain
|
||||||
|
@ -168,6 +169,13 @@ func (w *SerializationContext) serialize(item Item) error {
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("%w: Interop", ErrUnserializable)
|
return fmt.Errorf("%w: Interop", ErrUnserializable)
|
||||||
}
|
}
|
||||||
|
case *Pointer:
|
||||||
|
if w.allowInvalid {
|
||||||
|
w.data = append(w.data, byte(PointerT))
|
||||||
|
w.appendVarUint(uint64(t.pos))
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%w: Pointer", ErrUnserializable)
|
||||||
|
}
|
||||||
case *Array:
|
case *Array:
|
||||||
w.data = append(w.data, byte(ArrayT))
|
w.data = append(w.data, byte(ArrayT))
|
||||||
if err := w.writeArray(item, t.value, start); err != nil {
|
if err := w.writeArray(item, t.value, start); err != nil {
|
||||||
|
@ -311,6 +319,12 @@ func (r *deserContext) decodeBinary() Item {
|
||||||
return NewInterop(nil)
|
return NewInterop(nil)
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case PointerT:
|
||||||
|
if r.allowInvalid {
|
||||||
|
pos := int(r.ReadVarUint())
|
||||||
|
return NewPointerWithHash(pos, nil, util.Uint160{})
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
default:
|
default:
|
||||||
if t == InvalidT && r.allowInvalid {
|
if t == InvalidT && r.allowInvalid {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -76,6 +76,7 @@ func TestSerialize(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
testSerialize(t, ErrUnserializable, NewInterop(42))
|
testSerialize(t, ErrUnserializable, NewInterop(42))
|
||||||
|
testSerialize(t, ErrUnserializable, NewPointer(42, []byte{}))
|
||||||
testSerialize(t, ErrUnserializable, nil)
|
testSerialize(t, ErrUnserializable, nil)
|
||||||
|
|
||||||
t.Run("protected interop", func(t *testing.T) {
|
t.Run("protected interop", func(t *testing.T) {
|
||||||
|
@ -93,6 +94,22 @@ func TestSerialize(t *testing.T) {
|
||||||
require.NoError(t, r.Err)
|
require.NoError(t, r.Err)
|
||||||
require.IsType(t, (*Interop)(nil), item)
|
require.IsType(t, (*Interop)(nil), item)
|
||||||
})
|
})
|
||||||
|
t.Run("protected pointer", func(t *testing.T) {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
EncodeBinaryProtected(NewPointer(42, []byte{}), w.BinWriter)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
|
||||||
|
data := w.Bytes()
|
||||||
|
r := io.NewBinReaderFromBuf(data)
|
||||||
|
DecodeBinary(r)
|
||||||
|
require.Error(t, r.Err)
|
||||||
|
|
||||||
|
r = io.NewBinReaderFromBuf(data)
|
||||||
|
item := DecodeBinaryProtected(r)
|
||||||
|
require.NoError(t, r.Err)
|
||||||
|
require.IsType(t, (*Pointer)(nil), item)
|
||||||
|
require.Equal(t, 42, item.Value())
|
||||||
|
})
|
||||||
t.Run("protected nil", func(t *testing.T) {
|
t.Run("protected nil", func(t *testing.T) {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
EncodeBinaryProtected(nil, w.BinWriter)
|
EncodeBinaryProtected(nil, w.BinWriter)
|
||||||
|
|
Loading…
Add table
Reference in a new issue