core, vm: store VMState as byte instead of string

Part of #1183
This commit is contained in:
Anna Shaleva 2020-07-27 17:57:53 +03:00
parent 990db9f205
commit 70ef733ce7
12 changed files with 89 additions and 80 deletions

View file

@ -378,7 +378,7 @@ func (bc *Blockchain) notificationDispatcher() {
for ch := range executionFeed {
ch <- aer
}
if aer.VMState == "HALT" {
if aer.VMState == vm.HaltState {
for i := range aer.Events {
for ch := range notificationFeed {
ch <- &aer.Events[i]

View file

@ -13,6 +13,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
@ -316,7 +317,7 @@ func TestSubscriptions(t *testing.T) {
exec := <-executionCh
require.Equal(t, b.Hash(), exec.TxHash)
require.Equal(t, exec.VMState, "HALT")
require.Equal(t, exec.VMState, vm.HaltState)
// 3 burn events for every tx and 1 mint for primary node
require.True(t, len(notificationCh) >= 4)
@ -331,7 +332,7 @@ func TestSubscriptions(t *testing.T) {
require.Equal(t, txExpected, tx)
exec := <-executionCh
require.Equal(t, tx.Hash(), exec.TxHash)
if exec.VMState == "HALT" {
if exec.VMState == vm.HaltState {
notif := <-notificationCh
require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash)
}

View file

@ -10,6 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
@ -119,14 +120,14 @@ func TestNativeContract_Invoke(t *testing.T) {
res, err := chain.GetAppExecResult(tx.Hash())
require.NoError(t, err)
require.Equal(t, "HALT", res.VMState)
require.Equal(t, vm.HaltState, res.VMState)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, smartcontract.IntegerType, res.Stack[0].Type)
require.EqualValues(t, 42, res.Stack[0].Value)
res, err = chain.GetAppExecResult(tx2.Hash())
require.NoError(t, err)
require.Equal(t, "FAULT", res.VMState)
require.Equal(t, vm.FaultState, res.VMState)
require.NoError(t, chain.persist())
select {

View file

@ -12,6 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/stretchr/testify/require"
)
@ -253,7 +254,7 @@ func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interfac
}
func checkResult(t *testing.T, result *state.AppExecResult, expected smartcontract.Parameter) {
require.Equal(t, "HALT", result.VMState)
require.Equal(t, vm.HaltState, result.VMState)
require.Equal(t, 1, len(result.Stack))
require.Equal(t, expected.Type, result.Stack[0].Type)
require.EqualValues(t, expected.Value, result.Stack[0].Value)

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -23,7 +24,7 @@ type NotificationEvent struct {
type AppExecResult struct {
TxHash util.Uint256
Trigger trigger.Type
VMState string
VMState vm.State
GasConsumed int64
Stack []smartcontract.Parameter
Events []NotificationEvent
@ -56,7 +57,7 @@ func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(aer.TxHash[:])
w.WriteB(byte(aer.Trigger))
w.WriteString(aer.VMState)
w.WriteB(byte(aer.VMState))
w.WriteU64LE(uint64(aer.GasConsumed))
w.WriteArray(aer.Stack)
w.WriteArray(aer.Events)
@ -66,7 +67,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
r.ReadBytes(aer.TxHash[:])
aer.Trigger = trigger.Type(r.ReadB())
aer.VMState = r.ReadString()
aer.VMState = vm.State(r.ReadB())
aer.GasConsumed = int64(r.ReadU64LE())
r.ReadArray(&aer.Stack)
r.ReadArray(&aer.Events)

View file

@ -6,6 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -23,7 +24,7 @@ func TestEncodeDecodeAppExecResult(t *testing.T) {
appExecResult := &AppExecResult{
TxHash: random.Uint256(),
Trigger: 1,
VMState: "Hault",
VMState: vm.HaltState,
GasConsumed: 10,
Stack: []smartcontract.Parameter{},
Events: []NotificationEvent{},

View file

@ -49,7 +49,7 @@ func NewApplicationLog(appExecRes *state.AppExecResult) ApplicationLog {
return ApplicationLog{
TxHash: appExecRes.TxHash,
Trigger: appExecRes.Trigger.String(),
VMState: appExecRes.VMState,
VMState: appExecRes.VMState.String(),
GasConsumed: appExecRes.GasConsumed,
Stack: appExecRes.Stack,
Events: events,

View file

@ -880,7 +880,7 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *resu
vm.LoadScriptWithFlags(script, smartcontract.All)
_ = vm.Run()
result := &result.Invoke{
State: vm.State(),
State: vm.State().String(),
GasConsumed: vm.GasConsumed(),
Script: hex.EncodeToString(script),
Stack: vm.Estack().ToContractParameters(),

View file

@ -162,14 +162,14 @@ func testFile(t *testing.T, filename string) {
t.Run(ut.Tests[i].Name, func(t *testing.T) {
prog := []byte(test.Script)
vm := load(prog)
vm.state = breakState
vm.state = BreakState
vm.RegisterInteropGetter(getTestingInterop)
for i := range test.Steps {
execStep(t, vm, test.Steps[i])
result := test.Steps[i].Result
require.Equal(t, result.State, vm.state)
if result.State == faultState { // do not compare stacks on fault
if result.State == FaultState { // do not compare stacks on fault
continue
}

View file

@ -11,10 +11,14 @@ type State uint8
// Available States.
const (
noneState State = 0
haltState State = 1 << iota
faultState
breakState
// NoneState represents NONE VM state.
NoneState State = 0
// HaltState represents HALT VM state.
HaltState State = 1 << iota
// FaultState represents FAULT VM state.
FaultState
// BreakState represents BREAK VM state.
BreakState
)
// HasFlag checks for State flag presence.
@ -24,18 +28,18 @@ func (s State) HasFlag(f State) bool {
// String implements the stringer interface.
func (s State) String() string {
if s == noneState {
if s == NoneState {
return "NONE"
}
ss := make([]string, 0, 3)
if s.HasFlag(haltState) {
if s.HasFlag(HaltState) {
ss = append(ss, "HALT")
}
if s.HasFlag(faultState) {
if s.HasFlag(FaultState) {
ss = append(ss, "FAULT")
}
if s.HasFlag(breakState) {
if s.HasFlag(BreakState) {
ss = append(ss, "BREAK")
}
return strings.Join(ss, ", ")
@ -44,18 +48,18 @@ func (s State) String() string {
// StateFromString converts string into the VM State.
func StateFromString(s string) (st State, err error) {
if s = strings.TrimSpace(s); s == "NONE" {
return noneState, nil
return NoneState, nil
}
ss := strings.Split(s, ",")
for _, state := range ss {
switch state = strings.TrimSpace(state); state {
case "HALT":
st |= haltState
st |= HaltState
case "FAULT":
st |= faultState
st |= FaultState
case "BREAK":
st |= breakState
st |= BreakState
default:
return 0, errors.New("unknown state")
}

View file

@ -15,42 +15,42 @@ func TestStateFromString(t *testing.T) {
s, err = StateFromString("HALT")
assert.NoError(t, err)
assert.Equal(t, haltState, s)
assert.Equal(t, HaltState, s)
s, err = StateFromString("BREAK")
assert.NoError(t, err)
assert.Equal(t, breakState, s)
assert.Equal(t, BreakState, s)
s, err = StateFromString("FAULT")
assert.NoError(t, err)
assert.Equal(t, faultState, s)
assert.Equal(t, FaultState, s)
s, err = StateFromString("NONE")
assert.NoError(t, err)
assert.Equal(t, noneState, s)
assert.Equal(t, NoneState, s)
s, err = StateFromString("HALT, BREAK")
assert.NoError(t, err)
assert.Equal(t, haltState|breakState, s)
assert.Equal(t, HaltState|BreakState, s)
s, err = StateFromString("FAULT, BREAK")
assert.NoError(t, err)
assert.Equal(t, faultState|breakState, s)
assert.Equal(t, FaultState|BreakState, s)
_, err = StateFromString("HALT, KEK")
assert.Error(t, err)
}
func TestState_HasFlag(t *testing.T) {
assert.True(t, haltState.HasFlag(haltState))
assert.True(t, breakState.HasFlag(breakState))
assert.True(t, faultState.HasFlag(faultState))
assert.True(t, (haltState | breakState).HasFlag(haltState))
assert.True(t, (haltState | breakState).HasFlag(breakState))
assert.True(t, HaltState.HasFlag(HaltState))
assert.True(t, BreakState.HasFlag(BreakState))
assert.True(t, FaultState.HasFlag(FaultState))
assert.True(t, (HaltState | BreakState).HasFlag(HaltState))
assert.True(t, (HaltState | BreakState).HasFlag(BreakState))
assert.False(t, haltState.HasFlag(breakState))
assert.False(t, noneState.HasFlag(haltState))
assert.False(t, (faultState | breakState).HasFlag(haltState))
assert.False(t, HaltState.HasFlag(BreakState))
assert.False(t, NoneState.HasFlag(HaltState))
assert.False(t, (FaultState | BreakState).HasFlag(HaltState))
}
func TestState_MarshalJSON(t *testing.T) {
@ -59,11 +59,11 @@ func TestState_MarshalJSON(t *testing.T) {
err error
)
data, err = json.Marshal(haltState | breakState)
data, err = json.Marshal(HaltState | BreakState)
assert.NoError(t, err)
assert.Equal(t, data, []byte(`"HALT, BREAK"`))
data, err = json.Marshal(faultState)
data, err = json.Marshal(FaultState)
assert.NoError(t, err)
assert.Equal(t, data, []byte(`"FAULT"`))
}
@ -76,13 +76,13 @@ func TestState_UnmarshalJSON(t *testing.T) {
err = json.Unmarshal([]byte(`"HALT, BREAK"`), &s)
assert.NoError(t, err)
assert.Equal(t, haltState|breakState, s)
assert.Equal(t, HaltState|BreakState, s)
err = json.Unmarshal([]byte(`"FAULT, BREAK"`), &s)
assert.NoError(t, err)
assert.Equal(t, faultState|breakState, s)
assert.Equal(t, FaultState|BreakState, s)
err = json.Unmarshal([]byte(`"NONE"`), &s)
assert.NoError(t, err)
assert.Equal(t, noneState, s)
assert.Equal(t, NoneState, s)
}

View file

@ -93,7 +93,7 @@ func New() *VM {
func NewWithTrigger(t trigger.Type) *VM {
vm := &VM{
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
state: haltState,
state: HaltState,
istack: NewStack("invocation"),
refs: newRefCounter(),
keys: make(map[string]*keys.PublicKey),
@ -264,7 +264,7 @@ func (v *VM) Load(prog []byte) {
// Clear all stacks and state, it could be a reload.
v.istack.Clear()
v.estack.Clear()
v.state = noneState
v.state = NoneState
v.gasConsumed = 0
v.LoadScript(prog)
}
@ -328,9 +328,9 @@ func (v *VM) Stack(n string) string {
return string(b)
}
// State returns string representation of the state for the VM.
func (v *VM) State() string {
return v.state.String()
// State returns the state for the VM.
func (v *VM) State() State {
return v.state
}
// Ready returns true if the VM ready to execute the loaded program.
@ -342,37 +342,37 @@ func (v *VM) Ready() bool {
// Run starts the execution of the loaded program.
func (v *VM) Run() error {
if !v.Ready() {
v.state = faultState
v.state = FaultState
return errors.New("no program loaded")
}
if v.state.HasFlag(faultState) {
if v.state.HasFlag(FaultState) {
// VM already ran something and failed, in general its state is
// undefined in this case so we can't run anything.
return errors.New("VM has failed")
}
// haltState (the default) or breakState are safe to continue.
v.state = noneState
// HaltState (the default) or BreakState are safe to continue.
v.state = NoneState
for {
// check for breakpoint before executing the next instruction
ctx := v.Context()
if ctx != nil && ctx.atBreakPoint() {
v.state = breakState
v.state = BreakState
}
switch {
case v.state.HasFlag(faultState):
case v.state.HasFlag(FaultState):
// Should be caught and reported already by the v.Step(),
// but we're checking here anyway just in case.
return errors.New("VM has failed")
case v.state.HasFlag(haltState), v.state.HasFlag(breakState):
case v.state.HasFlag(HaltState), v.state.HasFlag(BreakState):
// Normal exit from this loop.
return nil
case v.state == noneState:
case v.state == NoneState:
if err := v.Step(); err != nil {
return err
}
default:
v.state = faultState
v.state = FaultState
return errors.New("unknown state")
}
}
@ -383,7 +383,7 @@ func (v *VM) Step() error {
ctx := v.Context()
op, param, err := ctx.Next()
if err != nil {
v.state = faultState
v.state = FaultState
return newError(ctx.ip, op, err)
}
return v.execute(ctx, op, param)
@ -395,7 +395,7 @@ func (v *VM) StepInto() error {
ctx := v.Context()
if ctx == nil {
v.state = haltState
v.state = HaltState
}
if v.HasStopped() {
@ -405,7 +405,7 @@ func (v *VM) StepInto() error {
if ctx != nil && ctx.prog != nil {
op, param, err := ctx.Next()
if err != nil {
v.state = faultState
v.state = FaultState
return newError(ctx.ip, op, err)
}
vErr := v.execute(ctx, op, param)
@ -416,7 +416,7 @@ func (v *VM) StepInto() error {
cctx := v.Context()
if cctx != nil && cctx.atBreakPoint() {
v.state = breakState
v.state = BreakState
}
return nil
}
@ -424,14 +424,14 @@ func (v *VM) StepInto() error {
// StepOut takes the debugger to the line where the current function was called.
func (v *VM) StepOut() error {
var err error
if v.state == breakState {
v.state = noneState
if v.state == BreakState {
v.state = NoneState
} else {
v.state = breakState
v.state = BreakState
}
expSize := v.istack.len
for v.state == noneState && v.istack.len >= expSize {
for v.state == NoneState && v.istack.len >= expSize {
err = v.StepInto()
}
return err
@ -445,22 +445,22 @@ func (v *VM) StepOver() error {
return err
}
if v.state == breakState {
v.state = noneState
if v.state == BreakState {
v.state = NoneState
} else {
v.state = breakState
v.state = BreakState
}
expSize := v.istack.len
for {
err = v.StepInto()
if !(v.state == noneState && v.istack.len > expSize) {
if !(v.state == NoneState && v.istack.len > expSize) {
break
}
}
if v.state == noneState {
v.state = breakState
if v.state == NoneState {
v.state = BreakState
}
return err
@ -469,22 +469,22 @@ func (v *VM) StepOver() error {
// HasFailed returns whether VM is in the failed state now. Usually used to
// check status after Run.
func (v *VM) HasFailed() bool {
return v.state.HasFlag(faultState)
return v.state.HasFlag(FaultState)
}
// HasStopped returns whether VM is in Halt or Failed state.
func (v *VM) HasStopped() bool {
return v.state.HasFlag(haltState) || v.state.HasFlag(faultState)
return v.state.HasFlag(HaltState) || v.state.HasFlag(FaultState)
}
// HasHalted returns whether VM is in Halt state.
func (v *VM) HasHalted() bool {
return v.state.HasFlag(haltState)
return v.state.HasFlag(HaltState)
}
// AtBreakpoint returns whether VM is at breakpoint.
func (v *VM) AtBreakpoint() bool {
return v.state.HasFlag(breakState)
return v.state.HasFlag(BreakState)
}
// GetInteropID converts instruction parameter to an interop ID.
@ -510,10 +510,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
// each panic at a central point, putting the VM in a fault state and setting error.
defer func() {
if errRecover := recover(); errRecover != nil {
v.state = faultState
v.state = FaultState
err = newError(ctx.ip, op, errRecover)
} else if v.refs.size > MaxStackSize {
v.state = faultState
v.state = FaultState
err = newError(ctx.ip, op, "stack is too big")
}
}()
@ -1277,7 +1277,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.unloadContext(oldCtx)
if v.istack.Len() == 0 {
v.state = haltState
v.state = HaltState
break
}