vm: allow historic calls inside VM CLI

This commit is contained in:
Anna Shaleva 2022-10-04 14:53:31 +03:00
parent 79e13f73d8
commit ff03c33e6d
2 changed files with 97 additions and 22 deletions

View file

@ -54,9 +54,16 @@ const (
// Various flag names.
const (
verboseFlagFullName = "verbose"
verboseFlagFullName = "verbose"
historicFlagFullName = "historic"
)
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.",
}
var commands = []cli.Command{
{
Name: "exit",
@ -113,7 +120,8 @@ var commands = []cli.Command{
Name: "loadnef",
Usage: "Load a NEF-consistent script into the VM",
UsageText: `loadnef <file> <manifest>`,
Description: `loadnef <file> <manifest>
Flags: []cli.Flag{historicFlag},
Description: `loadnef [--historic <height>] <file> <manifest>
both parameters are mandatory, example:
> loadnef /path/to/script.nef /path/to/manifest.json`,
Action: handleLoadNEF,
@ -121,8 +129,9 @@ both parameters are mandatory, example:
{
Name: "loadbase64",
Usage: "Load a base64-encoded script string into the VM",
UsageText: `loadbase64 <string>`,
Description: `loadbase64 <string>
UsageText: `loadbase64 [--historic <height>] <string>`,
Flags: []cli.Flag{historicFlag},
Description: `loadbase64 [--historic <height>] <string>
<string> is mandatory parameter, example:
> loadbase64 AwAQpdToAAAADBQV9ehtQR1OrVZVhtHtoUHRfoE+agwUzmFvf3Rhfg/EuAVYOvJgKiON9j8TwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I4`,
@ -131,8 +140,9 @@ both parameters are mandatory, example:
{
Name: "loadhex",
Usage: "Load a hex-encoded script string into the VM",
UsageText: `loadhex <string>`,
Description: `loadhex <string>
UsageText: `loadhex [--historic <height>] <string>`,
Flags: []cli.Flag{historicFlag},
Description: `loadhex [--historic <height>] <string>
<string> is mandatory parameter, example:
> loadhex 0c0c48656c6c6f20776f726c6421`,
@ -141,8 +151,9 @@ both parameters are mandatory, example:
{
Name: "loadgo",
Usage: "Compile and load a Go file with the manifest into the VM",
UsageText: `loadgo <file>`,
Description: `loadgo <file>
UsageText: `loadgo [--historic <height>] <file>`,
Flags: []cli.Flag{historicFlag},
Description: `loadgo [--historic <height>] <file>
<file> is mandatory parameter, example:
> loadgo /path/to/file.go`,
@ -150,7 +161,8 @@ both parameters are mandatory, example:
},
{
Name: "reset",
Usage: "Unload compiled script from the VM",
Usage: "Unload compiled script from the VM and reset context to proper (possibly, historic) state",
Flags: []cli.Flag{historicFlag},
Action: handleReset,
},
{
@ -485,8 +497,19 @@ func handleSlots(c *cli.Context) error {
return nil
}
// prepareVM retrieves --historic flag from context (if set) and resets app state
// (to the specified historic height if given).
func prepareVM(c *cli.Context) error {
if c.IsSet(historicFlagFullName) {
height := c.Int(historicFlagFullName)
return resetState(c.App, uint32(height))
}
return resetState(c.App)
}
func handleLoadNEF(c *cli.Context) error {
err := resetState(c.App)
err := prepareVM(c)
if err != nil {
return err
}
@ -509,7 +532,7 @@ func handleLoadNEF(c *cli.Context) error {
}
func handleLoadBase64(c *cli.Context) error {
err := resetState(c.App)
err := prepareVM(c)
if err != nil {
return err
}
@ -529,7 +552,7 @@ func handleLoadBase64(c *cli.Context) error {
}
func handleLoadHex(c *cli.Context) error {
err := resetState(c.App)
err := prepareVM(c)
if err != nil {
return err
}
@ -549,7 +572,7 @@ func handleLoadHex(c *cli.Context) error {
}
func handleLoadGo(c *cli.Context) error {
err := resetState(c.App)
err := prepareVM(c)
if err != nil {
return err
}
@ -579,7 +602,7 @@ func handleLoadGo(c *cli.Context) error {
}
func handleReset(c *cli.Context) error {
err := resetState(c.App)
err := prepareVM(c)
if err != nil {
return err
}
@ -595,13 +618,25 @@ func finalizeInteropContext(app *cli.App) {
// resetInteropContext calls finalizer for current interop context and replaces
// it with the newly created one.
func resetInteropContext(app *cli.App) error {
func resetInteropContext(app *cli.App, height ...uint32) error {
finalizeInteropContext(app)
bc := getChainFromContext(app)
newIc, err := bc.GetTestVM(trigger.Application, nil, nil)
if err != nil {
return fmt.Errorf("failed to create test VM: %w", err)
var (
newIc *interop.Context
err error
)
if len(height) != 0 {
newIc, err = bc.GetTestHistoricVM(trigger.Application, nil, height[0]+1)
if err != nil {
return fmt.Errorf("failed to create historic VM for height %d: %w", height[0], err)
}
} else {
newIc, err = bc.GetTestVM(trigger.Application, nil, nil)
if err != nil {
return fmt.Errorf("failed to create VM: %w", err)
}
}
setInteropContextInContext(app, newIc)
return nil
}
@ -613,10 +648,10 @@ func resetManifest(app *cli.App) {
// resetState resets state of the app (clear interop context and manifest) so that it's ready
// to load new program.
func resetState(app *cli.App) error {
err := resetInteropContext(app)
func resetState(app *cli.App, height ...uint32) error {
err := resetInteropContext(app, height...)
if err != nil {
return fmt.Errorf("failed to reset interop context state: %w", err)
return err
}
resetManifest(app)
return nil
@ -822,7 +857,9 @@ func handleEvents(c *cli.Context) error {
func handleEnv(c *cli.Context) error {
bc := getChainFromContext(c.App)
cfg := getChainConfigFromContext(c.App)
message := fmt.Sprintf("Chain height: %d\nNetwork magic: %d\nDB type: %s\n", bc.BlockHeight(), bc.GetConfig().Magic, cfg.ApplicationConfiguration.DBConfiguration.Type)
ic := getInteropContextFromContext(c.App)
message := fmt.Sprintf("Chain height: %d\nVM height (may differ from chain height in case of historic call): %d\nNetwork magic: %d\nDB type: %s\n",
bc.BlockHeight(), ic.BlockHeight(), bc.GetConfig().Magic, cfg.ApplicationConfiguration.DBConfiguration.Type)
if c.Bool(verboseFlagFullName) {
cfgBytes, err := json.MarshalIndent(cfg, "", "\t")
if err != nil {

View file

@ -760,6 +760,31 @@ func TestRunWithState(t *testing.T) {
e.checkStack(t, 3)
}
func TestRunWithHistoricState(t *testing.T) {
e := newTestVMClIWithState(t)
script := io.NewBufBinWriter()
h, err := e.cli.chain.GetContractScriptHash(1) // examples/storage/storage.go
require.NoError(t, err)
emit.AppCall(script.BinWriter, h, "get", callflag.All, 1)
b := script.Bytes()
e.runProg(t,
"loadhex "+hex.EncodeToString(b), // normal invocation
"run",
"loadhex --historic 3 "+hex.EncodeToString(b), // historic invocation, old value should be retrieved
"run",
"loadhex --historic 0 "+hex.EncodeToString(b), // historic invocation, contract is not deployed yet
"run",
)
e.checkNextLine(t, "READY: loaded 36 instructions")
e.checkStack(t, []byte{2})
e.checkNextLine(t, "READY: loaded 36 instructions")
e.checkStack(t, []byte{1})
e.checkNextLine(t, "READY: loaded 36 instructions")
e.checkNextLineExact(t, "Error: at instruction 31 (SYSCALL): failed to invoke syscall 1381727586: called contract a00e3c2643a08a452d8b0bdd31849ae11a17c445 not found: key not found\n")
}
func TestEvents(t *testing.T) {
e := newTestVMClIWithState(t)
@ -792,6 +817,7 @@ func TestEnv(t *testing.T) {
e := newTestVMCLI(t)
e.runProg(t, "env")
e.checkNextLine(t, "Chain height: 0")
e.checkNextLineExact(t, "VM height (may differ from chain height in case of historic call): 0\n")
e.checkNextLine(t, "Network magic: 42")
e.checkNextLine(t, "DB type: inmemory")
})
@ -799,6 +825,17 @@ func TestEnv(t *testing.T) {
e := newTestVMClIWithState(t)
e.runProg(t, "env")
e.checkNextLine(t, "Chain height: 5")
e.checkNextLineExact(t, "VM height (may differ from chain height in case of historic call): 5\n")
e.checkNextLine(t, "Network magic: 42")
e.checkNextLine(t, "DB type: leveldb")
})
t.Run("setup with historic state", func(t *testing.T) {
e := newTestVMClIWithState(t)
e.runProg(t, "loadbase64 --historic 3 "+base64.StdEncoding.EncodeToString([]byte{byte(opcode.PUSH1)}),
"env")
e.checkNextLine(t, "READY: loaded 1 instructions")
e.checkNextLine(t, "Chain height: 5")
e.checkNextLineExact(t, "VM height (may differ from chain height in case of historic call): 3\n")
e.checkNextLine(t, "Network magic: 42")
e.checkNextLine(t, "DB type: leveldb")
})
@ -806,6 +843,7 @@ func TestEnv(t *testing.T) {
e := newTestVMClIWithState(t)
e.runProg(t, "env -v")
e.checkNextLine(t, "Chain height: 5")
e.checkNextLineExact(t, "VM height (may differ from chain height in case of historic call): 5\n")
e.checkNextLine(t, "Network magic: 42")
e.checkNextLine(t, "DB type: leveldb")
e.checkNextLine(t, "Node config:") // Do not check exact node config.