Merge pull request #2586 from nspcc-dev/move-res-rpc-code

Move response-related RPC code
This commit is contained in:
Roman Khimov 2022-07-11 18:23:01 +03:00 committed by GitHub
commit 953f291836
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 652 additions and 695 deletions

View file

@ -23,8 +23,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
@ -354,7 +354,7 @@ func TestContractDeployWithData(t *testing.T) {
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State, res.FaultException)
require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte{12}, res.Stack[0].Value())
@ -366,7 +366,7 @@ func TestContractDeployWithData(t *testing.T) {
res = new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State, res.FaultException)
require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value())
}
@ -672,7 +672,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State, res.FaultException)
require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte("on create|sub create"), res.Stack[0].Value())
@ -821,7 +821,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
e.Run(t, append(cmd, strconv.FormatInt(storage.FindKeysOnly, 10))...)
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State)
require.Equal(t, vmstate.Halt.String(), res.State)
require.Len(t, res.Stack, 1)
require.Equal(t, []stackitem.Item{
stackitem.Make("findkey1"),
@ -832,7 +832,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
e.Run(t, append(cmd, strconv.FormatInt(storage.FindDefault, 10))...)
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State)
require.Equal(t, vmstate.Halt.String(), res.State)
require.Len(t, res.Stack, 1)
arr, ok := res.Stack[0].Value().([]stackitem.Item)
@ -883,7 +883,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State)
require.Equal(t, vmstate.Halt.String(), res.State)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte("on update|sub update"), res.Stack[0].Value())
})

View file

@ -23,7 +23,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpc/server"
"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/vmstate"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
"go.uber.org/zap"
@ -293,7 +293,7 @@ func (e *executor) checkTxPersisted(t *testing.T, prefix ...string) (*transactio
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, vm.HaltState, aer[0].VMState)
require.Equal(t, vmstate.Halt, aer[0].VMState)
return tx, height
}

View file

@ -15,7 +15,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"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/vmstate"
"github.com/stretchr/testify/require"
)
@ -154,7 +154,7 @@ func TestSignMultisigTx(t *testing.T) {
e.checkTxTestInvokeOutput(t, 11)
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State, res.FaultException)
require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException)
})
e.In.WriteString("pass\r")

View file

@ -22,6 +22,7 @@ import (
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/urfave/cli"
)
@ -129,7 +130,7 @@ func DumpApplicationLog(
if len(res.Executions) != 1 {
_, _ = tw.Write([]byte("Success:\tunknown (no execution data)\n"))
} else {
_, _ = tw.Write([]byte(fmt.Sprintf("Success:\t%t\n", res.Executions[0].VMState == vm.HaltState)))
_, _ = tw.Write([]byte(fmt.Sprintf("Success:\t%t\n", res.Executions[0].VMState == vmstate.Halt)))
}
}
if verbose {
@ -146,7 +147,7 @@ func DumpApplicationLog(
v.PrintOps(tw)
if res != nil {
for _, e := range res.Executions {
if e.VMState != vm.HaltState {
if e.VMState != vmstate.Halt {
_, _ = tw.Write([]byte("Exception:\t" + e.FaultException + "\n"))
}
}

View file

@ -16,6 +16,7 @@ import (
"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/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -117,7 +118,7 @@ func (e *executor) compareQueryTxVerbose(t *testing.T, tx *transaction.Transacti
e.checkNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(int(height)).StringLE())
res, _ := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
e.checkNextLine(t, fmt.Sprintf(`Success:\s+%t`, res[0].Execution.VMState == vm.HaltState))
e.checkNextLine(t, fmt.Sprintf(`Success:\s+%t`, res[0].Execution.VMState == vmstate.Halt))
for _, s := range tx.Signers {
e.checkNextLine(t, fmt.Sprintf(`Signer:\s+%s\s*\(%s\)`, address.Uint160ToString(s.Account), s.Scopes.String()))
}
@ -132,7 +133,7 @@ func (e *executor) compareQueryTxVerbose(t *testing.T, tx *transaction.Transacti
}
e.checkScriptDump(t, n)
if res[0].Execution.VMState != vm.HaltState {
if res[0].Execution.VMState != vmstate.Halt {
e.checkNextLine(t, `Exception:\s+`+regexp.QuoteMeta(res[0].Execution.FaultException))
}
e.checkEOF(t)

View file

@ -7,14 +7,15 @@ import (
"path/filepath"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
)
type dump []blockDump
type blockDump struct {
Block uint32 `json:"block"`
Size int `json:"size"`
Storage []storage.Operation `json:"storage"`
Block uint32 `json:"block"`
Size int `json:"size"`
Storage []dboper.Operation `json:"storage"`
}
func newDump() *dump {

View file

@ -9,8 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/network/metrics"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/rpc"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
@ -317,7 +316,7 @@ func TestConfigureAddresses(t *testing.T) {
t.Run("custom Pprof address", func(t *testing.T) {
cfg := &config.ApplicationConfiguration{
Address: defaultAddress,
Pprof: metrics.Config{
Pprof: config.BasicService{
Address: customAddress,
},
}
@ -330,7 +329,7 @@ func TestConfigureAddresses(t *testing.T) {
t.Run("custom Prometheus address", func(t *testing.T) {
cfg := &config.ApplicationConfiguration{
Address: defaultAddress,
Prometheus: metrics.Config{
Prometheus: config.BasicService{
Address: customAddress,
},
}
@ -350,7 +349,7 @@ func TestInitBlockChain(t *testing.T) {
t.Run("empty logger", func(t *testing.T) {
_, err := initBlockChain(config.Config{
ApplicationConfiguration: config.ApplicationConfiguration{
DBConfiguration: storage.DBConfiguration{
DBConfiguration: dbconfig.DBConfiguration{
Type: "inmemory",
},
},

View file

@ -29,6 +29,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require"
)
@ -116,10 +117,10 @@ func TestLedgerTransactionWitnessCondition(t *testing.T) {
}
func TestLedgerVMStates(t *testing.T) {
require.EqualValues(t, ledger.NoneState, vm.NoneState)
require.EqualValues(t, ledger.HaltState, vm.HaltState)
require.EqualValues(t, ledger.FaultState, vm.FaultState)
require.EqualValues(t, ledger.BreakState, vm.BreakState)
require.EqualValues(t, ledger.NoneState, vmstate.None)
require.EqualValues(t, ledger.HaltState, vmstate.Halt)
require.EqualValues(t, ledger.FaultState, vmstate.Fault)
require.EqualValues(t, ledger.BreakState, vmstate.Break)
}
type nativeTestCase struct {

View file

@ -1,33 +1,32 @@
package config
import (
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/network/metrics"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/rpc"
)
// ApplicationConfiguration config specific to the node.
type ApplicationConfiguration struct {
Address string `yaml:"Address"`
AnnouncedNodePort uint16 `yaml:"AnnouncedPort"`
AttemptConnPeers int `yaml:"AttemptConnPeers"`
DBConfiguration storage.DBConfiguration `yaml:"DBConfiguration"`
DialTimeout int64 `yaml:"DialTimeout"`
LogPath string `yaml:"LogPath"`
MaxPeers int `yaml:"MaxPeers"`
MinPeers int `yaml:"MinPeers"`
NodePort uint16 `yaml:"NodePort"`
PingInterval int64 `yaml:"PingInterval"`
PingTimeout int64 `yaml:"PingTimeout"`
Pprof metrics.Config `yaml:"Pprof"`
Prometheus metrics.Config `yaml:"Prometheus"`
ProtoTickInterval int64 `yaml:"ProtoTickInterval"`
Relay bool `yaml:"Relay"`
RPC rpc.Config `yaml:"RPC"`
UnlockWallet Wallet `yaml:"UnlockWallet"`
Oracle OracleConfiguration `yaml:"Oracle"`
P2PNotary P2PNotary `yaml:"P2PNotary"`
StateRoot StateRoot `yaml:"StateRoot"`
Address string `yaml:"Address"`
AnnouncedNodePort uint16 `yaml:"AnnouncedPort"`
AttemptConnPeers int `yaml:"AttemptConnPeers"`
DBConfiguration dbconfig.DBConfiguration `yaml:"DBConfiguration"`
DialTimeout int64 `yaml:"DialTimeout"`
LogPath string `yaml:"LogPath"`
MaxPeers int `yaml:"MaxPeers"`
MinPeers int `yaml:"MinPeers"`
NodePort uint16 `yaml:"NodePort"`
PingInterval int64 `yaml:"PingInterval"`
PingTimeout int64 `yaml:"PingTimeout"`
Pprof BasicService `yaml:"Pprof"`
Prometheus BasicService `yaml:"Prometheus"`
ProtoTickInterval int64 `yaml:"ProtoTickInterval"`
Relay bool `yaml:"Relay"`
RPC rpc.Config `yaml:"RPC"`
UnlockWallet Wallet `yaml:"UnlockWallet"`
Oracle OracleConfiguration `yaml:"Oracle"`
P2PNotary P2PNotary `yaml:"P2PNotary"`
StateRoot StateRoot `yaml:"StateRoot"`
// ExtensiblePoolSize is the maximum amount of the extensible payloads from a single sender.
ExtensiblePoolSize int `yaml:"ExtensiblePoolSize"`
}

View file

@ -0,0 +1,8 @@
package config
// BasicService is used for simple services like Pprof or Prometheus monitoring.
type BasicService struct {
Enabled bool `yaml:"Enabled"`
Address string `yaml:"Address"`
Port string `yaml:"Port"`
}

View file

@ -0,0 +1,17 @@
/*
Package limits contains a number of system-wide hardcoded constants.
Many of the Neo protocol parameters can be adjusted by the configuration, but
some can not and this package contains hardcoded limits that are relevant for
many applications.
*/
package limits
const (
// MaxStorageKeyLen is the maximum length of a key for storage items.
// Contracts can't use keys longer than that in their requests to the DB.
MaxStorageKeyLen = 64
// MaxStorageValueLen is the maximum length of a value for storage items.
// It is set to be the maximum value for uint16, contracts can't put
// values longer than that into the DB.
MaxStorageValueLen = 65535
)

View file

@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
@ -136,7 +137,7 @@ func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBloc
func newLevelDBForTesting(t testing.TB) storage.Store {
dbPath := t.TempDir()
dbOptions := storage.LevelDBOptions{
dbOptions := dbconfig.LevelDBOptions{
DataDirectoryPath: dbPath,
}
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
@ -147,7 +148,7 @@ func newLevelDBForTesting(t testing.TB) storage.Store {
func newBoltStoreForTesting(t testing.TB) storage.Store {
d := t.TempDir()
dbPath := filepath.Join(d, "test_bolt_db")
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
boltDBStore, err := storage.NewBoltDBStore(dbconfig.BoltDBOptions{FilePath: dbPath})
require.NoError(t, err)
return boltDBStore
}

View file

@ -13,6 +13,7 @@ import (
"time"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer/services"
@ -40,6 +41,7 @@ import (
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"go.uber.org/zap"
)
@ -816,7 +818,7 @@ func (bc *Blockchain) notificationDispatcher() {
for ch := range executionFeed {
ch <- aer
}
if aer.VMState == vm.HaltState {
if aer.VMState == vmstate.Halt {
for i := range aer.Events {
for ch := range notificationFeed {
ch <- &subscriptions.NotificationEvent{
@ -1065,7 +1067,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
err = fmt.Errorf("failed to store exec result: %w", err)
break
}
if aer.Execution.VMState == vm.HaltState {
if aer.Execution.VMState == vmstate.Halt {
for j := range aer.Execution.Events {
bc.handleNotification(&aer.Execution.Events[j], kvcache, transCache, block, aer.Container)
}
@ -1327,7 +1329,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.S
var id []byte
if len(arr) == 4 {
id, err = arr[3].TryBytes()
if err != nil || len(id) > storage.MaxStorageKeyLen {
if err != nil || len(id) > limits.MaxStorageKeyLen {
return
}
}

View file

@ -28,6 +28,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -39,10 +40,10 @@ 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/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -52,7 +53,7 @@ func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, s
if dbPath == "" {
dbPath = t.TempDir()
}
dbOptions := storage.LevelDBOptions{
dbOptions := dbconfig.LevelDBOptions{
DataDirectoryPath: dbPath,
}
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
@ -826,7 +827,7 @@ func TestBlockchain_Subscriptions(t *testing.T) {
exec := <-executionCh
require.Equal(t, b.Hash(), exec.Container)
require.Equal(t, exec.VMState, vm.HaltState)
require.Equal(t, exec.VMState, vmstate.Halt)
// 3 burn events for every tx and 1 mint for primary node
require.True(t, len(notificationCh) >= 4)
@ -841,7 +842,7 @@ func TestBlockchain_Subscriptions(t *testing.T) {
require.Equal(t, txExpected, tx)
exec := <-executionCh
require.Equal(t, tx.Hash(), exec.Container)
if exec.VMState == vm.HaltState {
if exec.VMState == vmstate.Halt {
notif := <-notificationCh
require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash)
}
@ -855,7 +856,7 @@ func TestBlockchain_Subscriptions(t *testing.T) {
exec = <-executionCh
require.Equal(t, b.Hash(), exec.Container)
require.Equal(t, exec.VMState, vm.HaltState)
require.Equal(t, exec.VMState, vmstate.Halt)
bc.UnsubscribeFromBlocks(blockCh)
bc.UnsubscribeFromTransactions(txCh)

View file

@ -10,6 +10,7 @@ import (
"math/big"
"sync"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
@ -830,7 +831,7 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32,
func (dao *Simple) getKeyBuf(len int) []byte {
if dao.private {
if dao.keyBuf == nil {
dao.keyBuf = make([]byte, 0, 1+4+storage.MaxStorageKeyLen) // Prefix, uint32, key.
dao.keyBuf = make([]byte, 0, 1+4+limits.MaxStorageKeyLen) // Prefix, uint32, key.
}
return dao.keyBuf[:len] // Should have enough capacity.
}

View file

@ -14,7 +14,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"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"
)
@ -168,7 +167,7 @@ func CallFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract
return fmt.Errorf("%w: %v", ErrNativeCall, err)
}
}
if ic.VM.State() == vm.FaultState {
if ic.VM.HasFailed() {
return ErrNativeCall
}
return nil

View file

@ -4,8 +4,8 @@ import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -81,10 +81,10 @@ func getContextInternal(ic *interop.Context, isReadOnly bool) error {
}
func putWithContext(ic *interop.Context, stc *Context, key []byte, value []byte) error {
if len(key) > storage.MaxStorageKeyLen {
if len(key) > limits.MaxStorageKeyLen {
return errors.New("key is too big")
}
if len(value) > storage.MaxStorageValueLen {
if len(value) > limits.MaxStorageValueLen {
return errors.New("value is too big")
}
if stc.ReadOnly {

View file

@ -5,6 +5,7 @@ import (
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
@ -12,7 +13,6 @@ import (
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
@ -61,7 +61,7 @@ func TestPut(t *testing.T) {
})
t.Run("check limits", func(t *testing.T) {
initVM(t, make([]byte, storage.MaxStorageKeyLen), make([]byte, storage.MaxStorageValueLen), -1)
initVM(t, make([]byte, limits.MaxStorageKeyLen), make([]byte, limits.MaxStorageValueLen), -1)
require.NoError(t, istorage.Put(ic))
})
@ -72,11 +72,11 @@ func TestPut(t *testing.T) {
require.Error(t, istorage.Put(ic))
})
t.Run("big key", func(t *testing.T) {
initVM(t, make([]byte, storage.MaxStorageKeyLen+1), []byte{1}, -1)
initVM(t, make([]byte, limits.MaxStorageKeyLen+1), []byte{1}, -1)
require.Error(t, istorage.Put(ic))
})
t.Run("big value", func(t *testing.T) {
initVM(t, []byte{1}, make([]byte, storage.MaxStorageValueLen+1), -1)
initVM(t, []byte{1}, make([]byte, limits.MaxStorageValueLen+1), -1)
require.Error(t, istorage.Put(ic))
})
})

View file

@ -6,14 +6,14 @@ import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const (
// maxPathLength is the max length of the extension node key.
maxPathLength = (storage.MaxStorageKeyLen + 4) * 2
maxPathLength = (limits.MaxStorageKeyLen + 4) * 2
// MaxKeyLength is the max length of the key to put in the trie
// before transforming to nibbles.

View file

@ -5,13 +5,13 @@ import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// MaxValueLength is the max length of a leaf node value.
const MaxValueLength = 3 + storage.MaxStorageValueLen + 1
const MaxValueLength = 3 + limits.MaxStorageValueLen + 1
// LeafNode represents an MPT's leaf node.
type LeafNode struct {

View file

@ -13,8 +13,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
// Ledger provides an interface to blocks/transactions storage for smart
@ -169,7 +169,7 @@ func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.I
}
h, _, aer, err := ic.DAO.GetTxExecResult(hash)
if err != nil || !isTraceableBlock(ic, h) {
return stackitem.Make(vm.NoneState)
return stackitem.Make(vmstate.None)
}
return stackitem.Make(aer.VMState)
}

View file

@ -13,9 +13,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require"
)
@ -56,22 +56,22 @@ func TestLedger_GetTransactionState(t *testing.T) {
hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
t.Run("unknown transaction", func(t *testing.T) {
ledgerInvoker.Invoke(t, vm.NoneState, "getTransactionVMState", util.Uint256{1, 2, 3})
ledgerInvoker.Invoke(t, vmstate.None, "getTransactionVMState", util.Uint256{1, 2, 3})
})
t.Run("not a hash", func(t *testing.T) {
ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionVMState", []byte{1, 2, 3})
})
t.Run("good: HALT", func(t *testing.T) {
ledgerInvoker.Invoke(t, vm.HaltState, "getTransactionVMState", hash)
ledgerInvoker.Invoke(t, vmstate.Halt, "getTransactionVMState", hash)
})
t.Run("isn't traceable", func(t *testing.T) {
// Add more blocks so that tx becomes untraceable.
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
ledgerInvoker.Invoke(t, vm.NoneState, "getTransactionVMState", hash)
ledgerInvoker.Invoke(t, vmstate.None, "getTransactionVMState", hash)
})
t.Run("good: FAULT", func(t *testing.T) {
faultedH := e.InvokeScript(t, []byte{byte(opcode.ABORT)}, []neotest.Signer{c.Committee})
ledgerInvoker.Invoke(t, vm.FaultState, "getTransactionVMState", faultedH)
ledgerInvoker.Invoke(t, vmstate.Fault, "getTransactionVMState", faultedH)
})
}

View file

@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
@ -21,10 +22,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"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/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require"
)
@ -69,7 +70,7 @@ func TestManagement_ContractCache(t *testing.T) {
managementInvoker.CheckHalt(t, tx1.Hash())
aer, err := managementInvoker.Chain.GetAppExecResults(tx2.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
require.Equal(t, vmstate.Halt, aer[0].VMState, aer[0].FaultException)
require.NotEqual(t, stackitem.Null{}, aer[0].Stack)
}
@ -281,9 +282,9 @@ func TestManagement_ContractDeploy(t *testing.T) {
func TestManagement_StartFromHeight(t *testing.T) {
// Create database to be able to start another chain from the same height later.
ldbDir := t.TempDir()
dbConfig := storage.DBConfiguration{
dbConfig := dbconfig.DBConfiguration{
Type: "leveldb",
LevelDBOptions: storage.LevelDBOptions{
LevelDBOptions: dbconfig.LevelDBOptions{
DataDirectoryPath: ldbDir,
},
}

View file

@ -8,8 +8,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io"
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
// NotificationEvent is a tuple of the scripthash that has emitted the Item as a
@ -94,7 +94,7 @@ func (aer *AppExecResult) EncodeBinaryWithContext(w *io.BinWriter, sc *stackitem
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
r.ReadBytes(aer.Container[:])
aer.Trigger = trigger.Type(r.ReadB())
aer.VMState = vm.State(r.ReadB())
aer.VMState = vmstate.State(r.ReadB())
aer.GasConsumed = int64(r.ReadU64LE())
sz := r.ReadVarUint()
if stackitem.MaxDeserialized < sz && r.Err == nil {
@ -197,7 +197,7 @@ func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
// all resulting notifications, state, stack and other metadata.
type Execution struct {
Trigger trigger.Type
VMState vm.State
VMState vmstate.State
GasConsumed int64
Stack []stackitem.Item
Events []NotificationEvent
@ -266,7 +266,7 @@ func (e *Execution) UnmarshalJSON(data []byte) error {
return err
}
e.Trigger = trigger
state, err := vm.StateFromString(aux.VMState)
state, err := vmstate.FromString(aux.VMState)
if err != nil {
return err
}

View file

@ -8,8 +8,8 @@ import (
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/io"
"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/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require"
)
@ -18,7 +18,7 @@ func BenchmarkAppExecResult_EncodeBinary(b *testing.B) {
Container: random.Uint256(),
Execution: Execution{
Trigger: trigger.Application,
VMState: vm.HaltState,
VMState: vmstate.Halt,
GasConsumed: 12345,
Stack: []stackitem.Item{},
Events: []NotificationEvent{{
@ -54,7 +54,7 @@ func TestEncodeDecodeAppExecResult(t *testing.T) {
Container: random.Uint256(),
Execution: Execution{
Trigger: 1,
VMState: vm.HaltState,
VMState: vmstate.Halt,
GasConsumed: 10,
Stack: []stackitem.Item{stackitem.NewBool(true)},
Events: []NotificationEvent{},
@ -63,12 +63,12 @@ func TestEncodeDecodeAppExecResult(t *testing.T) {
}
t.Run("halt", func(t *testing.T) {
appExecResult := newAer()
appExecResult.VMState = vm.HaltState
appExecResult.VMState = vmstate.Halt
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
})
t.Run("fault", func(t *testing.T) {
appExecResult := newAer()
appExecResult.VMState = vm.FaultState
appExecResult.VMState = vmstate.Fault
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
})
t.Run("with interop", func(t *testing.T) {
@ -150,7 +150,7 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
Container: random.Uint256(),
Execution: Execution{
Trigger: trigger.Application,
VMState: vm.HaltState,
VMState: vmstate.Halt,
GasConsumed: 10,
Stack: []stackitem.Item{},
Events: []NotificationEvent{},
@ -164,7 +164,7 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
Container: random.Uint256(),
Execution: Execution{
Trigger: trigger.Application,
VMState: vm.FaultState,
VMState: vmstate.Fault,
GasConsumed: 10,
Stack: []stackitem.Item{stackitem.NewBool(true)},
Events: []NotificationEvent{},
@ -178,7 +178,7 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
Container: random.Uint256(),
Execution: Execution{
Trigger: trigger.OnPersist,
VMState: vm.HaltState,
VMState: vmstate.Halt,
GasConsumed: 10,
Stack: []stackitem.Item{},
Events: []NotificationEvent{},

View file

@ -4,7 +4,7 @@ import (
"bytes"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"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"
@ -224,5 +224,5 @@ func (t *NEP11Transfer) EncodeBinary(w *io.BinWriter) {
// DecodeBinary implements the io.Serializable interface.
func (t *NEP11Transfer) DecodeBinary(r *io.BinReader) {
t.NEP17Transfer.DecodeBinary(r)
t.ID = r.ReadVarBytes(storage.MaxStorageKeyLen)
t.ID = r.ReadVarBytes(limits.MaxStorageKeyLen)
}

View file

@ -5,16 +5,12 @@ import (
"fmt"
"os"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util/slice"
"go.etcd.io/bbolt"
)
// BoltDBOptions configuration for boltdb.
type BoltDBOptions struct {
FilePath string `yaml:"FilePath"`
}
// Bucket represents bucket used in boltdb to store all the data.
var Bucket = []byte("DB")
@ -25,7 +21,7 @@ type BoltDBStore struct {
}
// NewBoltDBStore returns a new ready to use BoltDB storage with created bucket.
func NewBoltDBStore(cfg BoltDBOptions) (*BoltDBStore, error) {
func NewBoltDBStore(cfg dbconfig.BoltDBOptions) (*BoltDBStore, error) {
var opts *bbolt.Options // should be exposed via BoltDBOptions if anything needed
fileMode := os.FileMode(0600) // should be exposed via BoltDBOptions if anything needed
fileName := cfg.FilePath

View file

@ -4,13 +4,14 @@ import (
"path/filepath"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/stretchr/testify/require"
)
func newBoltStoreForTesting(t testing.TB) Store {
d := t.TempDir()
testFileName := filepath.Join(d, "test_bolt_db")
boltDBStore, err := NewBoltDBStore(BoltDBOptions{FilePath: testFileName})
boltDBStore, err := NewBoltDBStore(dbconfig.BoltDBOptions{FilePath: testFileName})
require.NoError(t, err)
return boltDBStore
}

View file

@ -0,0 +1,21 @@
/*
Package dbconfig is a micropackage that contains storage DB configuration options.
*/
package dbconfig
type (
// DBConfiguration describes configuration for DB. Supported: 'levelDB', 'boltDB'.
DBConfiguration struct {
Type string `yaml:"Type"`
LevelDBOptions LevelDBOptions `yaml:"LevelDBOptions"`
BoltDBOptions BoltDBOptions `yaml:"BoltDBOptions"`
}
// LevelDBOptions configuration for LevelDB.
LevelDBOptions struct {
DataDirectoryPath string `yaml:"DataDirectoryPath"`
}
// BoltDBOptions configuration for BoltDB.
BoltDBOptions struct {
FilePath string `yaml:"FilePath"`
}
)

View file

@ -0,0 +1,13 @@
/*
Package dboper contains a type used to represent single DB operation.
*/
package dboper
// Operation represents a single KV operation (add/del/change) performed
// in the DB.
type Operation struct {
// State can be Added, Changed or Deleted.
State string `json:"state"`
Key []byte `json:"key"`
Value []byte `json:"value,omitempty"`
}

View file

@ -1,17 +1,13 @@
package storage
import (
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
)
// LevelDBOptions configuration for LevelDB.
type LevelDBOptions struct {
DataDirectoryPath string `yaml:"DataDirectoryPath"`
}
// LevelDBStore is the official storage implementation for storing and retrieving
// blockchain data.
type LevelDBStore struct {
@ -21,7 +17,7 @@ type LevelDBStore struct {
// NewLevelDBStore returns a new LevelDBStore object that will
// initialize the database found at the given path.
func NewLevelDBStore(cfg LevelDBOptions) (*LevelDBStore, error) {
func NewLevelDBStore(cfg dbconfig.LevelDBOptions) (*LevelDBStore, error) {
var opts = new(opt.Options) // should be exposed via LevelDBOptions if anything needed
opts.Filter = filter.NewBloomFilter(10)

View file

@ -3,14 +3,15 @@ package storage
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/stretchr/testify/require"
)
func newLevelDBForTesting(t testing.TB) Store {
ldbDir := t.TempDir()
dbConfig := DBConfiguration{
dbConfig := dbconfig.DBConfiguration{
Type: "leveldb",
LevelDBOptions: LevelDBOptions{
LevelDBOptions: dbconfig.LevelDBOptions{
DataDirectoryPath: ldbDir,
},
}

View file

@ -68,13 +68,14 @@ func (s *MemoryStore) putChangeSet(puts map[string][]byte, stores map[string][]b
// Seek implements the Store interface.
func (s *MemoryStore) Seek(rng SeekRange, f func(k, v []byte) bool) {
s.mut.RLock()
s.seek(rng, f)
s.mut.RUnlock()
s.seek(rng, f, s.mut.RLock, s.mut.RUnlock)
}
// SeekGC implements the Store interface.
func (s *MemoryStore) SeekGC(rng SeekRange, keep func(k, v []byte) bool) error {
noop := func() {}
// Keep RW lock for the whole Seek time, state must be consistent across whole
// operation and we call delete in the handler.
s.mut.Lock()
// We still need to perform normal seek, some GC operations can be
// sensitive to the order of KV pairs.
@ -83,7 +84,7 @@ func (s *MemoryStore) SeekGC(rng SeekRange, keep func(k, v []byte) bool) error {
delete(s.chooseMap(k), string(k))
}
return true
})
}, noop, noop)
s.mut.Unlock()
return nil
}
@ -91,7 +92,7 @@ func (s *MemoryStore) SeekGC(rng SeekRange, keep func(k, v []byte) bool) error {
// seek is an internal unlocked implementation of Seek. `start` denotes whether
// seeking starting from the provided prefix should be performed. Backwards
// seeking from some point is supported with corresponding SeekRange field set.
func (s *MemoryStore) seek(rng SeekRange, f func(k, v []byte) bool) {
func (s *MemoryStore) seek(rng SeekRange, f func(k, v []byte) bool, lock func(), unlock func()) {
sPrefix := string(rng.Prefix)
lPrefix := len(sPrefix)
sStart := string(rng.Start)
@ -111,6 +112,7 @@ func (s *MemoryStore) seek(rng SeekRange, f func(k, v []byte) bool) {
return res != 0 && rng.Backwards == (res > 0)
}
lock()
m := s.chooseMap(rng.Prefix)
for k, v := range m {
if v != nil && isKeyOK(k) {
@ -120,6 +122,7 @@ func (s *MemoryStore) seek(rng SeekRange, f func(k, v []byte) bool) {
})
}
}
unlock()
sort.Slice(memList, func(i, j int) bool {
return less(memList[i].Key, memList[j].Key)
})

View file

@ -4,6 +4,8 @@ import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
"github.com/syndtr/goleveldb/leveldb/util"
)
@ -40,23 +42,6 @@ const (
ExecTransaction byte = 2
)
const (
// MaxStorageKeyLen is the maximum length of a key for storage items.
MaxStorageKeyLen = 64
// MaxStorageValueLen is the maximum length of a value for storage items.
// It is set to be the maximum value for uint16.
MaxStorageValueLen = 65535
)
// Operation represents a single KV operation (add/del/change) performed
// in the DB.
type Operation struct {
// State can be Added, Changed or Deleted.
State string `json:"state"`
Key []byte `json:"key"`
Value []byte `json:"value,omitempty"`
}
// SeekRange represents options for Store.Seek operation.
type SeekRange struct {
// Prefix denotes the Seek's lookup key.
@ -125,7 +110,7 @@ func seekRangeToPrefixes(sr SeekRange) *util.Range {
}
// NewStore creates storage with preselected in configuration database type.
func NewStore(cfg DBConfiguration) (Store, error) {
func NewStore(cfg dbconfig.DBConfiguration) (Store, error) {
var store Store
var err error
switch cfg.Type {
@ -141,10 +126,10 @@ func NewStore(cfg DBConfiguration) (Store, error) {
return store, err
}
// BatchToOperations converts a batch of changes into array of Operations.
func BatchToOperations(batch *MemBatch) []Operation {
// BatchToOperations converts a batch of changes into array of dboper.Operation.
func BatchToOperations(batch *MemBatch) []dboper.Operation {
size := len(batch.Put) + len(batch.Deleted)
ops := make([]Operation, 0, size)
ops := make([]dboper.Operation, 0, size)
for i := range batch.Put {
key := batch.Put[i].Key
if len(key) == 0 || key[0] != byte(STStorage) && key[0] != byte(STTempStorage) {
@ -156,7 +141,7 @@ func BatchToOperations(batch *MemBatch) []Operation {
op = "Changed"
}
ops = append(ops, Operation{
ops = append(ops, dboper.Operation{
State: op,
Key: key[1:],
Value: batch.Put[i].Value,
@ -170,7 +155,7 @@ func BatchToOperations(batch *MemBatch) []Operation {
continue
}
ops = append(ops, Operation{
ops = append(ops, dboper.Operation{
State: "Deleted",
Key: key[1:],
})

View file

@ -1,10 +0,0 @@
package storage
type (
// DBConfiguration describes configuration for DB. Supported: 'levelDB', 'boltDB'.
DBConfiguration struct {
Type string `yaml:"Type"`
LevelDBOptions LevelDBOptions `yaml:"LevelDBOptions"`
BoltDBOptions BoltDBOptions `yaml:"BoltDBOptions"`
}
)

View file

@ -3,6 +3,7 @@ package storage
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
"github.com/stretchr/testify/require"
)
@ -19,7 +20,7 @@ func TestBatchToOperations(t *testing.T) {
{KeyValue: KeyValue{Key: []byte{byte(STStorage), 0x06}, Value: []byte{0x06}}, Exists: true},
},
}
o := []Operation{
o := []dboper.Operation{
{State: "Added", Key: []byte{0x01}, Value: []byte{0x01}},
{State: "Changed", Key: []byte{0x03}, Value: []byte{0x03}},
{State: "Deleted", Key: []byte{0x06}},

View file

@ -22,6 +22,7 @@ import (
"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/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -210,7 +211,7 @@ func (e *Executor) InvokeScriptCheckFAULT(t testing.TB, script []byte, signers [
func (e *Executor) CheckHalt(t testing.TB, h util.Uint256, stack ...stackitem.Item) *state.AppExecResult {
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
require.Equal(t, vmstate.Halt, aer[0].VMState, aer[0].FaultException)
if len(stack) != 0 {
require.Equal(t, stack, aer[0].Stack)
}
@ -222,7 +223,7 @@ func (e *Executor) CheckHalt(t testing.TB, h util.Uint256, stack ...stackitem.It
func (e *Executor) CheckFault(t testing.TB, h util.Uint256, s string) {
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.FaultState, aer[0].VMState)
require.Equal(t, vmstate.Fault, aer[0].VMState)
require.True(t, strings.Contains(aer[0].FaultException, s),
"expected: %s, got: %s", s, aer[0].FaultException)
}

View file

@ -9,6 +9,7 @@ import (
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require"
)
@ -91,7 +92,7 @@ func (c *ContractInvoker) InvokeAndCheck(t testing.TB, checkResult func(t testin
c.AddNewBlock(t, tx)
aer, err := c.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
require.Equal(t, vmstate.Halt, aer[0].VMState, aer[0].FaultException)
if checkResult != nil {
checkResult(t, aer[0].Stack)
}

View file

@ -4,24 +4,18 @@ import (
"context"
"net/http"
"github.com/nspcc-dev/neo-go/pkg/config"
"go.uber.org/zap"
)
// Service serves metrics.
type Service struct {
*http.Server
config Config
config config.BasicService
log *zap.Logger
serviceType string
}
// Config config used for monitoring.
type Config struct {
Enabled bool `yaml:"Enabled"`
Address string `yaml:"Address"`
Port string `yaml:"Port"`
}
// Start runs http service with the exposed endpoint on the configured port.
func (ms *Service) Start() {
if ms.config.Enabled {

View file

@ -4,6 +4,7 @@ import (
"net/http"
"net/http/pprof"
"github.com/nspcc-dev/neo-go/pkg/config"
"go.uber.org/zap"
)
@ -11,7 +12,7 @@ import (
type PprofService Service
// NewPprofService creates a new service for gathering pprof metrics.
func NewPprofService(cfg Config, log *zap.Logger) *Service {
func NewPprofService(cfg config.BasicService, log *zap.Logger) *Service {
if log == nil {
return nil
}

View file

@ -3,6 +3,7 @@ package metrics
import (
"net/http"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
)
@ -11,7 +12,7 @@ import (
type PrometheusService Service
// NewPrometheusService creates a new service for gathering prometheus metrics.
func NewPrometheusService(cfg Config, log *zap.Logger) *Service {
func NewPrometheusService(cfg config.BasicService, log *zap.Logger) *Service {
if log == nil {
return nil
}

View file

@ -34,9 +34,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"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/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -129,7 +129,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
Executions: []state.Execution{
{
Trigger: trigger.Application,
VMState: vm.HaltState,
VMState: vmstate.Halt,
GasConsumed: 1,
Stack: []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1))},
Events: []state.NotificationEvent{},

View file

@ -5,16 +5,10 @@ import (
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type (
// LedgerAux is a set of methods needed to construct some outputs.
LedgerAux interface {
BlockHeight() uint32
GetHeaderHash(int) util.Uint256
}
// Block wrapper used for the representation of
// block.Block / block.Base on the RPC Server.
Block struct {
@ -31,24 +25,6 @@ type (
}
)
// NewBlock creates a new Block wrapper.
func NewBlock(b *block.Block, chain LedgerAux) Block {
res := Block{
Block: *b,
BlockMetadata: BlockMetadata{
Size: io.GetVarSize(b),
Confirmations: chain.BlockHeight() - b.Index + 1,
},
}
hash := chain.GetHeaderHash(int(b.Index) + 1)
if !hash.Equals(util.Uint256{}) {
res.NextBlockHash = &hash
}
return res
}
// MarshalJSON implements the json.Marshaler interface.
func (b Block) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(b.BlockMetadata)

View file

@ -5,8 +5,6 @@ import (
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type (
@ -18,23 +16,6 @@ type (
}
)
// NewHeader creates a new Header wrapper.
func NewHeader(h *block.Header, chain LedgerAux) Header {
res := Header{
Header: *h,
BlockMetadata: BlockMetadata{
Size: io.GetVarSize(h),
Confirmations: chain.BlockHeight() - h.Index + 1,
},
}
hash := chain.GetHeaderHash(int(h.Index) + 1)
if !hash.Equals(util.Uint256{}) {
res.NextBlockHash = &hash
}
return res
}
// MarshalJSON implements the json.Marshaler interface.
func (h Header) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(h.BlockMetadata)

View file

@ -5,67 +5,31 @@ import (
"fmt"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Invoke represents a code invocation result and is used by several RPC calls
// that invoke functions, scripts and generic bytecode.
type Invoke struct {
State string
GasConsumed int64
Script []byte
Stack []stackitem.Item
FaultException string
Notifications []state.NotificationEvent
Transaction *transaction.Transaction
Diagnostics *InvokeDiag
maxIteratorResultItems int
Session uuid.UUID
finalize func()
registerIterator RegisterIterator
State string
GasConsumed int64
Script []byte
Stack []stackitem.Item
FaultException string
Notifications []state.NotificationEvent
Transaction *transaction.Transaction
Diagnostics *InvokeDiag
Session uuid.UUID
}
// RegisterIterator is a callback used to register new iterator on the server side.
type RegisterIterator func(sessionID string, item stackitem.Item, id int, finalize func()) (uuid.UUID, error)
// InvokeDiag is an additional diagnostic data for invocation.
type InvokeDiag struct {
Changes []storage.Operation `json:"storagechanges"`
Invocations []*vm.InvocationTree `json:"invokedcontracts"`
}
// NewInvoke returns a new Invoke structure with the given fields set.
func NewInvoke(ic *interop.Context, script []byte, faultException string, registerIterator RegisterIterator, maxIteratorResultItems int) *Invoke {
var diag *InvokeDiag
tree := ic.VM.GetInvocationTree()
if tree != nil {
diag = &InvokeDiag{
Invocations: tree.Calls,
Changes: storage.BatchToOperations(ic.DAO.GetBatch()),
}
}
notifications := ic.Notifications
if notifications == nil {
notifications = make([]state.NotificationEvent, 0)
}
return &Invoke{
State: ic.VM.State().String(),
GasConsumed: ic.VM.GasConsumed(),
Script: script,
Stack: ic.VM.Estack().ToArray(),
FaultException: faultException,
Notifications: notifications,
Diagnostics: diag,
finalize: ic.Finalize,
maxIteratorResultItems: maxIteratorResultItems,
registerIterator: registerIterator,
}
Changes []dboper.Operation `json:"storagechanges"`
Invocations []*invocations.Tree `json:"invokedcontracts"`
}
type invokeAux struct {
@ -106,85 +70,85 @@ type Iterator struct {
Truncated bool
}
// Finalize releases resources occupied by Iterators created at the script invocation.
// This method will be called automatically on Invoke marshalling or by the Server's
// sessions handler.
func (r *Invoke) Finalize() {
if r.finalize != nil {
r.finalize()
// MarshalJSON implements the json.Marshaler.
func (r Iterator) MarshalJSON() ([]byte, error) {
var iaux iteratorAux
iaux.Type = stackitem.InteropT.String()
if r.ID != nil {
iaux.Interface = iteratorInterfaceName
iaux.ID = r.ID.String()
} else {
value := make([]json.RawMessage, len(r.Values))
for i := range r.Values {
var err error
value[i], err = stackitem.ToJSONWithTypes(r.Values[i])
if err != nil {
return nil, err
}
}
iaux.Value = value
iaux.Truncated = r.Truncated
}
return json.Marshal(iaux)
}
// UnmarshalJSON implements the json.Unmarshaler.
func (r *Iterator) UnmarshalJSON(data []byte) error {
iteratorAux := new(iteratorAux)
err := json.Unmarshal(data, iteratorAux)
if err != nil {
return err
}
if len(iteratorAux.Interface) != 0 {
if iteratorAux.Interface != iteratorInterfaceName {
return fmt.Errorf("unknown InteropInterface: %s", iteratorAux.Interface)
}
var iID uuid.UUID
iID, err = uuid.Parse(iteratorAux.ID)
if err != nil {
return fmt.Errorf("failed to unmarshal iterator ID: %w", err)
}
r.ID = &iID
} else {
r.Values = make([]stackitem.Item, len(iteratorAux.Value))
for j := range r.Values {
r.Values[j], err = stackitem.FromJSONWithTypes(iteratorAux.Value[j])
if err != nil {
return fmt.Errorf("failed to unmarshal iterator values: %w", err)
}
}
r.Truncated = iteratorAux.Truncated
}
return nil
}
// MarshalJSON implements the json.Marshaler.
func (r Invoke) MarshalJSON() ([]byte, error) {
var (
st json.RawMessage
err error
faultSep string
arr = make([]json.RawMessage, len(r.Stack))
sessionsEnabled = r.registerIterator != nil
sessionID string
st json.RawMessage
err error
faultSep string
arr = make([]json.RawMessage, len(r.Stack))
)
if len(r.FaultException) != 0 {
faultSep = " / "
}
arrloop:
for i := range arr {
var data []byte
if (r.Stack[i].Type() == stackitem.InteropT) && iterator.IsIterator(r.Stack[i]) {
if sessionsEnabled {
if sessionID == "" {
sessionID = uuid.NewString()
}
iteratorID, err := r.registerIterator(sessionID, r.Stack[i], i, r.finalize)
if err != nil {
// Call finalizer immediately, there can't be race between server and marshaller because session wasn't added to server's session pool.
r.Finalize()
return nil, fmt.Errorf("failed to register iterator session: %w", err)
}
data, err = json.Marshal(iteratorAux{
Type: stackitem.InteropT.String(),
Interface: iteratorInterfaceName,
ID: iteratorID.String(),
})
if err != nil {
r.FaultException += fmt.Sprintf("%sjson error: failed to marshal iterator: %v", faultSep, err)
break
}
} else {
iteratorValues, truncated := iterator.ValuesTruncated(r.Stack[i], r.maxIteratorResultItems)
value := make([]json.RawMessage, len(iteratorValues))
for j := range iteratorValues {
value[j], err = stackitem.ToJSONWithTypes(iteratorValues[j])
if err != nil {
r.FaultException += fmt.Sprintf("%sjson error: %v", faultSep, err)
break arrloop
}
}
data, err = json.Marshal(iteratorAux{
Type: stackitem.InteropT.String(),
Value: value,
Truncated: truncated,
})
if err != nil {
r.FaultException += fmt.Sprintf("%sjson error: %v", faultSep, err)
break
}
}
iter, ok := r.Stack[i].Value().(Iterator)
if (r.Stack[i].Type() == stackitem.InteropT) && ok {
data, err = json.Marshal(iter)
} else {
data, err = stackitem.ToJSONWithTypes(r.Stack[i])
if err != nil {
r.FaultException += fmt.Sprintf("%sjson error: %v", faultSep, err)
break
}
}
if err != nil {
r.FaultException += fmt.Sprintf("%sjson error: %v", faultSep, err)
break
}
arr[i] = data
}
if !sessionsEnabled || sessionID == "" {
// Call finalizer manually if iterators are disabled or there's no unnested iterators on estack.
defer r.Finalize()
}
if err == nil {
st, err = json.Marshal(arr)
if err != nil {
@ -195,6 +159,10 @@ arrloop:
if r.Transaction != nil {
txbytes = r.Transaction.Bytes()
}
var sessionID string
if r.Session != (uuid.UUID{}) {
sessionID = r.Session.String()
}
aux := &invokeAux{
GasConsumed: r.GasConsumed,
Script: r.Script,
@ -233,41 +201,12 @@ func (r *Invoke) UnmarshalJSON(data []byte) error {
break
}
if st[i].Type() == stackitem.InteropT {
iteratorAux := new(iteratorAux)
if json.Unmarshal(arr[i], iteratorAux) == nil {
if len(iteratorAux.Interface) != 0 {
if iteratorAux.Interface != iteratorInterfaceName {
err = fmt.Errorf("unknown InteropInterface: %s", iteratorAux.Interface)
break
}
var iID uuid.UUID
iID, err = uuid.Parse(iteratorAux.ID) // iteratorAux.ID is always non-empty, see https://github.com/neo-project/neo-modules/pull/715#discussion_r897635424.
if err != nil {
err = fmt.Errorf("failed to unmarshal iterator ID: %w", err)
break
}
// It's impossible to restore initial iterator type; also iterator is almost
// useless outside the VM, thus let's replace it with a special structure.
st[i] = stackitem.NewInterop(Iterator{
ID: &iID,
})
} else {
iteratorValues := make([]stackitem.Item, len(iteratorAux.Value))
for j := range iteratorValues {
iteratorValues[j], err = stackitem.FromJSONWithTypes(iteratorAux.Value[j])
if err != nil {
err = fmt.Errorf("failed to unmarshal iterator values: %w", err)
break
}
}
// It's impossible to restore initial iterator type; also iterator is almost
// useless outside the VM, thus let's replace it with a special structure.
st[i] = stackitem.NewInterop(Iterator{
Values: iteratorValues,
Truncated: iteratorAux.Truncated,
})
}
var iter = Iterator{}
err = json.Unmarshal(arr[i], &iter)
if err != nil {
break
}
st[i] = stackitem.NewInterop(iter)
}
}
if err != nil {

View file

@ -4,8 +4,6 @@ import (
"encoding/json"
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util"
)
@ -25,25 +23,6 @@ type TransactionMetadata struct {
VMState string `json:"vmstate,omitempty"`
}
// NewTransactionOutputRaw returns a new ransactionOutputRaw object.
func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header, appExecResult *state.AppExecResult, chain LedgerAux) TransactionOutputRaw {
result := TransactionOutputRaw{
Transaction: *tx,
}
if header == nil {
return result
}
// confirmations formula
confirmations := int(chain.BlockHeight() - header.Index + 1)
result.TransactionMetadata = TransactionMetadata{
Blockhash: header.Hash(),
Confirmations: confirmations,
Timestamp: header.Timestamp,
VMState: appExecResult.VMState.String(),
}
return result
}
// MarshalJSON implements the json.Marshaler interface.
func (t TransactionOutputRaw) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(t.TransactionMetadata)

View file

@ -35,10 +35,10 @@ import (
"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/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/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -648,7 +648,7 @@ func TestSignAndPushP2PNotaryRequest(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 1, len(appLogs))
appLog := appLogs[0]
require.Equal(t, vm.HaltState, appLog.VMState)
require.Equal(t, vmstate.Halt, appLog.VMState)
require.Equal(t, appLog.GasConsumed, req.FallbackTransaction.SystemFee)
})
}
@ -1282,7 +1282,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) {
t.Run("default max items constraint", func(t *testing.T) {
res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil)
require.NoError(t, err)
require.Equal(t, vm.HaltState.String(), res.State)
require.Equal(t, vmstate.Halt.String(), res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, stackitem.ArrayT, res.Stack[0].Type())
arr, ok := res.Stack[0].Value().([]stackitem.Item)
@ -1298,7 +1298,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) {
max := 123
res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil, max)
require.NoError(t, err)
require.Equal(t, vm.HaltState.String(), res.State)
require.Equal(t, vmstate.Halt.String(), res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, stackitem.ArrayT, res.Stack[0].Type())
arr, ok := res.Stack[0].Value().([]stackitem.Item)

View file

@ -20,6 +20,7 @@ import (
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/config/limits"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
@ -101,29 +102,14 @@ type (
// or from historic MPT-based invocation. In the second case, iteratorIdentifiers are supposed
// to be filled during the first `traverseiterator` call using corresponding params.
iteratorIdentifiers []*iteratorIdentifier
// params stores invocation params for historic MPT-based iterator traversing. It is nil in case
// of default non-MPT-based sessions mechanism enabled.
params *invocationParams
timer *time.Timer
finalize func()
timer *time.Timer
finalize func()
}
// iteratorIdentifier represents Iterator on the server side, holding iterator ID, Iterator stackitem
// and iterator index on stack.
// iteratorIdentifier represents Iterator on the server side, holding iterator ID and Iterator stackitem.
iteratorIdentifier struct {
ID string
// Item represents Iterator stackitem. It is nil if SessionBackedByMPT is set to true and no `traverseiterator`
// call was called for the corresponding session.
// Item represents Iterator stackitem.
Item stackitem.Item
// StackIndex represents Iterator stackitem index on the stack. It can be used only for SessionBackedByMPT configuration.
StackIndex int
}
// invocationParams is a set of parameters used for invoke* calls.
invocationParams struct {
Trigger trigger.Type
Script []byte
ContractScriptHash util.Uint160
Transaction *transaction.Transaction
NextBlockHeight uint32
}
)
@ -350,9 +336,7 @@ func (s *Server) Shutdown() {
for _, session := range s.sessions {
// Concurrent iterator traversal may still be in process, thus need to protect iteratorIdentifiers access.
session.iteratorsLock.Lock()
if session.finalize != nil {
session.finalize()
}
session.finalize()
if !session.timer.Stop() {
<-session.timer.C
}
@ -582,6 +566,19 @@ func (s *Server) blockHashFromParam(param *params.Param) (util.Uint256, *respons
return hash, nil
}
func (s *Server) fillBlockMetadata(obj io.Serializable, h *block.Header) result.BlockMetadata {
res := result.BlockMetadata{
Size: io.GetVarSize(obj), // obj can be a Block or a Header.
Confirmations: s.chain.BlockHeight() - h.Index + 1,
}
hash := s.chain.GetHeaderHash(int(h.Index) + 1)
if !hash.Equals(util.Uint256{}) {
res.NextBlockHash = &hash
}
return res
}
func (s *Server) getBlock(reqParams params.Params) (interface{}, *response.Error) {
param := reqParams.Value(0)
hash, respErr := s.blockHashFromParam(param)
@ -595,7 +592,11 @@ func (s *Server) getBlock(reqParams params.Params) (interface{}, *response.Error
}
if v, _ := reqParams.Value(1).GetBoolean(); v {
return result.NewBlock(block, s.chain), nil
res := result.Block{
Block: *block,
BlockMetadata: s.fillBlockMetadata(block, &block.Header),
}
return res, nil
}
writer := io.NewBufBinWriter()
block.EncodeBinary(writer.BinWriter)
@ -829,7 +830,7 @@ contract_loop:
curAsset := &bs.Balances[len(bs.Balances)-1]
for i := range toks {
id, err := toks[i].TryBytes()
if err != nil || len(id) > storage.MaxStorageKeyLen {
if err != nil || len(id) > limits.MaxStorageKeyLen {
continue
}
var amount = "1"
@ -1542,8 +1543,11 @@ func (s *Server) getrawtransaction(reqParams params.Params) (interface{}, *respo
return nil, response.ErrUnknownTransaction
}
if v, _ := reqParams.Value(1).GetBoolean(); v {
if height == math.MaxUint32 {
return result.NewTransactionOutputRaw(tx, nil, nil, s.chain), nil
res := result.TransactionOutputRaw{
Transaction: *tx,
}
if height == math.MaxUint32 { // Mempooled transaction.
return res, nil
}
_header := s.chain.GetHeaderHash(int(height))
header, err := s.chain.GetHeader(_header)
@ -1557,7 +1561,13 @@ func (s *Server) getrawtransaction(reqParams params.Params) (interface{}, *respo
if len(aers) == 0 {
return nil, response.NewRPCError("Inconsistent application log", "application log for the transaction is empty")
}
return result.NewTransactionOutputRaw(tx, header, &aers[0], s.chain), nil
res.TransactionMetadata = result.TransactionMetadata{
Blockhash: header.Hash(),
Confirmations: int(s.chain.BlockHeight() - header.Index + 1),
Timestamp: header.Timestamp,
VMState: aers[0].VMState.String(),
}
return res, nil
}
return tx.Bytes(), nil
}
@ -1630,7 +1640,11 @@ func (s *Server) getBlockHeader(reqParams params.Params) (interface{}, *response
}
if verbose {
return result.NewHeader(h, s.chain), nil
res := result.Header{
Header: *h,
BlockMetadata: s.fillBlockMetadata(h, h),
}
return res, nil
}
buf := io.NewBufBinWriter()
@ -2006,64 +2020,117 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
if err != nil {
faultException = err.Error()
}
var registerIterator result.RegisterIterator
if s.config.SessionEnabled {
registerIterator = func(sessionID string, item stackitem.Item, stackIndex int, finalize func()) (uuid.UUID, error) {
iterID := uuid.New()
items := ic.VM.Estack().ToArray()
sess := s.postProcessExecStack(items)
var id uuid.UUID
if sess != nil {
// b == nil only when we're not using MPT-backed storage, therefore
// the second attempt won't stop here.
if s.config.SessionBackedByMPT && b == nil {
ic.Finalize()
b, err = s.getFakeNextBlock(ic.Block.Index)
if err != nil {
return nil, response.NewInternalServerError(fmt.Sprintf("unable to prepare block for historic call: %s", err))
}
// Rerun with MPT-backed storage.
return s.runScriptInVM(t, script, contractScriptHash, tx, b, verbose)
}
id = uuid.New()
sessionID := id.String()
sess.finalize = ic.Finalize
sess.timer = time.AfterFunc(time.Second*time.Duration(s.config.SessionExpirationTime), func() {
s.sessionsLock.Lock()
defer s.sessionsLock.Unlock()
if len(s.sessions) == 0 {
return
}
sess, ok := s.sessions[sessionID]
if !ok {
if len(s.sessions) >= s.config.SessionPoolSize {
return uuid.UUID{}, errors.New("max capacity reached")
}
timer := time.AfterFunc(time.Second*time.Duration(s.config.SessionExpirationTime), func() {
s.sessionsLock.Lock()
defer s.sessionsLock.Unlock()
if len(s.sessions) == 0 {
return
}
sess, ok := s.sessions[sessionID]
if !ok {
return
}
sess.iteratorsLock.Lock()
if sess.finalize != nil {
sess.finalize()
}
delete(s.sessions, sessionID)
sess.iteratorsLock.Unlock()
})
sess = &session{
finalize: finalize,
timer: timer,
}
if s.config.SessionBackedByMPT {
sess.params = &invocationParams{
Trigger: t,
Script: script,
ContractScriptHash: contractScriptHash,
Transaction: tx,
NextBlockHeight: ic.Block.Index,
}
// Call finalizer manually if MPT-based iterator sessions are enabled. If disabled, then register finalizator.
if finalize != nil {
finalize()
sess.finalize = nil
}
item = nil
}
return
}
sess.iteratorIdentifiers = append(sess.iteratorIdentifiers, &iteratorIdentifier{
ID: iterID.String(),
Item: item,
StackIndex: stackIndex,
})
s.sessions[sessionID] = sess
sess.iteratorsLock.Lock()
sess.finalize()
delete(s.sessions, sessionID)
sess.iteratorsLock.Unlock()
})
s.sessionsLock.Lock()
if len(s.sessions) >= s.config.SessionPoolSize {
ic.Finalize()
s.sessionsLock.Unlock()
return iterID, nil
return nil, response.NewInternalServerError("max session capacity reached")
}
s.sessions[sessionID] = sess
s.sessionsLock.Unlock()
} else {
ic.Finalize()
}
var diag *result.InvokeDiag
tree := ic.VM.GetInvocationTree()
if tree != nil {
diag = &result.InvokeDiag{
Invocations: tree.Calls,
Changes: storage.BatchToOperations(ic.DAO.GetBatch()),
}
}
return result.NewInvoke(ic, script, faultException, registerIterator, s.config.MaxIteratorResultItems), nil
notifications := ic.Notifications
if notifications == nil {
notifications = make([]state.NotificationEvent, 0)
}
res := &result.Invoke{
State: ic.VM.State().String(),
GasConsumed: ic.VM.GasConsumed(),
Script: script,
Stack: items,
FaultException: faultException,
Notifications: notifications,
Diagnostics: diag,
Session: id,
}
return res, nil
}
// postProcessExecStack changes iterator interop items according to the server configuration.
// It does modifications in-place, but it returns a session if any iterator was registered.
func (s *Server) postProcessExecStack(stack []stackitem.Item) *session {
var sess session
for i, v := range stack {
var id uuid.UUID
stack[i], id = s.registerOrDumpIterator(v)
if id != (uuid.UUID{}) {
sess.iteratorIdentifiers = append(sess.iteratorIdentifiers, &iteratorIdentifier{
ID: id.String(),
Item: v,
})
}
}
if len(sess.iteratorIdentifiers) != 0 {
return &sess
}
return nil
}
// registerOrDumpIterator changes iterator interop stack items into result.Iterator
// interop stack items and returns a uuid for it if sessions are enabled. All the other stack
// items are not changed.
func (s *Server) registerOrDumpIterator(item stackitem.Item) (stackitem.Item, uuid.UUID) {
var iterID uuid.UUID
if (item.Type() != stackitem.InteropT) || !iterator.IsIterator(item) {
return item, iterID
}
var resIterator result.Iterator
if s.config.SessionEnabled {
iterID = uuid.New()
resIterator.ID = &iterID
} else {
resIterator.Values, resIterator.Truncated = iterator.ValuesTruncated(item, s.config.MaxIteratorResultItems)
}
return stackitem.NewInterop(resIterator), iterID
}
func (s *Server) traverseIterator(reqParams params.Params) (interface{}, *response.Error) {
@ -2102,43 +2169,11 @@ func (s *Server) traverseIterator(reqParams params.Params) (interface{}, *respon
s.sessionsLock.Unlock()
var (
iIDStr = iID.String()
iVals []stackitem.Item
respErr *response.Error
iIDStr = iID.String()
iVals []stackitem.Item
)
for _, it := range session.iteratorIdentifiers {
if iIDStr == it.ID {
// If SessionBackedByMPT is enabled, then use MPT-backed historic call to retrieve and traverse iterator.
// Otherwise, iterator stackitem is ready and can be used.
if s.config.SessionBackedByMPT && it.Item == nil {
var (
b *block.Block
ic *interop.Context
)
b, err = s.getFakeNextBlock(session.params.NextBlockHeight)
if err != nil {
session.iteratorsLock.Unlock()
return nil, response.NewInternalServerError(fmt.Sprintf("unable to prepare block for historic call: %s", err))
}
ic, respErr = s.prepareInvocationContext(session.params.Trigger, session.params.Script, session.params.ContractScriptHash, session.params.Transaction, b, false)
if respErr != nil {
session.iteratorsLock.Unlock()
return nil, respErr
}
_ = ic.VM.Run() // No error check because FAULTed invocations could also contain iterator on stack.
stack := ic.VM.Estack().ToArray()
// Fill in the whole set of iterators for the current session in order not to repeat test invocation one more time for other session iterators.
for _, itID := range session.iteratorIdentifiers {
j := itID.StackIndex
if (stack[j].Type() != stackitem.InteropT) || !iterator.IsIterator(stack[j]) {
session.iteratorsLock.Unlock()
return nil, response.NewInternalServerError(fmt.Sprintf("inconsistent historic call result: expected %s, got %s at stack position #%d", stackitem.InteropT, stack[j].Type(), j))
}
session.iteratorIdentifiers[j].Item = stack[j]
}
session.finalize = ic.Finalize
}
iVals = iterator.Values(it.Item, count)
break
}
@ -2171,9 +2206,7 @@ func (s *Server) terminateSession(reqParams params.Params) (interface{}, *respon
// Iterators access Seek channel under the hood; finalizer closes this channel, thus,
// we need to perform finalisation under iteratorsLock.
session.iteratorsLock.Lock()
if session.finalize != nil {
session.finalize()
}
session.finalize()
if !session.timer.Stop() {
<-session.timer.C
}

View file

@ -27,7 +27,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -41,10 +41,11 @@ import (
rpc2 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster"
"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/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -102,7 +103,7 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.Equal(t, 1, len(res.Executions))
assert.Equal(t, expectedTxHash, res.Container)
assert.Equal(t, trigger.Application, res.Executions[0].Trigger)
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
assert.Equal(t, vmstate.Halt, res.Executions[0].VMState)
},
},
{
@ -116,7 +117,7 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.Equal(t, 2, len(res.Executions))
assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger)
assert.Equal(t, trigger.PostPersist, res.Executions[1].Trigger)
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
assert.Equal(t, vmstate.Halt, res.Executions[0].VMState)
},
},
{
@ -129,7 +130,7 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.Equal(t, genesisBlockHash, res.Container.StringLE())
assert.Equal(t, 1, len(res.Executions))
assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger)
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
assert.Equal(t, vmstate.Halt, res.Executions[0].VMState)
},
},
{
@ -142,7 +143,7 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.Equal(t, genesisBlockHash, res.Container.StringLE())
assert.Equal(t, 1, len(res.Executions))
assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger)
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
assert.Equal(t, vmstate.Halt, res.Executions[0].VMState)
},
},
{
@ -925,7 +926,7 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.Equal(t, "HALT", res.State)
assert.Equal(t, []stackitem.Item{stackitem.Make(true)}, res.Stack)
assert.NotEqual(t, 0, res.GasConsumed)
chg := []storage.Operation{{
chg := []dboper.Operation{{
State: "Changed",
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb},
Value: []byte{0x70, 0xd9, 0x59, 0x9d, 0x51, 0x79, 0x12},
@ -960,13 +961,13 @@ var rpcTestCases = map[string][]rpcTestCase{
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
Notifications: []state.NotificationEvent{},
Diagnostics: &result.InvokeDiag{
Changes: []storage.Operation{},
Invocations: []*vm.InvocationTree{{
Changes: []dboper.Operation{},
Invocations: []*invocations.Tree{{
Current: hash.Hash160(script),
Calls: []*vm.InvocationTree{
Calls: []*invocations.Tree{
{
Current: nnsHash,
Calls: []*vm.InvocationTree{
Calls: []*invocations.Tree{
{
Current: stdHash,
},
@ -1073,13 +1074,13 @@ var rpcTestCases = map[string][]rpcTestCase{
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
Notifications: []state.NotificationEvent{},
Diagnostics: &result.InvokeDiag{
Changes: []storage.Operation{},
Invocations: []*vm.InvocationTree{{
Changes: []dboper.Operation{},
Invocations: []*invocations.Tree{{
Current: hash.Hash160(script),
Calls: []*vm.InvocationTree{
Calls: []*invocations.Tree{
{
Current: nnsHash,
Calls: []*vm.InvocationTree{
Calls: []*invocations.Tree{
{
Current: stdHash,
},
@ -1165,8 +1166,8 @@ var rpcTestCases = map[string][]rpcTestCase{
FaultException: "at instruction 0 (ROT): too big index",
Notifications: []state.NotificationEvent{},
Diagnostics: &result.InvokeDiag{
Changes: []storage.Operation{},
Invocations: []*vm.InvocationTree{{
Changes: []dboper.Operation{},
Invocations: []*invocations.Tree{{
Current: hash.Hash160(script),
}},
},
@ -1276,8 +1277,8 @@ var rpcTestCases = map[string][]rpcTestCase{
FaultException: "at instruction 0 (ROT): too big index",
Notifications: []state.NotificationEvent{},
Diagnostics: &result.InvokeDiag{
Changes: []storage.Operation{},
Invocations: []*vm.InvocationTree{{
Changes: []dboper.Operation{},
Invocations: []*invocations.Tree{{
Current: hash.Hash160(script),
}},
},
@ -1966,9 +1967,9 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.NoError(t, json.Unmarshal(data, &res))
require.Equal(t, 2, len(res.Executions))
require.Equal(t, trigger.OnPersist, res.Executions[0].Trigger)
require.Equal(t, vm.HaltState, res.Executions[0].VMState)
require.Equal(t, vmstate.Halt, res.Executions[0].VMState)
require.Equal(t, trigger.PostPersist, res.Executions[1].Trigger)
require.Equal(t, vm.HaltState, res.Executions[1].VMState)
require.Equal(t, vmstate.Halt, res.Executions[1].VMState)
})
t.Run("submit", func(t *testing.T) {

View file

@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -53,7 +54,7 @@ type Context struct {
// NEF represents a NEF file for the current contract.
NEF *nef.File
// invTree is an invocation tree (or branch of it) for this context.
invTree *InvocationTree
invTree *invocations.Tree
// onUnload is a callback that should be called after current context unloading
// if no exception occurs.
onUnload ContextUnloadCallback

View file

@ -1,12 +0,0 @@
package vm
import (
"github.com/nspcc-dev/neo-go/pkg/util"
)
// InvocationTree represents a tree with script hashes; when traversing it,
// you can see how contracts called each other.
type InvocationTree struct {
Current util.Uint160 `json:"hash"`
Calls []*InvocationTree `json:"call,omitempty"`
}

View file

@ -4,6 +4,7 @@ import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
)
@ -36,13 +37,13 @@ func TestInvocationTree(t *testing.T) {
topHash := v.Context().ScriptHash()
require.NoError(t, v.Run())
res := &InvocationTree{
Calls: []*InvocationTree{{
res := &invocations.Tree{
Calls: []*invocations.Tree{{
Current: topHash,
Calls: []*InvocationTree{
Calls: []*invocations.Tree{
{
Current: util.Uint160{1},
Calls: []*InvocationTree{
Calls: []*invocations.Tree{
{
Current: util.Uint160{2},
},
@ -53,7 +54,7 @@ func TestInvocationTree(t *testing.T) {
},
{
Current: util.Uint160{4},
Calls: []*InvocationTree{
Calls: []*invocations.Tree{
{
Current: util.Uint160{5},
},

View file

@ -0,0 +1,12 @@
package invocations
import (
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Tree represents a tree with script hashes; when traversing it,
// you can see how contracts called each other.
type Tree struct {
Current util.Uint160 `json:"hash"`
Calls []*Tree `json:"call,omitempty"`
}

View file

@ -18,6 +18,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require"
)
@ -44,7 +45,7 @@ type (
}
vmUTExecutionEngineState struct {
State State `json:"state"`
State vmstate.State `json:"state"`
ResultStack []vmUTStackItem `json:"resultStack"`
InvocationStack []vmUTExecutionContextState `json:"invocationStack"`
}
@ -152,14 +153,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 = vmstate.Break
vm.SyscallHandler = testSyscallHandler
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 == vmstate.Fault { // do not compare stacks on fault
continue
}

View file

@ -1,97 +0,0 @@
package vm
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStateFromString(t *testing.T) {
var (
s State
err error
)
s, err = StateFromString("HALT")
assert.NoError(t, err)
assert.Equal(t, HaltState, s)
s, err = StateFromString("BREAK")
assert.NoError(t, err)
assert.Equal(t, BreakState, s)
s, err = StateFromString("FAULT")
assert.NoError(t, err)
assert.Equal(t, FaultState, s)
s, err = StateFromString("NONE")
assert.NoError(t, err)
assert.Equal(t, NoneState, s)
s, err = StateFromString("HALT, BREAK")
assert.NoError(t, err)
assert.Equal(t, HaltState|BreakState, s)
s, err = StateFromString("FAULT, BREAK")
assert.NoError(t, err)
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.False(t, HaltState.HasFlag(BreakState))
assert.False(t, NoneState.HasFlag(HaltState))
assert.False(t, (FaultState | BreakState).HasFlag(HaltState))
}
func TestState_MarshalJSON(t *testing.T) {
var (
data []byte
err error
)
data, err = json.Marshal(HaltState | BreakState)
assert.NoError(t, err)
assert.Equal(t, data, []byte(`"HALT, BREAK"`))
data, err = json.Marshal(FaultState)
assert.NoError(t, err)
assert.Equal(t, data, []byte(`"FAULT"`))
}
func TestState_UnmarshalJSON(t *testing.T) {
var (
s State
err error
)
err = json.Unmarshal([]byte(`"HALT, BREAK"`), &s)
assert.NoError(t, err)
assert.Equal(t, HaltState|BreakState, s)
err = json.Unmarshal([]byte(`"FAULT, BREAK"`), &s)
assert.NoError(t, err)
assert.Equal(t, FaultState|BreakState, s)
err = json.Unmarshal([]byte(`"NONE"`), &s)
assert.NoError(t, err)
assert.Equal(t, NoneState, s)
}
// TestState_EnumCompat tests that byte value of State matches the C#'s one got from
// https://github.com/neo-project/neo-vm/blob/0028d862e253bda3c12eb8bb007a2d95822d3922/src/neo-vm/VMState.cs#L16.
func TestState_EnumCompat(t *testing.T) {
assert.Equal(t, byte(0), byte(NoneState))
assert.Equal(t, byte(1<<0), byte(HaltState))
assert.Equal(t, byte(1<<1), byte(FaultState))
assert.Equal(t, byte(1<<2), byte(BreakState))
}

View file

@ -21,8 +21,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/util/slice"
"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
type errorAtInstruct struct {
@ -62,7 +64,7 @@ type SyscallHandler = func(*VM, uint32) error
// VM represents the virtual machine.
type VM struct {
state State
state vmstate.State
// callback to get interop price
getPrice func(opcode.Opcode, []byte) int64
@ -86,7 +88,7 @@ type VM struct {
trigger trigger.Type
// invTree is a top-level invocation tree (if enabled).
invTree *InvocationTree
invTree *invocations.Tree
}
var (
@ -104,7 +106,7 @@ func New() *VM {
// NewWithTrigger returns a new VM for executions triggered by t.
func NewWithTrigger(t trigger.Type) *VM {
vm := &VM{
state: NoneState,
state: vmstate.None,
trigger: t,
SyscallHandler: defaultSyscallHandler,
@ -126,7 +128,7 @@ func (v *VM) SetPriceGetter(f func(opcode.Opcode, []byte) int64) {
// more efficient. It reuses invocation and evaluation stacks as well as VM structure
// itself.
func (v *VM) Reset(t trigger.Type) {
v.state = NoneState
v.state = vmstate.None
v.getPrice = nil
v.istack.elems = v.istack.elems[:0]
v.estack.elems = v.estack.elems[:0]
@ -270,11 +272,11 @@ func (v *VM) LoadFileWithFlags(path string, f callflag.CallFlag) error {
// CollectInvocationTree enables collecting invocation tree data.
func (v *VM) EnableInvocationTree() {
v.invTree = &InvocationTree{}
v.invTree = &invocations.Tree{}
}
// GetInvocationTree returns the current invocation tree structure.
func (v *VM) GetInvocationTree() *InvocationTree {
func (v *VM) GetInvocationTree() *invocations.Tree {
return v.invTree
}
@ -288,7 +290,7 @@ func (v *VM) LoadWithFlags(prog []byte, f callflag.CallFlag) {
// Clear all stacks and state, it could be a reload.
v.istack.Clear()
v.estack.Clear()
v.state = NoneState
v.state = vmstate.None
v.gasConsumed = 0
v.invTree = nil
v.LoadScriptWithFlags(prog, f)
@ -355,7 +357,7 @@ func (v *VM) loadScriptWithCallingHash(b []byte, exe *nef.File, caller util.Uint
if parent != nil {
curTree = parent.invTree
}
newTree := &InvocationTree{Current: ctx.ScriptHash()}
newTree := &invocations.Tree{Current: ctx.ScriptHash()}
curTree.Calls = append(curTree.Calls, newTree)
ctx.invTree = newTree
}
@ -398,7 +400,7 @@ func dumpStack(s *Stack) string {
}
// State returns the state for the VM.
func (v *VM) State() State {
func (v *VM) State() vmstate.State {
return v.state
}
@ -413,39 +415,39 @@ func (v *VM) Run() error {
var ctx *Context
if !v.Ready() {
v.state = FaultState
v.state = vmstate.Fault
return errors.New("no program loaded")
}
if v.state.HasFlag(FaultState) {
if v.state.HasFlag(vmstate.Fault) {
// 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
// vmstate.Halt (the default) or vmstate.Break are safe to continue.
v.state = vmstate.None
ctx = v.Context()
for {
switch {
case v.state.HasFlag(FaultState):
case v.state.HasFlag(vmstate.Fault):
// 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(vmstate.Halt), v.state.HasFlag(vmstate.Break):
// Normal exit from this loop.
return nil
case v.state == NoneState:
case v.state == vmstate.None:
if err := v.step(ctx); err != nil {
return err
}
default:
v.state = FaultState
v.state = vmstate.Fault
return errors.New("unknown state")
}
// check for breakpoint before executing the next instruction
ctx = v.Context()
if ctx != nil && ctx.atBreakPoint() {
v.state = BreakState
v.state = vmstate.Break
}
}
}
@ -460,7 +462,7 @@ func (v *VM) Step() error {
func (v *VM) step(ctx *Context) error {
op, param, err := ctx.Next()
if err != nil {
v.state = FaultState
v.state = vmstate.Fault
return newError(ctx.ip, op, err)
}
return v.execute(ctx, op, param)
@ -472,7 +474,7 @@ func (v *VM) StepInto() error {
ctx := v.Context()
if ctx == nil {
v.state = HaltState
v.state = vmstate.Halt
}
if v.HasStopped() {
@ -482,7 +484,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 = vmstate.Fault
return newError(ctx.ip, op, err)
}
vErr := v.execute(ctx, op, param)
@ -493,7 +495,7 @@ func (v *VM) StepInto() error {
cctx := v.Context()
if cctx != nil && cctx.atBreakPoint() {
v.state = BreakState
v.state = vmstate.Break
}
return nil
}
@ -501,16 +503,16 @@ 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 == vmstate.Break {
v.state = vmstate.None
}
expSize := v.istack.Len()
for v.state == NoneState && v.istack.Len() >= expSize {
for v.state == vmstate.None && v.istack.Len() >= expSize {
err = v.StepInto()
}
if v.state == NoneState {
v.state = BreakState
if v.state == vmstate.None {
v.state = vmstate.Break
}
return err
}
@ -523,20 +525,20 @@ func (v *VM) StepOver() error {
return err
}
if v.state == BreakState {
v.state = NoneState
if v.state == vmstate.Break {
v.state = vmstate.None
}
expSize := v.istack.Len()
for {
err = v.StepInto()
if !(v.state == NoneState && v.istack.Len() > expSize) {
if !(v.state == vmstate.None && v.istack.Len() > expSize) {
break
}
}
if v.state == NoneState {
v.state = BreakState
if v.state == vmstate.None {
v.state = vmstate.Break
}
return err
@ -545,22 +547,22 @@ func (v *VM) StepOver() error {
// HasFailed returns whether the VM is in the failed state now. Usually, it's used to
// check status after Run.
func (v *VM) HasFailed() bool {
return v.state.HasFlag(FaultState)
return v.state.HasFlag(vmstate.Fault)
}
// HasStopped returns whether the VM is in the Halt or Failed state.
func (v *VM) HasStopped() bool {
return v.state.HasFlag(HaltState) || v.state.HasFlag(FaultState)
return v.state.HasFlag(vmstate.Halt) || v.state.HasFlag(vmstate.Fault)
}
// HasHalted returns whether the VM is in the Halt state.
func (v *VM) HasHalted() bool {
return v.state.HasFlag(HaltState)
return v.state.HasFlag(vmstate.Halt)
}
// AtBreakpoint returns whether the VM is at breakpoint.
func (v *VM) AtBreakpoint() bool {
return v.state.HasFlag(BreakState)
return v.state.HasFlag(vmstate.Break)
}
// GetInteropID converts instruction parameter to an interop ID.
@ -574,10 +576,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 = vmstate.Fault
err = newError(ctx.ip, op, errRecover)
} else if v.refs > MaxStackSize {
v.state = FaultState
v.state = vmstate.Fault
err = newError(ctx.ip, op, "stack is too big")
}
}()
@ -1469,7 +1471,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 = vmstate.Halt
break
}

View file

@ -1,23 +1,29 @@
package vm
/*
Package vmstate contains a set of VM state flags along with appropriate type.
It provides a set of conversion/marshaling functions/methods for this type as
well. This package is made to make VM state reusable across all of the other
components that need it without importing whole VM package.
*/
package vmstate
import (
"errors"
"strings"
)
// State of the VM.
// State of the VM. It's a set of flags stored in the integer number.
type State uint8
// Available States.
const (
// HaltState represents HALT VM state.
HaltState State = 1 << iota
// FaultState represents FAULT VM state.
FaultState
// BreakState represents BREAK VM state.
BreakState
// NoneState represents NONE VM state.
NoneState State = 0
// Halt represents HALT VM state (finished normally).
Halt State = 1 << iota
// Fault represents FAULT VM state (finished with an error).
Fault
// Break represents BREAK VM state (running, debug mode).
Break
// None represents NONE VM state (not started yet).
None State = 0
)
// HasFlag checks for State flag presence.
@ -25,40 +31,40 @@ func (s State) HasFlag(f State) bool {
return s&f != 0
}
// String implements the stringer interface.
// String implements the fmt.Stringer interface.
func (s State) String() string {
if s == NoneState {
if s == None {
return "NONE"
}
ss := make([]string, 0, 3)
if s.HasFlag(HaltState) {
if s.HasFlag(Halt) {
ss = append(ss, "HALT")
}
if s.HasFlag(FaultState) {
if s.HasFlag(Fault) {
ss = append(ss, "FAULT")
}
if s.HasFlag(BreakState) {
if s.HasFlag(Break) {
ss = append(ss, "BREAK")
}
return strings.Join(ss, ", ")
}
// StateFromString converts a string into the VM State.
func StateFromString(s string) (st State, err error) {
// FromString converts a string into the State.
func FromString(s string) (st State, err error) {
if s = strings.TrimSpace(s); s == "NONE" {
return NoneState, nil
return None, nil
}
ss := strings.Split(s, ",")
for _, state := range ss {
switch state = strings.TrimSpace(state); state {
case "HALT":
st |= HaltState
st |= Halt
case "FAULT":
st |= FaultState
st |= Fault
case "BREAK":
st |= BreakState
st |= Break
default:
return 0, errors.New("unknown state")
}
@ -78,6 +84,6 @@ func (s *State) UnmarshalJSON(data []byte) (err error) {
return errors.New("wrong format")
}
*s, err = StateFromString(string(data[1 : l-1]))
*s, err = FromString(string(data[1 : l-1]))
return
}

View file

@ -0,0 +1,97 @@
package vmstate
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFromString(t *testing.T) {
var (
s State
err error
)
s, err = FromString("HALT")
assert.NoError(t, err)
assert.Equal(t, Halt, s)
s, err = FromString("BREAK")
assert.NoError(t, err)
assert.Equal(t, Break, s)
s, err = FromString("FAULT")
assert.NoError(t, err)
assert.Equal(t, Fault, s)
s, err = FromString("NONE")
assert.NoError(t, err)
assert.Equal(t, None, s)
s, err = FromString("HALT, BREAK")
assert.NoError(t, err)
assert.Equal(t, Halt|Break, s)
s, err = FromString("FAULT, BREAK")
assert.NoError(t, err)
assert.Equal(t, Fault|Break, s)
_, err = FromString("HALT, KEK")
assert.Error(t, err)
}
func TestState_HasFlag(t *testing.T) {
assert.True(t, Halt.HasFlag(Halt))
assert.True(t, Break.HasFlag(Break))
assert.True(t, Fault.HasFlag(Fault))
assert.True(t, (Halt | Break).HasFlag(Halt))
assert.True(t, (Halt | Break).HasFlag(Break))
assert.False(t, Halt.HasFlag(Break))
assert.False(t, None.HasFlag(Halt))
assert.False(t, (Fault | Break).HasFlag(Halt))
}
func TestState_MarshalJSON(t *testing.T) {
var (
data []byte
err error
)
data, err = json.Marshal(Halt | Break)
assert.NoError(t, err)
assert.Equal(t, data, []byte(`"HALT, BREAK"`))
data, err = json.Marshal(Fault)
assert.NoError(t, err)
assert.Equal(t, data, []byte(`"FAULT"`))
}
func TestState_UnmarshalJSON(t *testing.T) {
var (
s State
err error
)
err = json.Unmarshal([]byte(`"HALT, BREAK"`), &s)
assert.NoError(t, err)
assert.Equal(t, Halt|Break, s)
err = json.Unmarshal([]byte(`"FAULT, BREAK"`), &s)
assert.NoError(t, err)
assert.Equal(t, Fault|Break, s)
err = json.Unmarshal([]byte(`"NONE"`), &s)
assert.NoError(t, err)
assert.Equal(t, None, s)
}
// TestState_EnumCompat tests that byte value of State matches the C#'s one got from
// https://github.com/neo-project/neo-vm/blob/0028d862e253bda3c12eb8bb007a2d95822d3922/src/neo-vm/VMState.cs#L16.
func TestState_EnumCompat(t *testing.T) {
assert.Equal(t, byte(0), byte(None))
assert.Equal(t, byte(1<<0), byte(Halt))
assert.Equal(t, byte(1<<1), byte(Fault))
assert.Equal(t, byte(1<<2), byte(Break))
}