Merge pull request #1056 from nspcc-dev/fix/runtime

Adjust runtime syscalls to NEO3
This commit is contained in:
Roman Khimov 2020-06-17 13:24:42 +03:00 committed by GitHub
commit 06ef1e6c74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 244 additions and 82 deletions

View file

@ -1,22 +1,21 @@
package enginecontract
import (
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
)
// Main is that famous Main() function, you know.
func Main() bool {
tx := engine.GetScriptContainer()
tx := runtime.GetScriptContainer()
runtime.Notify(tx)
callingScriptHash := engine.GetCallingScriptHash()
callingScriptHash := runtime.GetCallingScriptHash()
runtime.Notify(callingScriptHash)
execScriptHash := engine.GetExecutingScriptHash()
execScriptHash := runtime.GetExecutingScriptHash()
runtime.Notify(execScriptHash)
entryScriptHash := engine.GetEntryScriptHash()
entryScriptHash := runtime.GetEntryScriptHash()
runtime.Notify(entryScriptHash)
return true

View file

@ -1,7 +1,6 @@
package nep5
import (
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
@ -85,7 +84,7 @@ func IsUsableAddress(addr []byte) bool {
}
// Check if a smart contract is calling scripthash
callingScriptHash := engine.GetCallingScriptHash()
callingScriptHash := runtime.GetCallingScriptHash()
if util.Equals(callingScriptHash, addr) {
return true
}

View file

@ -586,6 +586,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.Ident:
if tv := c.typeAndValueOf(n); tv.Value != nil {
c.emitLoadConst(tv)
} else if n.Name == "nil" {
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
} else {
c.emitLoadVar(n.Name)
}

View file

@ -24,6 +24,14 @@ var syscalls = map[string]map[string]string{
"Put": "System.Storage.Put",
},
"runtime": {
"GetScriptContainer": "System.Runtime.GetScriptContainer",
"GetCallingScriptHash": "System.Runtime.GetCallingScriptHash",
"GetEntryScriptHash": "System.Runtime.GetEntryScriptHash",
"GetExecutingScriptHash": "System.Runtime.GetExecutingScriptHash",
"GetNotifications": "System.Runtime.GetNotifications",
"GetInvocationCounter": "System.Runtime.GetInvocationCounter",
"GasLeft": "System.Runtime.GasLeft",
"GetTrigger": "System.Runtime.GetTrigger",
"CheckWitness": "System.Runtime.CheckWitness",
"Notify": "System.Runtime.Notify",
@ -44,12 +52,9 @@ var syscalls = map[string]map[string]string{
"Create": "System.Contract.Create",
"Destroy": "System.Contract.Destroy",
"Update": "System.Contract.Update",
},
"engine": {
"GetScriptContainer": "System.ExecutionEngine.GetScriptContainer",
"GetCallingScriptHash": "System.ExecutionEngine.GetCallingScriptHash",
"GetEntryScriptHash": "System.ExecutionEngine.GetEntryScriptHash",
"GetExecutingScriptHash": "System.ExecutionEngine.GetExecutingScriptHash",
"IsStandard": "System.Contract.IsStandard",
"CreateStandardAccount": "System.Contract.CreateStandardAccount",
},
"iterator": {
"Concat": "System.Iterator.Concat",

View file

@ -566,7 +566,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
v.LoadScriptWithFlags(tx.Script, smartcontract.All)
v.SetPriceGetter(getPrice)
if bc.config.FreeGasLimit > 0 {
v.SetGasLimit(bc.config.FreeGasLimit + tx.SystemFee)
v.GasLimit = bc.config.FreeGasLimit + tx.SystemFee
}
err := v.Run()

View file

@ -30,6 +30,7 @@ type Context struct {
DAO *dao.Cached
Notifications []state.NotificationEvent
Log *zap.Logger
Invocations map[util.Uint160]int
}
// NewContext returns new interop context.
@ -45,6 +46,7 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n
DAO: dao,
Notifications: nes,
Log: log,
Invocations: make(map[util.Uint160]int),
}
}

View file

@ -0,0 +1,61 @@
package runtime
import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"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/pkg/errors"
)
// GasLeft returns remaining amount of GAS.
func GasLeft(_ *interop.Context, v *vm.VM) error {
v.Estack().PushVal(int64(v.GasLimit - v.GasConsumed()))
return nil
}
// GetNotifications returns notifications emitted by current contract execution.
func GetNotifications(ic *interop.Context, v *vm.VM) error {
item := v.Estack().Pop().Item()
notifications := ic.Notifications
if _, ok := item.(stackitem.Null); !ok {
b, err := item.TryBytes()
if err != nil {
return err
}
u, err := util.Uint160DecodeBytesBE(b)
if err != nil {
return err
}
notifications = []state.NotificationEvent{}
for i := range ic.Notifications {
if ic.Notifications[i].ScriptHash.Equals(u) {
notifications = append(notifications, ic.Notifications[i])
}
}
}
if len(notifications) > vm.MaxStackSize {
return errors.New("too many notifications")
}
arr := stackitem.NewArray(make([]stackitem.Item, 0, len(notifications)))
for i := range notifications {
ev := stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(notifications[i].ScriptHash.BytesBE()),
notifications[i].Item,
})
arr.Append(ev)
}
v.Estack().PushVal(arr)
return nil
}
// GetInvocationCounter returns how many times current contract was invoked during current tx execution.
func GetInvocationCounter(ic *interop.Context, v *vm.VM) error {
count, ok := ic.Invocations[v.GetCurrentScriptHash()]
if !ok {
return errors.New("current contract wasn't invoked from others")
}
v.Estack().PushVal(count)
return nil
}

View file

@ -443,6 +443,7 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac
return errors.New("disallowed method call")
}
}
ic.Invocations[u]++
v.LoadScriptWithHash(cs.Script, u, v.Context().GetCallFlags()&f)
v.Estack().PushVal(args)
v.Estack().PushVal(method)

View file

@ -5,7 +5,9 @@ import (
"testing"
"github.com/nspcc-dev/dbft/crypto"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -197,3 +199,66 @@ func TestContractCreateAccount(t *testing.T) {
require.Error(t, contractCreateStandardAccount(ic, v))
})
}
func TestRuntimeGasLeft(t *testing.T) {
v, ic, chain := createVM(t)
defer chain.Close()
v.GasLimit = 100
v.AddGas(58)
require.NoError(t, runtime.GasLeft(ic, v))
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
}
func TestRuntimeGetNotifications(t *testing.T) {
v, ic, chain := createVM(t)
defer chain.Close()
ic.Notifications = []state.NotificationEvent{
{ScriptHash: util.Uint160{1}, Item: stackitem.NewByteArray([]byte{11})},
{ScriptHash: util.Uint160{2}, Item: stackitem.NewByteArray([]byte{22})},
{ScriptHash: util.Uint160{1}, Item: stackitem.NewByteArray([]byte{33})},
}
t.Run("NoFilter", func(t *testing.T) {
v.Estack().PushVal(stackitem.Null{})
require.NoError(t, runtime.GetNotifications(ic, v))
arr := v.Estack().Pop().Array()
require.Equal(t, len(ic.Notifications), len(arr))
for i := range arr {
elem := arr[i].Value().([]stackitem.Item)
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
require.Equal(t, ic.Notifications[i].Item, elem[1])
}
})
t.Run("WithFilter", func(t *testing.T) {
h := util.Uint160{2}.BytesBE()
v.Estack().PushVal(h)
require.NoError(t, runtime.GetNotifications(ic, v))
arr := v.Estack().Pop().Array()
require.Equal(t, 1, len(arr))
elem := arr[0].Value().([]stackitem.Item)
require.Equal(t, h, elem[0].Value())
require.Equal(t, ic.Notifications[1].Item, elem[1])
})
}
func TestRuntimeGetInvocationCounter(t *testing.T) {
v, ic, chain := createVM(t)
defer chain.Close()
ic.Invocations[hash.Hash160([]byte{2})] = 42
t.Run("Zero", func(t *testing.T) {
v.LoadScript([]byte{1})
require.Error(t, runtime.GetInvocationCounter(ic, v))
})
t.Run("NonZero", func(t *testing.T) {
v.LoadScript([]byte{2})
require.NoError(t, runtime.GetInvocationCounter(ic, v))
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
})
}

View file

@ -99,10 +99,6 @@ var systemInterops = []interop.Function{
{Name: "System.Enumerator.Create", Func: enumerator.Create, Price: 400},
{Name: "System.Enumerator.Next", Func: enumerator.Next, Price: 1000000},
{Name: "System.Enumerator.Value", Func: enumerator.Value, Price: 400},
{Name: "System.ExecutionEngine.GetCallingScriptHash", Func: engineGetCallingScriptHash, Price: 1},
{Name: "System.ExecutionEngine.GetEntryScriptHash", Func: engineGetEntryScriptHash, Price: 1},
{Name: "System.ExecutionEngine.GetExecutingScriptHash", Func: engineGetExecutingScriptHash, Price: 1},
{Name: "System.ExecutionEngine.GetScriptContainer", Func: engineGetScriptContainer, Price: 1},
{Name: "System.Iterator.Concat", Func: iterator.Concat, Price: 400},
{Name: "System.Iterator.Create", Func: iterator.Create, Price: 400},
{Name: "System.Iterator.Key", Func: iterator.Key, Price: 400},
@ -110,13 +106,19 @@ var systemInterops = []interop.Function{
{Name: "System.Iterator.Values", Func: iterator.Values, Price: 400},
{Name: "System.Json.Deserialize", Func: json.Deserialize, Price: 500000},
{Name: "System.Json.Serialize", Func: json.Serialize, Price: 100000},
{Name: "System.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200, RequiredFlags: smartcontract.AllowStates},
{Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 1,
AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowStates},
{Name: "System.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 1},
{Name: "System.Runtime.Log", Func: runtimeLog, Price: 1, RequiredFlags: smartcontract.AllowNotify},
{Name: "System.Runtime.Notify", Func: runtimeNotify, Price: 1, RequiredFlags: smartcontract.AllowNotify},
{Name: "System.Runtime.Platform", Func: runtimePlatform, Price: 1},
{Name: "System.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 30000},
{Name: "System.Runtime.GasLeft", Func: runtime.GasLeft, Price: 400},
{Name: "System.Runtime.GetCallingScriptHash", Func: engineGetCallingScriptHash, Price: 400},
{Name: "System.Runtime.GetEntryScriptHash", Func: engineGetEntryScriptHash, Price: 400},
{Name: "System.Runtime.GetExecutingScriptHash", Func: engineGetExecutingScriptHash, Price: 400},
{Name: "System.Runtime.GetInvocationCounter", Func: runtime.GetInvocationCounter, Price: 400},
{Name: "System.Runtime.GetScriptContainer", Func: engineGetScriptContainer, Price: 250},
{Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 250,
AllowedTriggers: trigger.Application},
{Name: "System.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 250},
{Name: "System.Runtime.Log", Func: runtimeLog, Price: 1000000, RequiredFlags: smartcontract.AllowNotify},
{Name: "System.Runtime.Notify", Func: runtimeNotify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify},
{Name: "System.Runtime.Platform", Func: runtimePlatform, Price: 250},
{Name: "System.Storage.Delete", Func: storageDelete, Price: StoragePrice,
AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowModifyStates},
{Name: "System.Storage.Find", Func: storageFind, Price: 1000000,

View file

@ -32,3 +32,15 @@ func Update(script []byte, manifest []byte) Contract {
// not by any outside code. When contract is deleted all associated storage
// items are deleted too. This function uses `System.Contract.Destroy` syscall.
func Destroy() {}
// IsStandard checks if contract with provided hash is a standard signature/multisig contract.
// This function uses `System.Contract.IsStandard` syscall.
func IsStandard(h []byte) bool {
return false
}
// CreateStandardAccount calculates script hash of a given public key.
// This function uses `System.Contract.CreateStandardAccount` syscall.
func CreateStandardAccount(pub []byte) []byte {
return nil
}

View file

@ -1,47 +1,10 @@
/*
Package engine provides access to VM execution metadata and allows to make contract calls.
Package engine allows to make contract calls.
It's roughly similar in function to ExecutionEngine class in the Neo .net
framework.
*/
package engine
import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
// GetScriptContainer returns the transaction that initially triggered current
// execution context. It never changes in a single execution, no matter how deep
// this execution goes. This function uses
// `System.ExecutionEngine.GetScriptContainer` syscall.
func GetScriptContainer() blockchain.Transaction {
return blockchain.Transaction{}
}
// GetExecutingScriptHash returns script hash (160 bit in BE form represented
// as 20-byte slice) of the contract that is currently being executed. Any
// AppCall can change the value returned by this function if it calls a
// different contract. This function uses
// `System.ExecutionEngine.GetExecutingScriptHash` syscall.
func GetExecutingScriptHash() []byte {
return nil
}
// GetCallingScriptHash returns script hash (160 bit in BE form represented
// as 20-byte slice) of the contract that started the execution of the currently
// running context (caller of current contract or function), so it's one level
// above the GetExecutingScriptHash in the call stack. It uses
// `System.ExecutionEngine.GetCallingScriptHash` syscall.
func GetCallingScriptHash() []byte {
return nil
}
// GetEntryScriptHash returns script hash (160 bit in BE form represented
// as 20-byte slice) of the contract that initially started current execution
// (this is a script that is contained in a transaction returned by
// GetScriptContainer) execution from the start. This function uses
// `System.ExecutionEngine.GetEntryScriptHash` syscall.
func GetEntryScriptHash() []byte {
return nil
}
// AppCall executes previously deployed blockchain contract with specified hash
// (160 bit in BE form represented as 20-byte slice) using provided arguments.
// It returns whatever this contract returns. Even though this function accepts

View file

@ -0,0 +1,38 @@
package runtime
import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
// GetScriptContainer returns the transaction that initially triggered current
// execution context. It never changes in a single execution, no matter how deep
// this execution goes. This function uses
// `System.Runtime.GetScriptContainer` syscall.
func GetScriptContainer() blockchain.Transaction {
return blockchain.Transaction{}
}
// GetExecutingScriptHash returns script hash (160 bit in BE form represented
// as 20-byte slice) of the contract that is currently being executed. Any
// AppCall can change the value returned by this function if it calls a
// different contract. This function uses
// `System.Runtime.GetExecutingScriptHash` syscall.
func GetExecutingScriptHash() []byte {
return nil
}
// GetCallingScriptHash returns script hash (160 bit in BE form represented
// as 20-byte slice) of the contract that started the execution of the currently
// running context (caller of current contract or function), so it's one level
// above the GetExecutingScriptHash in the call stack. It uses
// `System.Runtime.GetCallingScriptHash` syscall.
func GetCallingScriptHash() []byte {
return nil
}
// GetEntryScriptHash returns script hash (160 bit in BE form represented
// as 20-byte slice) of the contract that initially started current execution
// (this is a script that is contained in a transaction returned by
// GetScriptContainer) execution from the start. This function uses
// `System.Runtime.GetEntryScriptHash` syscall.
func GetEntryScriptHash() []byte {
return nil
}

View file

@ -54,3 +54,23 @@ func Application() byte {
func Verification() byte {
return 0x00
}
// GasLeft returns the amount of gas available for the current execution.
// This function uses `System.Runtime.GasLeft` syscall.
func GasLeft() int64 {
return 0
}
// GetNotifications returns notifications emitted by contract h.
// 'nil' literal means no filtering. It returns slice consisting of following elements:
// [ scripthash of notification's contract , emitted item ].
// This function uses `System.Runtime.GetNotifications` syscall.
func GetNotifications(h []byte) [][]interface{} {
return nil
}
// GetInvocationCounter returns how many times current contract was invoked during current tx execution.
// This function uses `System.Runtime.GetInvocationCounter` syscall.
func GetInvocationCounter() int {
return 0
}

View file

@ -906,7 +906,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
// result.
func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *result.Invoke {
vm := s.chain.GetTestVM(tx)
vm.SetGasLimit(s.config.MaxGasInvoke)
vm.GasLimit = s.config.MaxGasInvoke
vm.LoadScriptWithFlags(script, smartcontract.All)
_ = vm.Run()
result := &result.Invoke{

View file

@ -50,18 +50,18 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{})
}
const testContractHash = "279c86355b30d452386c1717e2ffa01c9dac31f3"
const testContractHash = "e65ff7b3a02d207b584a5c27057d4e9862ef01da"
var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": {
{
name: "positive",
params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`,
params: `["9352fa8d351635bb151e7e5a3a923bfe4d8fb90f05b605ec00af95c2410b594d"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20")
expectedTxHash, err := util.Uint256DecodeStringLE("9352fa8d351635bb151e7e5a3a923bfe4d8fb90f05b605ec00af95c2410b594d")
require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions))
@ -483,7 +483,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"gettransactionheight": {
{
name: "positive",
params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`,
params: `["9352fa8d351635bb151e7e5a3a923bfe4d8fb90f05b605ec00af95c2410b594d"]`,
result: func(e *executor) interface{} {
h := 0
return &h

Binary file not shown.

View file

@ -1,7 +1,6 @@
package testdata
import (
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
@ -71,7 +70,7 @@ func Main(operation string, args []interface{}) interface{} {
return true
case "init":
ctx := storage.GetContext()
h := engine.GetExecutingScriptHash()
h := runtime.GetExecutingScriptHash()
amount := totalSupply
storage.Put(ctx, h, amount)
runtime.Notify("transfer", []byte{}, h, amount)

Binary file not shown.

View file

@ -78,7 +78,7 @@ type VM struct {
refs *refCounter
gasConsumed util.Fixed8
gasLimit util.Fixed8
GasLimit util.Fixed8
trigger trigger.Type
@ -134,16 +134,10 @@ func (v *VM) GasConsumed() util.Fixed8 {
return v.gasConsumed
}
// SetGasLimit sets maximum amount of gas which v can spent.
// If max <= 0, no limit is imposed.
func (v *VM) SetGasLimit(max util.Fixed8) {
v.gasLimit = max
}
// AddGas consumes specified amount of gas. It returns true iff gas limit wasn't exceeded.
func (v *VM) AddGas(gas util.Fixed8) bool {
v.gasConsumed += gas
return v.gasLimit == 0 || v.gasConsumed <= v.gasLimit
return v.GasLimit == 0 || v.gasConsumed <= v.GasLimit
}
// Estack returns the evaluation stack so interop hooks can utilize this.
@ -520,7 +514,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if v.getPrice != nil && ctx.ip < len(ctx.prog) {
v.gasConsumed += v.getPrice(v, op, parameter)
if v.gasLimit > 0 && v.gasConsumed > v.gasLimit {
if v.GasLimit > 0 && v.gasConsumed > v.GasLimit {
panic("gas limit is exceeded")
}
}

View file

@ -90,7 +90,7 @@ func TestVM_SetPriceGetter(t *testing.T) {
t.Run("with sufficient gas limit", func(t *testing.T) {
v.Load(prog)
v.SetGasLimit(9)
v.GasLimit = 9
runVM(t, v)
require.EqualValues(t, 9, v.GasConsumed())
@ -98,14 +98,14 @@ func TestVM_SetPriceGetter(t *testing.T) {
t.Run("with small gas limit", func(t *testing.T) {
v.Load(prog)
v.SetGasLimit(8)
v.GasLimit = 8
checkVMFailed(t, v)
})
}
func TestAddGas(t *testing.T) {
v := New()
v.SetGasLimit(10)
v.GasLimit = 10
require.True(t, v.AddGas(5))
require.True(t, v.AddGas(5))
require.False(t, v.AddGas(5))