Merge pull request #2816 from nspcc-dev/fix-pointer-serialization

Fix pointer serialization
This commit is contained in:
Roman Khimov 2022-11-20 22:59:52 +07:00 committed by GitHub
commit 0039615ae3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 22 deletions

View file

@ -60,15 +60,22 @@ const (
const (
verboseFlagFullName = "verbose"
historicFlagFullName = "historic"
gasFlagFullName = "gas"
backwardsFlagFullName = "backwards"
diffFlagFullName = "diff"
)
var historicFlag = cli.IntFlag{
var (
historicFlag = cli.IntFlag{
Name: historicFlagFullName,
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{
{
@ -143,8 +150,8 @@ Example:
{
Name: "loadnef",
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>, ...]`,
Flags: []cli.Flag{historicFlag},
UsageText: `loadnef [--historic <height>] [--gas <int>] <file> <manifest> [<signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
Description: `<file> and <manifest> parameters are mandatory.
` + cmdargs.SignersParsingDoc + `
@ -156,8 +163,8 @@ Example:
{
Name: "loadbase64",
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>, ...]`,
Flags: []cli.Flag{historicFlag},
UsageText: `loadbase64 [--historic <height>] [--gas <int>] <string> [<signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
Description: `<string> is mandatory parameter.
` + cmdargs.SignersParsingDoc + `
@ -169,8 +176,8 @@ Example:
{
Name: "loadhex",
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>, ...]`,
Flags: []cli.Flag{historicFlag},
UsageText: `loadhex [--historic <height>] [--gas <int>] <string> [<signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
Description: `<string> is mandatory parameter.
` + cmdargs.SignersParsingDoc + `
@ -182,8 +189,8 @@ Example:
{
Name: "loadgo",
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>, ...]`,
Flags: []cli.Flag{historicFlag},
UsageText: `loadgo [--historic <height>] [--gas <int>] <file> [<signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
Description: `<file> is mandatory parameter.
` + cmdargs.SignersParsingDoc + `
@ -195,10 +202,13 @@ Example:
{
Name: "loadtx",
Usage: "Load transaction into the VM from chain or from parameter context file",
UsageText: `loadtx [--historic <height>] <file-or-hash>`,
Flags: []cli.Flag{historicFlag},
UsageText: `loadtx [--historic <height>] [--gas <int>] <file-or-hash>`,
Flags: []cli.Flag{historicFlag, gasFlag},
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.
@ -209,8 +219,8 @@ Example:
{
Name: "loaddeployed",
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>, ...]`,
Flags: []cli.Flag{historicFlag},
UsageText: `loaddeployed [--historic <height>] [--gas <int>] <hash-or-address-or-id> [<signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
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.
@ -640,12 +650,22 @@ func handleSlots(c *cli.Context) error {
// prepareVM retrieves --historic flag from context (if set) and resets app state
// (to the specified historic height if given).
func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
var err error
if c.IsSet(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)
}
return resetState(c.App, tx)
if err != nil {
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 {
@ -812,6 +832,9 @@ func handleLoadTx(c *cli.Context) error {
return err
}
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())
changePrompt(c.App)
return nil
@ -856,7 +879,9 @@ func handleLoadDeployed(c *cli.Context) error {
return err
}
ic = getInteropContextFromContext(c.App) // fetch newly-created IC.
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)
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr())
setManifestInContext(c.App, &cs.Manifest)

View file

@ -1118,6 +1118,8 @@ func TestLoadtx(t *testing.T) {
e.runProg(t,
"loadtx "+tx.Hash().StringLE(), // hash LE
"run",
"loadtx --gas 10000 "+tx.Hash().StringLE(), // with GAS
"run",
"loadtx 0x"+tx.Hash().StringLE(), // hash LE with 0x prefix
"run",
"loadtx '"+tmp+"'", // Tx from parameter context file.
@ -1128,6 +1130,8 @@ func TestLoadtx(t *testing.T) {
e.checkNextLine(t, "READY: loaded \\d+ instructions")
e.checkStack(t, 1)
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.checkNextLine(t, "READY: loaded \\d+ instructions")
e.checkStack(t, 1)
@ -1147,6 +1151,8 @@ func TestLoaddeployed(t *testing.T) {
e.runProg(t,
"loaddeployed "+h.StringLE(), // hash LE
"run get 1",
"loaddeployed --gas 420000 "+h.StringLE(), // gas-limited
"run get 1",
"loaddeployed 0x"+h.StringLE(), // hash LE with 0x prefix
"run get 1",
"loaddeployed 1", // contract ID
@ -1172,6 +1178,8 @@ func TestLoaddeployed(t *testing.T) {
e.checkNextLine(t, "READY: loaded \\d+ instructions")
e.checkStack(t, []byte{2})
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.checkNextLine(t, "READY: loaded \\d+ instructions")
e.checkStack(t, []byte{2})

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"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
@ -168,6 +169,13 @@ func (w *SerializationContext) serialize(item Item) error {
} else {
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:
w.data = append(w.data, byte(ArrayT))
if err := w.writeArray(item, t.value, start); err != nil {
@ -311,6 +319,12 @@ func (r *deserContext) decodeBinary() Item {
return NewInterop(nil)
}
fallthrough
case PointerT:
if r.allowInvalid {
pos := int(r.ReadVarUint())
return NewPointerWithHash(pos, nil, util.Uint160{})
}
fallthrough
default:
if t == InvalidT && r.allowInvalid {
return nil

View file

@ -76,6 +76,7 @@ func TestSerialize(t *testing.T) {
})
t.Run("invalid", func(t *testing.T) {
testSerialize(t, ErrUnserializable, NewInterop(42))
testSerialize(t, ErrUnserializable, NewPointer(42, []byte{}))
testSerialize(t, ErrUnserializable, nil)
t.Run("protected interop", func(t *testing.T) {
@ -93,6 +94,22 @@ func TestSerialize(t *testing.T) {
require.NoError(t, r.Err)
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) {
w := io.NewBufBinWriter()
EncodeBinaryProtected(nil, w.BinWriter)