vm/core: add ID support for SYSCALL, redo interop registration

This solves two problems:
 * adds support for shortened SYSCALL form that uses IDs (similar to #434, but
   for NEO 2.0, supporting both forms), which is important for compatibility
   with C# node and mainnet chain that uses it from some height
 * reworks interop plugging to use callbacks rather than appending to the map,
   these map mangling functions are clearly visible in the VM profiling
   statistics and we want spawning a VM to be fast, so it makes sense
   optimizing it. This change moves most of the work to the init() phase
   making VM setup cheaper.

Caveats:
 * InteropNameToID accepts `[]byte` because that's the thing we have in
   SYSCALL processing and that's the most often usecase for it, it leads to
   some conversions in other places but that's acceptable because those are
   either tests or init()
 * three getInterop functions are: `getDefaultVMInterop`, `getSystemInterop`
   and `getNeoInterop`

Our 100K (1.4M->1.5M) block import time improves by ~4% with this change.
This commit is contained in:
Roman Khimov 2019-12-18 19:49:56 +03:00
parent 1a26548be8
commit a7457d08a1
7 changed files with 384 additions and 247 deletions

View file

@ -1342,8 +1342,8 @@ func (bc *Blockchain) spawnVMWithInterops(interopCtx *interopContext) *vm.VM {
}
return cs.Script
})
vm.RegisterInteropFuncs(interopCtx.getSystemInteropMap())
vm.RegisterInteropFuncs(interopCtx.getNeoInteropMap())
vm.RegisterInteropGetter(interopCtx.getSystemInterop)
vm.RegisterInteropGetter(interopCtx.getNeoInterop)
return vm
}

View file

@ -8,6 +8,8 @@ package core
*/
import (
"sort"
"github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
@ -29,193 +31,240 @@ func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Bl
return &interopContext{bc, trigger, block, tx, dao, nes}
}
// interopedFunction binds function name, id with the function itself and price,
// it's supposed to be inited once for all interopContexts, so it doesn't use
// vm.InteropFuncPrice directly.
type interopedFunction struct {
ID uint32
Name string
Func func(*interopContext, *vm.VM) error
Price int
}
// getSystemInterop returns matching interop function from the System namespace
// for a given id in the current context.
func (ic *interopContext) getSystemInterop(id uint32) *vm.InteropFuncPrice {
return ic.getInteropFromSlice(id, systemInterops)
}
// getNeoInterop returns matching interop function from the Neo and AntShares
// namespaces for a given id in the current context.
func (ic *interopContext) getNeoInterop(id uint32) *vm.InteropFuncPrice {
return ic.getInteropFromSlice(id, neoInterops)
}
// getInteropFromSlice returns matching interop function from the given slice of
// interop functions in the current context.
func (ic *interopContext) getInteropFromSlice(id uint32, slice []interopedFunction) *vm.InteropFuncPrice {
n := sort.Search(len(slice), func(i int) bool {
return slice[i].ID >= id
})
if n < len(slice) && slice[n].ID == id {
// Currying, yay!
return &vm.InteropFuncPrice{Func: func(v *vm.VM) error {
return slice[n].Func(ic, v)
}, Price: slice[n].Price}
}
return nil
}
// All lists are sorted, keep 'em this way, please.
// getSystemInteropMap returns interop mappings for System namespace.
func (ic *interopContext) getSystemInteropMap() map[string]vm.InteropFuncPrice {
return map[string]vm.InteropFuncPrice{
"System.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1},
"System.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1},
"System.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1},
"System.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200},
"System.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100},
"System.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100},
"System.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
"System.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 200},
"System.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100},
"System.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
"System.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1},
"System.ExecutionEngine.GetCallingScriptHash": {Func: ic.engineGetCallingScriptHash, Price: 1},
"System.ExecutionEngine.GetEntryScriptHash": {Func: ic.engineGetEntryScriptHash, Price: 1},
"System.ExecutionEngine.GetExecutingScriptHash": {Func: ic.engineGetExecutingScriptHash, Price: 1},
"System.ExecutionEngine.GetScriptContainer": {Func: ic.engineGetScriptContainer, Price: 1},
"System.Header.GetHash": {Func: ic.headerGetHash, Price: 1},
"System.Header.GetIndex": {Func: ic.headerGetIndex, Price: 1},
"System.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1},
"System.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1},
"System.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200},
"System.Runtime.GetTime": {Func: ic.runtimeGetTime, Price: 1},
"System.Runtime.GetTrigger": {Func: ic.runtimeGetTrigger, Price: 1},
"System.Runtime.Log": {Func: ic.runtimeLog, Price: 1},
"System.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1},
"System.Runtime.Platform": {Func: ic.runtimePlatform, Price: 1},
"System.Storage.Delete": {Func: ic.storageDelete, Price: 100},
"System.Storage.Get": {Func: ic.storageGet, Price: 100},
"System.Storage.GetContext": {Func: ic.storageGetContext, Price: 1},
"System.Storage.GetReadOnlyContext": {Func: ic.storageGetReadOnlyContext, Price: 1},
"System.Storage.Put": {Func: ic.storagePut, Price: 0}, // These don't have static price in C# code.
"System.Storage.PutEx": {Func: ic.storagePutEx, Price: 0},
"System.StorageContext.AsReadOnly": {Func: ic.storageContextAsReadOnly, Price: 1},
"System.Transaction.GetHash": {Func: ic.txGetHash, Price: 1},
"System.Runtime.Deserialize": {Func: ic.runtimeDeserialize, Price: 1},
"System.Runtime.Serialize": {Func: ic.runtimeSerialize, Price: 1},
}
var systemInterops = []interopedFunction{
{Name: "System.Block.GetTransaction", Func: (*interopContext).blockGetTransaction, Price: 1},
{Name: "System.Block.GetTransactionCount", Func: (*interopContext).blockGetTransactionCount, Price: 1},
{Name: "System.Block.GetTransactions", Func: (*interopContext).blockGetTransactions, Price: 1},
{Name: "System.Blockchain.GetBlock", Func: (*interopContext).bcGetBlock, Price: 200},
{Name: "System.Blockchain.GetContract", Func: (*interopContext).bcGetContract, Price: 100},
{Name: "System.Blockchain.GetHeader", Func: (*interopContext).bcGetHeader, Price: 100},
{Name: "System.Blockchain.GetHeight", Func: (*interopContext).bcGetHeight, Price: 1},
{Name: "System.Blockchain.GetTransaction", Func: (*interopContext).bcGetTransaction, Price: 200},
{Name: "System.Blockchain.GetTransactionHeight", Func: (*interopContext).bcGetTransactionHeight, Price: 100},
{Name: "System.Contract.Destroy", Func: (*interopContext).contractDestroy, Price: 1},
{Name: "System.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
{Name: "System.ExecutionEngine.GetCallingScriptHash", Func: (*interopContext).engineGetCallingScriptHash, Price: 1},
{Name: "System.ExecutionEngine.GetEntryScriptHash", Func: (*interopContext).engineGetEntryScriptHash, Price: 1},
{Name: "System.ExecutionEngine.GetExecutingScriptHash", Func: (*interopContext).engineGetExecutingScriptHash, Price: 1},
{Name: "System.ExecutionEngine.GetScriptContainer", Func: (*interopContext).engineGetScriptContainer, Price: 1},
{Name: "System.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
{Name: "System.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1},
{Name: "System.Header.GetPrevHash", Func: (*interopContext).headerGetPrevHash, Price: 1},
{Name: "System.Header.GetTimestamp", Func: (*interopContext).headerGetTimestamp, Price: 1},
{Name: "System.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
{Name: "System.Runtime.Deserialize", Func: (*interopContext).runtimeDeserialize, Price: 1},
{Name: "System.Runtime.GetTime", Func: (*interopContext).runtimeGetTime, Price: 1},
{Name: "System.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1},
{Name: "System.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
{Name: "System.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1},
{Name: "System.Runtime.Platform", Func: (*interopContext).runtimePlatform, Price: 1},
{Name: "System.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1},
{Name: "System.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100},
{Name: "System.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
{Name: "System.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
{Name: "System.Storage.GetReadOnlyContext", Func: (*interopContext).storageGetReadOnlyContext, Price: 1},
{Name: "System.Storage.Put", Func: (*interopContext).storagePut, Price: 0}, // These don't have static price in C# code.
{Name: "System.Storage.PutEx", Func: (*interopContext).storagePutEx, Price: 0},
{Name: "System.StorageContext.AsReadOnly", Func: (*interopContext).storageContextAsReadOnly, Price: 1},
{Name: "System.Transaction.GetHash", Func: (*interopContext).txGetHash, Price: 1},
}
// getSystemInteropMap returns interop mappings for Neo and (legacy) AntShares namespaces.
func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice {
return map[string]vm.InteropFuncPrice{
"Neo.Account.GetBalance": {Func: ic.accountGetBalance, Price: 1},
"Neo.Account.GetScriptHash": {Func: ic.accountGetScriptHash, Price: 1},
"Neo.Account.GetVotes": {Func: ic.accountGetVotes, Price: 1},
"Neo.Account.IsStandard": {Func: ic.accountIsStandard, Price: 100},
"Neo.Asset.Create": {Func: ic.assetCreate, Price: 0},
"Neo.Asset.GetAdmin": {Func: ic.assetGetAdmin, Price: 1},
"Neo.Asset.GetAmount": {Func: ic.assetGetAmount, Price: 1},
"Neo.Asset.GetAssetId": {Func: ic.assetGetAssetID, Price: 1},
"Neo.Asset.GetAssetType": {Func: ic.assetGetAssetType, Price: 1},
"Neo.Asset.GetAvailable": {Func: ic.assetGetAvailable, Price: 1},
"Neo.Asset.GetIssuer": {Func: ic.assetGetIssuer, Price: 1},
"Neo.Asset.GetOwner": {Func: ic.assetGetOwner, Price: 1},
"Neo.Asset.GetPrecision": {Func: ic.assetGetPrecision, Price: 1},
"Neo.Asset.Renew": {Func: ic.assetRenew, Price: 0},
"Neo.Attribute.GetData": {Func: ic.attrGetData, Price: 1},
"Neo.Attribute.GetUsage": {Func: ic.attrGetUsage, Price: 1},
"Neo.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1},
"Neo.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1},
"Neo.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1},
"Neo.Blockchain.GetAccount": {Func: ic.bcGetAccount, Price: 100},
"Neo.Blockchain.GetAsset": {Func: ic.bcGetAsset, Price: 100},
"Neo.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200},
"Neo.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100},
"Neo.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100},
"Neo.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
"Neo.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
"Neo.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100},
"Neo.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200},
"Neo.Contract.Create": {Func: ic.contractCreate, Price: 0},
"Neo.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
"Neo.Contract.GetScript": {Func: ic.contractGetScript, Price: 1},
"Neo.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1},
"Neo.Contract.IsPayable": {Func: ic.contractIsPayable, Price: 1},
"Neo.Contract.Migrate": {Func: ic.contractMigrate, Price: 0},
"Neo.Header.GetConsensusData": {Func: ic.headerGetConsensusData, Price: 1},
"Neo.Header.GetHash": {Func: ic.headerGetHash, Price: 1},
"Neo.Header.GetIndex": {Func: ic.headerGetIndex, Price: 1},
"Neo.Header.GetMerkleRoot": {Func: ic.headerGetMerkleRoot, Price: 1},
"Neo.Header.GetNextConsensus": {Func: ic.headerGetNextConsensus, Price: 1},
"Neo.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1},
"Neo.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1},
"Neo.Header.GetVersion": {Func: ic.headerGetVersion, Price: 1},
"Neo.Input.GetHash": {Func: ic.inputGetHash, Price: 1},
"Neo.Input.GetIndex": {Func: ic.inputGetIndex, Price: 1},
"Neo.Output.GetAssetId": {Func: ic.outputGetAssetID, Price: 1},
"Neo.Output.GetScriptHash": {Func: ic.outputGetScriptHash, Price: 1},
"Neo.Output.GetValue": {Func: ic.outputGetValue, Price: 1},
"Neo.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200},
"Neo.Runtime.GetTime": {Func: ic.runtimeGetTime, Price: 1},
"Neo.Runtime.GetTrigger": {Func: ic.runtimeGetTrigger, Price: 1},
"Neo.Runtime.Log": {Func: ic.runtimeLog, Price: 1},
"Neo.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1},
"Neo.Storage.Delete": {Func: ic.storageDelete, Price: 100},
"Neo.Storage.Get": {Func: ic.storageGet, Price: 100},
"Neo.Storage.GetContext": {Func: ic.storageGetContext, Price: 1},
"Neo.Storage.GetReadOnlyContext": {Func: ic.storageGetReadOnlyContext, Price: 1},
"Neo.Storage.Put": {Func: ic.storagePut, Price: 0},
"Neo.StorageContext.AsReadOnly": {Func: ic.storageContextAsReadOnly, Price: 1},
"Neo.Transaction.GetAttributes": {Func: ic.txGetAttributes, Price: 1},
"Neo.Transaction.GetHash": {Func: ic.txGetHash, Price: 1},
"Neo.Transaction.GetInputs": {Func: ic.txGetInputs, Price: 1},
"Neo.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1},
"Neo.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200},
"Neo.Transaction.GetType": {Func: ic.txGetType, Price: 1},
"Neo.Transaction.GetUnspentCoins": {Func: ic.txGetUnspentCoins, Price: 200},
"Neo.Transaction.GetWitnesses": {Func: ic.txGetWitnesses, Price: 200},
// "Neo.Enumerator.Concat": {Func: ic.enumeratorConcat, Price: 1},
// "Neo.Enumerator.Create": {Func: ic.enumeratorCreate, Price: 1},
// "Neo.Enumerator.Next": {Func: ic.enumeratorNext, Price: 1},
// "Neo.Enumerator.Value": {Func: ic.enumeratorValue, Price: 1},
// "Neo.InvocationTransaction.GetScript": {ic.invocationTx_GetScript, 1},
// "Neo.Iterator.Concat": {Func: ic.iteratorConcat, Price: 1},
// "Neo.Iterator.Create": {Func: ic.iteratorCreate, Price: 1},
// "Neo.Iterator.Key": {Func: ic.iteratorKey, Price: 1},
// "Neo.Iterator.Keys": {Func: ic.iteratorKeys, Price: 1},
// "Neo.Iterator.Values": {Func: ic.iteratorValues, Price: 1},
"Neo.Runtime.Deserialize": {Func: ic.runtimeDeserialize, Price: 1},
"Neo.Runtime.Serialize": {Func: ic.runtimeSerialize, Price: 1},
// "Neo.Storage.Find": {Func: ic.storageFind, Price: 1},
// "Neo.Witness.GetVerificationScript": {Func: ic.witnessGetVerificationScript, Price: 100},
var neoInterops = []interopedFunction{
{Name: "Neo.Account.GetBalance", Func: (*interopContext).accountGetBalance, Price: 1},
{Name: "Neo.Account.GetScriptHash", Func: (*interopContext).accountGetScriptHash, Price: 1},
{Name: "Neo.Account.GetVotes", Func: (*interopContext).accountGetVotes, Price: 1},
{Name: "Neo.Account.IsStandard", Func: (*interopContext).accountIsStandard, Price: 100},
{Name: "Neo.Asset.Create", Func: (*interopContext).assetCreate, Price: 0},
{Name: "Neo.Asset.GetAdmin", Func: (*interopContext).assetGetAdmin, Price: 1},
{Name: "Neo.Asset.GetAmount", Func: (*interopContext).assetGetAmount, Price: 1},
{Name: "Neo.Asset.GetAssetId", Func: (*interopContext).assetGetAssetID, Price: 1},
{Name: "Neo.Asset.GetAssetType", Func: (*interopContext).assetGetAssetType, Price: 1},
{Name: "Neo.Asset.GetAvailable", Func: (*interopContext).assetGetAvailable, Price: 1},
{Name: "Neo.Asset.GetIssuer", Func: (*interopContext).assetGetIssuer, Price: 1},
{Name: "Neo.Asset.GetOwner", Func: (*interopContext).assetGetOwner, Price: 1},
{Name: "Neo.Asset.GetPrecision", Func: (*interopContext).assetGetPrecision, Price: 1},
{Name: "Neo.Asset.Renew", Func: (*interopContext).assetRenew, Price: 0},
{Name: "Neo.Attribute.GetData", Func: (*interopContext).attrGetData, Price: 1},
{Name: "Neo.Attribute.GetUsage", Func: (*interopContext).attrGetUsage, Price: 1},
{Name: "Neo.Block.GetTransaction", Func: (*interopContext).blockGetTransaction, Price: 1},
{Name: "Neo.Block.GetTransactionCount", Func: (*interopContext).blockGetTransactionCount, Price: 1},
{Name: "Neo.Block.GetTransactions", Func: (*interopContext).blockGetTransactions, Price: 1},
{Name: "Neo.Blockchain.GetAccount", Func: (*interopContext).bcGetAccount, Price: 100},
{Name: "Neo.Blockchain.GetAsset", Func: (*interopContext).bcGetAsset, Price: 100},
{Name: "Neo.Blockchain.GetBlock", Func: (*interopContext).bcGetBlock, Price: 200},
{Name: "Neo.Blockchain.GetContract", Func: (*interopContext).bcGetContract, Price: 100},
{Name: "Neo.Blockchain.GetHeader", Func: (*interopContext).bcGetHeader, Price: 100},
{Name: "Neo.Blockchain.GetHeight", Func: (*interopContext).bcGetHeight, Price: 1},
{Name: "Neo.Blockchain.GetTransaction", Func: (*interopContext).bcGetTransaction, Price: 100},
{Name: "Neo.Blockchain.GetTransactionHeight", Func: (*interopContext).bcGetTransactionHeight, Price: 100},
{Name: "Neo.Blockchain.GetValidators", Func: (*interopContext).bcGetValidators, Price: 200},
{Name: "Neo.Contract.Create", Func: (*interopContext).contractCreate, Price: 0},
{Name: "Neo.Contract.Destroy", Func: (*interopContext).contractDestroy, Price: 1},
{Name: "Neo.Contract.GetScript", Func: (*interopContext).contractGetScript, Price: 1},
{Name: "Neo.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
{Name: "Neo.Contract.IsPayable", Func: (*interopContext).contractIsPayable, Price: 1},
{Name: "Neo.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0},
{Name: "Neo.Header.GetConsensusData", Func: (*interopContext).headerGetConsensusData, Price: 1},
{Name: "Neo.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
{Name: "Neo.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1},
{Name: "Neo.Header.GetMerkleRoot", Func: (*interopContext).headerGetMerkleRoot, Price: 1},
{Name: "Neo.Header.GetNextConsensus", Func: (*interopContext).headerGetNextConsensus, Price: 1},
{Name: "Neo.Header.GetPrevHash", Func: (*interopContext).headerGetPrevHash, Price: 1},
{Name: "Neo.Header.GetTimestamp", Func: (*interopContext).headerGetTimestamp, Price: 1},
{Name: "Neo.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1},
{Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
{Name: "Neo.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1},
{Name: "Neo.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
{Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
{Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
{Name: "Neo.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
{Name: "Neo.Runtime.GetTime", Func: (*interopContext).runtimeGetTime, Price: 1},
{Name: "Neo.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1},
{Name: "Neo.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
{Name: "Neo.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1},
{Name: "Neo.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100},
{Name: "Neo.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
{Name: "Neo.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
{Name: "Neo.Storage.GetReadOnlyContext", Func: (*interopContext).storageGetReadOnlyContext, Price: 1},
{Name: "Neo.Storage.Put", Func: (*interopContext).storagePut, Price: 0},
{Name: "Neo.StorageContext.AsReadOnly", Func: (*interopContext).storageContextAsReadOnly, Price: 1},
{Name: "Neo.Transaction.GetAttributes", Func: (*interopContext).txGetAttributes, Price: 1},
{Name: "Neo.Transaction.GetHash", Func: (*interopContext).txGetHash, Price: 1},
{Name: "Neo.Transaction.GetInputs", Func: (*interopContext).txGetInputs, Price: 1},
{Name: "Neo.Transaction.GetOutputs", Func: (*interopContext).txGetOutputs, Price: 1},
{Name: "Neo.Transaction.GetReferences", Func: (*interopContext).txGetReferences, Price: 200},
{Name: "Neo.Transaction.GetType", Func: (*interopContext).txGetType, Price: 1},
{Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200},
{Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200},
// {Name: "Neo.Enumerator.Concat", Func: (*interopContext).enumeratorConcat, Price: 1},
// {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1},
// {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
// {Name: "Neo.Enumerator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
// {Name: "Neo.InvocationTransaction.GetScript", {ic.invocationTx_GetScript, 1},
// {Name: "Neo.Iterator.Concat", Func: (*interopContext).iteratorConcat, Price: 1},
// {Name: "Neo.Iterator.Create", Func: (*interopContext).iteratorCreate, Price: 1},
// {Name: "Neo.Iterator.Key", Func: (*interopContext).iteratorKey, Price: 1},
// {Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1},
// {Name: "Neo.Iterator.Values", Func: (*interopContext).iteratorValues, Price: 1},
{Name: "Neo.Runtime.Deserialize", Func: (*interopContext).runtimeDeserialize, Price: 1},
{Name: "Neo.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1},
// {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1},
// {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100},
// Aliases.
// "Neo.Iterator.Next": {Func: ic.enumeratorNext, Price: 1},
// "Neo.Iterator.Value": {Func: ic.enumeratorValue, Price: 1},
// {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
// {Name: "Neo.Iterator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
// Old compatibility APIs.
"AntShares.Account.GetBalance": {Func: ic.accountGetBalance, Price: 1},
"AntShares.Account.GetScriptHash": {Func: ic.accountGetScriptHash, Price: 1},
"AntShares.Account.GetVotes": {Func: ic.accountGetVotes, Price: 1},
"AntShares.Asset.Create": {Func: ic.assetCreate, Price: 0},
"AntShares.Asset.GetAdmin": {Func: ic.assetGetAdmin, Price: 1},
"AntShares.Asset.GetAmount": {Func: ic.assetGetAmount, Price: 1},
"AntShares.Asset.GetAssetId": {Func: ic.assetGetAssetID, Price: 1},
"AntShares.Asset.GetAssetType": {Func: ic.assetGetAssetType, Price: 1},
"AntShares.Asset.GetAvailable": {Func: ic.assetGetAvailable, Price: 1},
"AntShares.Asset.GetIssuer": {Func: ic.assetGetIssuer, Price: 1},
"AntShares.Asset.GetOwner": {Func: ic.assetGetOwner, Price: 1},
"AntShares.Asset.GetPrecision": {Func: ic.assetGetPrecision, Price: 1},
"AntShares.Asset.Renew": {Func: ic.assetRenew, Price: 0},
"AntShares.Attribute.GetData": {Func: ic.attrGetData, Price: 1},
"AntShares.Attribute.GetUsage": {Func: ic.attrGetUsage, Price: 1},
"AntShares.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1},
"AntShares.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1},
"AntShares.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1},
"AntShares.Blockchain.GetAccount": {Func: ic.bcGetAccount, Price: 100},
"AntShares.Blockchain.GetAsset": {Func: ic.bcGetAsset, Price: 100},
"AntShares.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200},
"AntShares.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100},
"AntShares.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100},
"AntShares.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
"AntShares.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
"AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200},
"AntShares.Contract.Create": {Func: ic.contractCreate, Price: 0},
"AntShares.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
"AntShares.Contract.GetScript": {Func: ic.contractGetScript, Price: 1},
"AntShares.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1},
"AntShares.Contract.Migrate": {Func: ic.contractMigrate, Price: 0},
"AntShares.Header.GetConsensusData": {Func: ic.headerGetConsensusData, Price: 1},
"AntShares.Header.GetHash": {Func: ic.headerGetHash, Price: 1},
"AntShares.Header.GetMerkleRoot": {Func: ic.headerGetMerkleRoot, Price: 1},
"AntShares.Header.GetNextConsensus": {Func: ic.headerGetNextConsensus, Price: 1},
"AntShares.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1},
"AntShares.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1},
"AntShares.Header.GetVersion": {Func: ic.headerGetVersion, Price: 1},
"AntShares.Input.GetHash": {Func: ic.inputGetHash, Price: 1},
"AntShares.Input.GetIndex": {Func: ic.inputGetIndex, Price: 1},
"AntShares.Output.GetAssetId": {Func: ic.outputGetAssetID, Price: 1},
"AntShares.Output.GetScriptHash": {Func: ic.outputGetScriptHash, Price: 1},
"AntShares.Output.GetValue": {Func: ic.outputGetValue, Price: 1},
"AntShares.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200},
"AntShares.Runtime.Log": {Func: ic.runtimeLog, Price: 1},
"AntShares.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1},
"AntShares.Storage.Delete": {Func: ic.storageDelete, Price: 100},
"AntShares.Storage.Get": {Func: ic.storageGet, Price: 100},
"AntShares.Storage.GetContext": {Func: ic.storageGetContext, Price: 1},
"AntShares.Storage.Put": {Func: ic.storagePut, Price: 0},
"AntShares.Transaction.GetAttributes": {Func: ic.txGetAttributes, Price: 1},
"AntShares.Transaction.GetHash": {Func: ic.txGetHash, Price: 1},
"AntShares.Transaction.GetInputs": {Func: ic.txGetInputs, Price: 1},
"AntShares.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1},
"AntShares.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200},
"AntShares.Transaction.GetType": {Func: ic.txGetType, Price: 1},
{Name: "AntShares.Account.GetBalance", Func: (*interopContext).accountGetBalance, Price: 1},
{Name: "AntShares.Account.GetScriptHash", Func: (*interopContext).accountGetScriptHash, Price: 1},
{Name: "AntShares.Account.GetVotes", Func: (*interopContext).accountGetVotes, Price: 1},
{Name: "AntShares.Asset.Create", Func: (*interopContext).assetCreate, Price: 0},
{Name: "AntShares.Asset.GetAdmin", Func: (*interopContext).assetGetAdmin, Price: 1},
{Name: "AntShares.Asset.GetAmount", Func: (*interopContext).assetGetAmount, Price: 1},
{Name: "AntShares.Asset.GetAssetId", Func: (*interopContext).assetGetAssetID, Price: 1},
{Name: "AntShares.Asset.GetAssetType", Func: (*interopContext).assetGetAssetType, Price: 1},
{Name: "AntShares.Asset.GetAvailable", Func: (*interopContext).assetGetAvailable, Price: 1},
{Name: "AntShares.Asset.GetIssuer", Func: (*interopContext).assetGetIssuer, Price: 1},
{Name: "AntShares.Asset.GetOwner", Func: (*interopContext).assetGetOwner, Price: 1},
{Name: "AntShares.Asset.GetPrecision", Func: (*interopContext).assetGetPrecision, Price: 1},
{Name: "AntShares.Asset.Renew", Func: (*interopContext).assetRenew, Price: 0},
{Name: "AntShares.Attribute.GetData", Func: (*interopContext).attrGetData, Price: 1},
{Name: "AntShares.Attribute.GetUsage", Func: (*interopContext).attrGetUsage, Price: 1},
{Name: "AntShares.Block.GetTransaction", Func: (*interopContext).blockGetTransaction, Price: 1},
{Name: "AntShares.Block.GetTransactionCount", Func: (*interopContext).blockGetTransactionCount, Price: 1},
{Name: "AntShares.Block.GetTransactions", Func: (*interopContext).blockGetTransactions, Price: 1},
{Name: "AntShares.Blockchain.GetAccount", Func: (*interopContext).bcGetAccount, Price: 100},
{Name: "AntShares.Blockchain.GetAsset", Func: (*interopContext).bcGetAsset, Price: 100},
{Name: "AntShares.Blockchain.GetBlock", Func: (*interopContext).bcGetBlock, Price: 200},
{Name: "AntShares.Blockchain.GetContract", Func: (*interopContext).bcGetContract, Price: 100},
{Name: "AntShares.Blockchain.GetHeader", Func: (*interopContext).bcGetHeader, Price: 100},
{Name: "AntShares.Blockchain.GetHeight", Func: (*interopContext).bcGetHeight, Price: 1},
{Name: "AntShares.Blockchain.GetTransaction", Func: (*interopContext).bcGetTransaction, Price: 100},
{Name: "AntShares.Blockchain.GetValidators", Func: (*interopContext).bcGetValidators, Price: 200},
{Name: "AntShares.Contract.Create", Func: (*interopContext).contractCreate, Price: 0},
{Name: "AntShares.Contract.Destroy", Func: (*interopContext).contractDestroy, Price: 1},
{Name: "AntShares.Contract.GetScript", Func: (*interopContext).contractGetScript, Price: 1},
{Name: "AntShares.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
{Name: "AntShares.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0},
{Name: "AntShares.Header.GetConsensusData", Func: (*interopContext).headerGetConsensusData, Price: 1},
{Name: "AntShares.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
{Name: "AntShares.Header.GetMerkleRoot", Func: (*interopContext).headerGetMerkleRoot, Price: 1},
{Name: "AntShares.Header.GetNextConsensus", Func: (*interopContext).headerGetNextConsensus, Price: 1},
{Name: "AntShares.Header.GetPrevHash", Func: (*interopContext).headerGetPrevHash, Price: 1},
{Name: "AntShares.Header.GetTimestamp", Func: (*interopContext).headerGetTimestamp, Price: 1},
{Name: "AntShares.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1},
{Name: "AntShares.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
{Name: "AntShares.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1},
{Name: "AntShares.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
{Name: "AntShares.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
{Name: "AntShares.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
{Name: "AntShares.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
{Name: "AntShares.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
{Name: "AntShares.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1},
{Name: "AntShares.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100},
{Name: "AntShares.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
{Name: "AntShares.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
{Name: "AntShares.Storage.Put", Func: (*interopContext).storagePut, Price: 0},
{Name: "AntShares.Transaction.GetAttributes", Func: (*interopContext).txGetAttributes, Price: 1},
{Name: "AntShares.Transaction.GetHash", Func: (*interopContext).txGetHash, Price: 1},
{Name: "AntShares.Transaction.GetInputs", Func: (*interopContext).txGetInputs, Price: 1},
{Name: "AntShares.Transaction.GetOutputs", Func: (*interopContext).txGetOutputs, Price: 1},
{Name: "AntShares.Transaction.GetReferences", Func: (*interopContext).txGetReferences, Price: 200},
{Name: "AntShares.Transaction.GetType", Func: (*interopContext).txGetType, Price: 1},
}
// initIDinInteropsSlice initializes IDs from names in one given
// interopedFunction slice and then sorts it.
func initIDinInteropsSlice(iops []interopedFunction) {
for i := range iops {
iops[i].ID = vm.InteropNameToID([]byte(iops[i].Name))
}
sort.Slice(iops, func(i, j int) bool {
return iops[i].ID < iops[j].ID
})
}
// init initializes IDs in the global interop slices.
func init() {
initIDinInteropsSlice(systemInterops)
initIDinInteropsSlice(neoInterops)
}

View file

@ -1,13 +1,63 @@
package vm
import (
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"sort"
)
// InteropFunc allows to hook into the VM.
type InteropFunc func(vm *VM) error
// InteropFuncPrice represents an interop function with a price.
type InteropFuncPrice struct {
Func InteropFunc
Price int
}
// interopIDFuncPrice adds an ID to the InteropFuncPrice.
type interopIDFuncPrice struct {
ID uint32
InteropFuncPrice
}
// InteropGetterFunc is a function that returns an interop function-price
// structure by the given interop ID.
type InteropGetterFunc func(uint32) *InteropFuncPrice
var defaultVMInterops = []interopIDFuncPrice{
{InteropNameToID([]byte("Neo.Runtime.Log")),
InteropFuncPrice{runtimeLog, 1}},
{InteropNameToID([]byte("Neo.Runtime.Notify")),
InteropFuncPrice{runtimeNotify, 1}},
{InteropNameToID([]byte("Neo.Runtime.Serialize")),
InteropFuncPrice{RuntimeSerialize, 1}},
{InteropNameToID([]byte("System.Runtime.Serialize")),
InteropFuncPrice{RuntimeSerialize, 1}},
{InteropNameToID([]byte("Neo.Runtime.Deserialize")),
InteropFuncPrice{RuntimeDeserialize, 1}},
{InteropNameToID([]byte("System.Runtime.Deserialize")),
InteropFuncPrice{RuntimeDeserialize, 1}},
}
func getDefaultVMInterop(id uint32) *InteropFuncPrice {
n := sort.Search(len(defaultVMInterops), func(i int) bool {
return defaultVMInterops[i].ID >= id
})
if n < len(defaultVMInterops) && defaultVMInterops[n].ID == id {
return &defaultVMInterops[n].InteropFuncPrice
}
return nil
}
// InteropNameToID returns an identificator of the method based on its name.
func InteropNameToID(name []byte) uint32 {
h := sha256.Sum256(name)
return binary.LittleEndian.Uint32(h[:4])
}
// runtimeLog handles the syscall "Neo.Runtime.Log" for printing and logging stuff.
func runtimeLog(vm *VM) error {
item := vm.Estack().Pop()
@ -50,3 +100,10 @@ func RuntimeDeserialize(vm *VM) error {
return nil
}
// init sorts the global defaultVMInterops value.
func init() {
sort.Slice(defaultVMInterops, func(i, j int) bool {
return defaultVMInterops[i].ID < defaultVMInterops[j].ID
})
}

View file

@ -108,6 +108,18 @@ func TestUT(t *testing.T) {
require.Equal(t, true, testsRan, "neo-vm tests should be available (check submodules)")
}
func getTestingInterop(id uint32) *InteropFuncPrice {
// FIXME in NEO 3.0 it is []byte{0x77, 0x77, 0x77, 0x77} https://github.com/nspcc-dev/neo-go/issues/477
if id == InteropNameToID([]byte("Test.ExecutionEngine.GetScriptContainer")) ||
id == InteropNameToID([]byte("System.ExecutionEngine.GetScriptContainer")) {
return &InteropFuncPrice{InteropFunc(func(v *VM) error {
v.estack.Push(&Element{value: (*InteropItem)(nil)})
return nil
}), 0}
}
return nil
}
func testFile(t *testing.T, filename string) {
data, err := ioutil.ReadFile(filename)
if err != nil {
@ -134,16 +146,7 @@ func testFile(t *testing.T, filename string) {
// FIXME remove when NEO 3.0 https://github.com/nspcc-dev/neo-go/issues/477
vm.getScript = getScript(test.ScriptTable)
// FIXME in NEO 3.0 it is []byte{0x77, 0x77, 0x77, 0x77} https://github.com/nspcc-dev/neo-go/issues/477
vm.RegisterInteropFunc("Test.ExecutionEngine.GetScriptContainer", InteropFunc(func(v *VM) error {
v.estack.Push(&Element{value: (*InteropItem)(nil)})
return nil
}), 0)
vm.RegisterInteropFunc("System.ExecutionEngine.GetScriptContainer", InteropFunc(func(v *VM) error {
v.estack.Push(&Element{value: (*InteropItem)(nil)})
return nil
}), 0)
vm.RegisterInteropGetter(getTestingInterop)
for i := range test.Steps {
execStep(t, vm, test.Steps[i])

View file

@ -48,9 +48,7 @@ func vmAndCompile(t *testing.T, src string) *vm.VM {
vm := vm.New()
storePlugin := newStoragePlugin()
vm.RegisterInteropFunc("Neo.Storage.Get", storePlugin.Get, 1)
vm.RegisterInteropFunc("Neo.Storage.Put", storePlugin.Put, 1)
vm.RegisterInteropFunc("Neo.Storage.GetContext", storePlugin.GetContext, 1)
vm.RegisterInteropGetter(storePlugin.getInterop)
b, err := compiler.Compile(strings.NewReader(src))
if err != nil {
@ -62,12 +60,27 @@ func vmAndCompile(t *testing.T, src string) *vm.VM {
type storagePlugin struct {
mem map[string][]byte
interops map[uint32]vm.InteropFunc
}
func newStoragePlugin() *storagePlugin {
return &storagePlugin{
s := &storagePlugin{
mem: make(map[string][]byte),
interops: make(map[uint32]vm.InteropFunc),
}
s.interops[vm.InteropNameToID([]byte("Neo.Storage.Get"))] = s.Get
s.interops[vm.InteropNameToID([]byte("Neo.Storage.Put"))] = s.Put
s.interops[vm.InteropNameToID([]byte("Neo.Storage.GetContext"))] = s.GetContext
return s
}
func (s *storagePlugin) getInterop(id uint32) *vm.InteropFuncPrice {
f := s.interops[id]
if f != nil {
return &vm.InteropFuncPrice{Func: f, Price: 1}
}
return nil
}
func (s *storagePlugin) Delete(vm *vm.VM) error {

View file

@ -60,8 +60,8 @@ const (
type VM struct {
state State
// registered interop hooks.
interop map[string]InteropFuncPrice
// callbacks to get interops.
getInterop []InteropGetterFunc
// callback to get scripts.
getScript func(util.Uint160) []byte
@ -80,16 +80,10 @@ type VM struct {
keys map[string]*keys.PublicKey
}
// InteropFuncPrice represents an interop function with a price.
type InteropFuncPrice struct {
Func InteropFunc
Price int
}
// New returns a new VM object ready to load .avm bytecode scripts.
func New() *VM {
vm := &VM{
interop: make(map[string]InteropFuncPrice),
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
getScript: nil,
state: haltState,
istack: NewStack("invocation"),
@ -101,14 +95,7 @@ func New() *VM {
vm.estack = vm.newItemStack("evaluation")
vm.astack = vm.newItemStack("alt")
// Register native interop hooks.
vm.RegisterInteropFunc("Neo.Runtime.Log", runtimeLog, 1)
vm.RegisterInteropFunc("Neo.Runtime.Notify", runtimeNotify, 1)
vm.RegisterInteropFunc("Neo.Runtime.Serialize", RuntimeSerialize, 1)
vm.RegisterInteropFunc("System.Runtime.Serialize", RuntimeSerialize, 1)
vm.RegisterInteropFunc("Neo.Runtime.Deserialize", RuntimeDeserialize, 1)
vm.RegisterInteropFunc("System.Runtime.Deserialize", RuntimeDeserialize, 1)
vm.RegisterInteropGetter(getDefaultVMInterop)
return vm
}
@ -120,18 +107,11 @@ func (v *VM) newItemStack(n string) *Stack {
return s
}
// RegisterInteropFunc registers the given InteropFunc to the VM.
func (v *VM) RegisterInteropFunc(name string, f InteropFunc, price int) {
v.interop[name] = InteropFuncPrice{f, price}
}
// RegisterInteropFuncs registers all interop functions passed in a map in
// the VM. Effectively it's a batched version of RegisterInteropFunc.
func (v *VM) RegisterInteropFuncs(interops map[string]InteropFuncPrice) {
// We allow reregistration here.
for name := range interops {
v.interop[name] = interops[name]
}
// RegisterInteropGetter registers the given InteropGetterFunc into VM. There
// can be many interop getters and they're probed in LIFO order wrt their
// registration time.
func (v *VM) RegisterInteropGetter(f InteropGetterFunc) {
v.getInterop = append(v.getInterop, f)
}
// Estack returns the evaluation stack so interop hooks can utilize this.
@ -1095,9 +1075,23 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
}
case opcode.SYSCALL:
ifunc, ok := v.interop[string(parameter)]
if !ok {
panic(fmt.Sprintf("interop hook (%q) not registered", parameter))
var ifunc *InteropFuncPrice
var interopID uint32
if len(parameter) == 4 {
interopID = binary.LittleEndian.Uint32(parameter)
} else {
interopID = InteropNameToID(parameter)
}
// LIFO interpretation of callbacks.
for i := len(v.getInterop) - 1; i >= 0; i-- {
ifunc = v.getInterop[i](interopID)
if ifunc != nil {
break
}
}
if ifunc == nil {
panic(fmt.Sprintf("interop hook (%q/0x%x) not registered", parameter, interopID))
}
if err := ifunc.Func(v); err != nil {
panic(fmt.Sprintf("failed to invoke syscall: %s", err))

View file

@ -16,12 +16,19 @@ import (
"github.com/stretchr/testify/require"
)
func TestInteropHook(t *testing.T) {
v := New()
v.RegisterInteropFunc("foo", func(evm *VM) error {
func fooInteropGetter(id uint32) *InteropFuncPrice {
if id == InteropNameToID([]byte("foo")) {
return &InteropFuncPrice{func(evm *VM) error {
evm.Estack().PushVal(1)
return nil
}, 1)
}, 1}
}
return nil
}
func TestInteropHook(t *testing.T) {
v := New()
v.RegisterInteropGetter(fooInteropGetter)
buf := new(bytes.Buffer)
EmitSyscall(buf, "foo")
@ -32,13 +39,27 @@ func TestInteropHook(t *testing.T) {
assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value())
}
func TestRegisterInterop(t *testing.T) {
func TestInteropHookViaID(t *testing.T) {
v := New()
currRegistered := len(v.interop)
v.RegisterInteropFunc("foo", func(evm *VM) error { return nil }, 1)
assert.Equal(t, currRegistered+1, len(v.interop))
_, ok := v.interop["foo"]
assert.Equal(t, true, ok)
v.RegisterInteropGetter(fooInteropGetter)
buf := new(bytes.Buffer)
fooid := InteropNameToID([]byte("foo"))
var id = make([]byte, 4)
binary.LittleEndian.PutUint32(id, fooid)
_ = EmitSyscall(buf, string(id))
_ = EmitOpcode(buf, opcode.RET)
v.Load(buf.Bytes())
runVM(t, v)
assert.Equal(t, 1, v.estack.Len())
assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value())
}
func TestRegisterInteropGetter(t *testing.T) {
v := New()
currRegistered := len(v.getInterop)
v.RegisterInteropGetter(fooInteropGetter)
assert.Equal(t, currRegistered+1, len(v.getInterop))
}
func TestBytesToPublicKey(t *testing.T) {