vm: add 'storage' VM CLI command to dump storage items

Another nice one, very useful for debugging.
This commit is contained in:
Anna Shaleva 2022-10-04 15:38:42 +03:00
parent ff03c33e6d
commit 8c78177806
2 changed files with 105 additions and 2 deletions

View file

@ -17,6 +17,7 @@ import (
"github.com/chzyer/readline" "github.com/chzyer/readline"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
@ -54,8 +55,9 @@ const (
// Various flag names. // Various flag names.
const ( const (
verboseFlagFullName = "verbose" verboseFlagFullName = "verbose"
historicFlagFullName = "historic" historicFlagFullName = "historic"
backwardsFlagFullName = "backwards"
) )
var historicFlag = cli.IntFlag{ var historicFlag = cli.IntFlag{
@ -267,6 +269,32 @@ Example:
> env -v`, > env -v`,
Action: handleEnv, Action: handleEnv,
}, },
{
Name: "storage",
Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation. " +
"Can be used if no script is loaded. " +
"Hex-encoded storage items prefix may be specified (empty by default to return the whole set of storage items). " +
"If seek prefix is not empty, then it's trimmed from the resulting keys." +
"Items are sorted. Backwards seek direction may be specified (false by default, which means forwards storage seek direction).",
UsageText: `storage <hash-or-address-or-id> [<prefix>] [--backwards]`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: backwardsFlagFullName + ",b",
Usage: "Backwards traversal direction",
},
},
Description: `storage <hash-or-address-or-id> <prefix> --backwards
Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation.
Can be used if no script is loaded.
Hex-encoded storage items prefix may be specified (empty by default to return the whole set of storage items).
If seek prefix is not empty, then it's trimmed from the resulting keys.
Items are sorted. Backwards seek direction may be specified (false by default, which means forwards storage seek direction).
Example:
> storage 0x0000000009070e030d0f0e020d0c06050e030c02 030e --backwards`,
Action: handleStorage,
},
} }
var completer *readline.PrefixCompleter var completer *readline.PrefixCompleter
@ -871,6 +899,50 @@ func handleEnv(c *cli.Context) error {
return nil return nil
} }
func handleStorage(c *cli.Context) error {
if !c.Args().Present() {
return errors.New("contract hash, address or ID is mandatory argument")
}
hashOrID := c.Args().Get(0)
var (
id int32
ic = getInteropContextFromContext(c.App)
prefix []byte
backwards bool
)
h, err := flags.ParseAddress(hashOrID)
if err != nil {
i, err := strconv.Atoi(hashOrID)
if err != nil {
return fmt.Errorf("failed to parse contract hash, address or ID: %w", err)
}
id = int32(i)
} else {
cs, err := ic.GetContract(h)
if err != nil {
return fmt.Errorf("contract %s not found: %w", h.StringLE(), err)
}
id = cs.ID
}
if c.NArg() > 1 {
prefix, err = hex.DecodeString(c.Args().Get(1))
if err != nil {
return fmt.Errorf("failed to decode prefix from hex: %w", err)
}
}
if c.Bool(backwardsFlagFullName) {
backwards = true
}
ic.DAO.Seek(id, storage.SeekRange{
Prefix: prefix,
Backwards: backwards,
}, func(k, v []byte) bool {
fmt.Fprintf(c.App.Writer, "%s: %v\n", hex.EncodeToString(k), hex.EncodeToString(v))
return true
})
return nil
}
func dumpEvents(app *cli.App) (string, error) { func dumpEvents(app *cli.App) (string, error) {
ic := getInteropContextFromContext(app) ic := getInteropContextFromContext(app)
if len(ic.Notifications) == 0 { if len(ic.Notifications) == 0 {

View file

@ -221,6 +221,12 @@ func (e *executor) checkEvents(t *testing.T, isKeywordExpected bool, events ...s
require.NoError(t, err) require.NoError(t, err)
} }
func (e *executor) checkStorage(t *testing.T, kvs ...storage.KeyValue) {
for _, kv := range kvs {
e.checkNextLine(t, fmt.Sprintf("%s: %s", hex.EncodeToString(kv.Key), hex.EncodeToString(kv.Value)))
}
}
func (e *executor) checkSlot(t *testing.T, items ...interface{}) { func (e *executor) checkSlot(t *testing.T, items ...interface{}) {
d := json.NewDecoder(e.out) d := json.NewDecoder(e.out)
var actual interface{} var actual interface{}
@ -849,3 +855,28 @@ func TestEnv(t *testing.T) {
e.checkNextLine(t, "Node config:") // Do not check exact node config. e.checkNextLine(t, "Node config:") // Do not check exact node config.
}) })
} }
func TestDumpStorage(t *testing.T) {
e := newTestVMClIWithState(t)
h, err := e.cli.chain.GetContractScriptHash(1) // examples/storage/storage.go
require.NoError(t, err)
expected := []storage.KeyValue{
{Key: []byte{1}, Value: []byte{2}},
{Key: []byte{2}, Value: []byte{2}},
}
e.runProg(t,
"storage "+h.StringLE(),
"storage 0x"+h.StringLE(),
"storage "+address.Uint160ToString(h),
"storage 1",
"storage 1 "+hex.EncodeToString(expected[0].Key),
"storage 1 --backwards",
)
e.checkStorage(t, expected...)
e.checkStorage(t, expected...)
e.checkStorage(t, expected...)
e.checkStorage(t, expected...)
e.checkStorage(t, storage.KeyValue{Key: nil, Value: []byte{2}}) // empty key because search prefix is trimmed
e.checkStorage(t, expected[1], expected[0])
}