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 return cs.Script
}) })
vm.RegisterInteropFuncs(interopCtx.getSystemInteropMap()) vm.RegisterInteropGetter(interopCtx.getSystemInterop)
vm.RegisterInteropFuncs(interopCtx.getNeoInteropMap()) vm.RegisterInteropGetter(interopCtx.getNeoInterop)
return vm return vm
} }

View file

@ -8,6 +8,8 @@ package core
*/ */
import ( import (
"sort"
"github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "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} 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. // All lists are sorted, keep 'em this way, please.
var systemInterops = []interopedFunction{
// getSystemInteropMap returns interop mappings for System namespace. {Name: "System.Block.GetTransaction", Func: (*interopContext).blockGetTransaction, Price: 1},
func (ic *interopContext) getSystemInteropMap() map[string]vm.InteropFuncPrice { {Name: "System.Block.GetTransactionCount", Func: (*interopContext).blockGetTransactionCount, Price: 1},
return map[string]vm.InteropFuncPrice{ {Name: "System.Block.GetTransactions", Func: (*interopContext).blockGetTransactions, Price: 1},
"System.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1}, {Name: "System.Blockchain.GetBlock", Func: (*interopContext).bcGetBlock, Price: 200},
"System.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1}, {Name: "System.Blockchain.GetContract", Func: (*interopContext).bcGetContract, Price: 100},
"System.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1}, {Name: "System.Blockchain.GetHeader", Func: (*interopContext).bcGetHeader, Price: 100},
"System.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200}, {Name: "System.Blockchain.GetHeight", Func: (*interopContext).bcGetHeight, Price: 1},
"System.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100}, {Name: "System.Blockchain.GetTransaction", Func: (*interopContext).bcGetTransaction, Price: 200},
"System.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100}, {Name: "System.Blockchain.GetTransactionHeight", Func: (*interopContext).bcGetTransactionHeight, Price: 100},
"System.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1}, {Name: "System.Contract.Destroy", Func: (*interopContext).contractDestroy, Price: 1},
"System.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 200}, {Name: "System.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
"System.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100}, {Name: "System.ExecutionEngine.GetCallingScriptHash", Func: (*interopContext).engineGetCallingScriptHash, Price: 1},
"System.Contract.Destroy": {Func: ic.contractDestroy, Price: 1}, {Name: "System.ExecutionEngine.GetEntryScriptHash", Func: (*interopContext).engineGetEntryScriptHash, Price: 1},
"System.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1}, {Name: "System.ExecutionEngine.GetExecutingScriptHash", Func: (*interopContext).engineGetExecutingScriptHash, Price: 1},
"System.ExecutionEngine.GetCallingScriptHash": {Func: ic.engineGetCallingScriptHash, Price: 1}, {Name: "System.ExecutionEngine.GetScriptContainer", Func: (*interopContext).engineGetScriptContainer, Price: 1},
"System.ExecutionEngine.GetEntryScriptHash": {Func: ic.engineGetEntryScriptHash, Price: 1}, {Name: "System.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
"System.ExecutionEngine.GetExecutingScriptHash": {Func: ic.engineGetExecutingScriptHash, Price: 1}, {Name: "System.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1},
"System.ExecutionEngine.GetScriptContainer": {Func: ic.engineGetScriptContainer, Price: 1}, {Name: "System.Header.GetPrevHash", Func: (*interopContext).headerGetPrevHash, Price: 1},
"System.Header.GetHash": {Func: ic.headerGetHash, Price: 1}, {Name: "System.Header.GetTimestamp", Func: (*interopContext).headerGetTimestamp, Price: 1},
"System.Header.GetIndex": {Func: ic.headerGetIndex, Price: 1}, {Name: "System.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
"System.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1}, {Name: "System.Runtime.Deserialize", Func: (*interopContext).runtimeDeserialize, Price: 1},
"System.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1}, {Name: "System.Runtime.GetTime", Func: (*interopContext).runtimeGetTime, Price: 1},
"System.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200}, {Name: "System.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1},
"System.Runtime.GetTime": {Func: ic.runtimeGetTime, Price: 1}, {Name: "System.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
"System.Runtime.GetTrigger": {Func: ic.runtimeGetTrigger, Price: 1}, {Name: "System.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1},
"System.Runtime.Log": {Func: ic.runtimeLog, Price: 1}, {Name: "System.Runtime.Platform", Func: (*interopContext).runtimePlatform, Price: 1},
"System.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1}, {Name: "System.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1},
"System.Runtime.Platform": {Func: ic.runtimePlatform, Price: 1}, {Name: "System.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100},
"System.Storage.Delete": {Func: ic.storageDelete, Price: 100}, {Name: "System.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
"System.Storage.Get": {Func: ic.storageGet, Price: 100}, {Name: "System.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
"System.Storage.GetContext": {Func: ic.storageGetContext, Price: 1}, {Name: "System.Storage.GetReadOnlyContext", Func: (*interopContext).storageGetReadOnlyContext, Price: 1},
"System.Storage.GetReadOnlyContext": {Func: ic.storageGetReadOnlyContext, Price: 1}, {Name: "System.Storage.Put", Func: (*interopContext).storagePut, Price: 0}, // These don't have static price in C# code.
"System.Storage.Put": {Func: ic.storagePut, Price: 0}, // These don't have static price in C# code. {Name: "System.Storage.PutEx", Func: (*interopContext).storagePutEx, Price: 0},
"System.Storage.PutEx": {Func: ic.storagePutEx, Price: 0}, {Name: "System.StorageContext.AsReadOnly", Func: (*interopContext).storageContextAsReadOnly, Price: 1},
"System.StorageContext.AsReadOnly": {Func: ic.storageContextAsReadOnly, Price: 1}, {Name: "System.Transaction.GetHash", Func: (*interopContext).txGetHash, 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},
}
} }
// getSystemInteropMap returns interop mappings for Neo and (legacy) AntShares namespaces. var neoInterops = []interopedFunction{
func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice { {Name: "Neo.Account.GetBalance", Func: (*interopContext).accountGetBalance, Price: 1},
return map[string]vm.InteropFuncPrice{ {Name: "Neo.Account.GetScriptHash", Func: (*interopContext).accountGetScriptHash, Price: 1},
"Neo.Account.GetBalance": {Func: ic.accountGetBalance, Price: 1}, {Name: "Neo.Account.GetVotes", Func: (*interopContext).accountGetVotes, Price: 1},
"Neo.Account.GetScriptHash": {Func: ic.accountGetScriptHash, Price: 1}, {Name: "Neo.Account.IsStandard", Func: (*interopContext).accountIsStandard, Price: 100},
"Neo.Account.GetVotes": {Func: ic.accountGetVotes, Price: 1}, {Name: "Neo.Asset.Create", Func: (*interopContext).assetCreate, Price: 0},
"Neo.Account.IsStandard": {Func: ic.accountIsStandard, Price: 100}, {Name: "Neo.Asset.GetAdmin", Func: (*interopContext).assetGetAdmin, Price: 1},
"Neo.Asset.Create": {Func: ic.assetCreate, Price: 0}, {Name: "Neo.Asset.GetAmount", Func: (*interopContext).assetGetAmount, Price: 1},
"Neo.Asset.GetAdmin": {Func: ic.assetGetAdmin, Price: 1}, {Name: "Neo.Asset.GetAssetId", Func: (*interopContext).assetGetAssetID, Price: 1},
"Neo.Asset.GetAmount": {Func: ic.assetGetAmount, Price: 1}, {Name: "Neo.Asset.GetAssetType", Func: (*interopContext).assetGetAssetType, Price: 1},
"Neo.Asset.GetAssetId": {Func: ic.assetGetAssetID, Price: 1}, {Name: "Neo.Asset.GetAvailable", Func: (*interopContext).assetGetAvailable, Price: 1},
"Neo.Asset.GetAssetType": {Func: ic.assetGetAssetType, Price: 1}, {Name: "Neo.Asset.GetIssuer", Func: (*interopContext).assetGetIssuer, Price: 1},
"Neo.Asset.GetAvailable": {Func: ic.assetGetAvailable, Price: 1}, {Name: "Neo.Asset.GetOwner", Func: (*interopContext).assetGetOwner, Price: 1},
"Neo.Asset.GetIssuer": {Func: ic.assetGetIssuer, Price: 1}, {Name: "Neo.Asset.GetPrecision", Func: (*interopContext).assetGetPrecision, Price: 1},
"Neo.Asset.GetOwner": {Func: ic.assetGetOwner, Price: 1}, {Name: "Neo.Asset.Renew", Func: (*interopContext).assetRenew, Price: 0},
"Neo.Asset.GetPrecision": {Func: ic.assetGetPrecision, Price: 1}, {Name: "Neo.Attribute.GetData", Func: (*interopContext).attrGetData, Price: 1},
"Neo.Asset.Renew": {Func: ic.assetRenew, Price: 0}, {Name: "Neo.Attribute.GetUsage", Func: (*interopContext).attrGetUsage, Price: 1},
"Neo.Attribute.GetData": {Func: ic.attrGetData, Price: 1}, {Name: "Neo.Block.GetTransaction", Func: (*interopContext).blockGetTransaction, Price: 1},
"Neo.Attribute.GetUsage": {Func: ic.attrGetUsage, Price: 1}, {Name: "Neo.Block.GetTransactionCount", Func: (*interopContext).blockGetTransactionCount, Price: 1},
"Neo.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1}, {Name: "Neo.Block.GetTransactions", Func: (*interopContext).blockGetTransactions, Price: 1},
"Neo.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1}, {Name: "Neo.Blockchain.GetAccount", Func: (*interopContext).bcGetAccount, Price: 100},
"Neo.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1}, {Name: "Neo.Blockchain.GetAsset", Func: (*interopContext).bcGetAsset, Price: 100},
"Neo.Blockchain.GetAccount": {Func: ic.bcGetAccount, Price: 100}, {Name: "Neo.Blockchain.GetBlock", Func: (*interopContext).bcGetBlock, Price: 200},
"Neo.Blockchain.GetAsset": {Func: ic.bcGetAsset, Price: 100}, {Name: "Neo.Blockchain.GetContract", Func: (*interopContext).bcGetContract, Price: 100},
"Neo.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200}, {Name: "Neo.Blockchain.GetHeader", Func: (*interopContext).bcGetHeader, Price: 100},
"Neo.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100}, {Name: "Neo.Blockchain.GetHeight", Func: (*interopContext).bcGetHeight, Price: 1},
"Neo.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100}, {Name: "Neo.Blockchain.GetTransaction", Func: (*interopContext).bcGetTransaction, Price: 100},
"Neo.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1}, {Name: "Neo.Blockchain.GetTransactionHeight", Func: (*interopContext).bcGetTransactionHeight, Price: 100},
"Neo.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100}, {Name: "Neo.Blockchain.GetValidators", Func: (*interopContext).bcGetValidators, Price: 200},
"Neo.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100}, {Name: "Neo.Contract.Create", Func: (*interopContext).contractCreate, Price: 0},
"Neo.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200}, {Name: "Neo.Contract.Destroy", Func: (*interopContext).contractDestroy, Price: 1},
"Neo.Contract.Create": {Func: ic.contractCreate, Price: 0}, {Name: "Neo.Contract.GetScript", Func: (*interopContext).contractGetScript, Price: 1},
"Neo.Contract.Destroy": {Func: ic.contractDestroy, Price: 1}, {Name: "Neo.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
"Neo.Contract.GetScript": {Func: ic.contractGetScript, Price: 1}, {Name: "Neo.Contract.IsPayable", Func: (*interopContext).contractIsPayable, Price: 1},
"Neo.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1}, {Name: "Neo.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0},
"Neo.Contract.IsPayable": {Func: ic.contractIsPayable, Price: 1}, {Name: "Neo.Header.GetConsensusData", Func: (*interopContext).headerGetConsensusData, Price: 1},
"Neo.Contract.Migrate": {Func: ic.contractMigrate, Price: 0}, {Name: "Neo.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
"Neo.Header.GetConsensusData": {Func: ic.headerGetConsensusData, Price: 1}, {Name: "Neo.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1},
"Neo.Header.GetHash": {Func: ic.headerGetHash, Price: 1}, {Name: "Neo.Header.GetMerkleRoot", Func: (*interopContext).headerGetMerkleRoot, Price: 1},
"Neo.Header.GetIndex": {Func: ic.headerGetIndex, Price: 1}, {Name: "Neo.Header.GetNextConsensus", Func: (*interopContext).headerGetNextConsensus, Price: 1},
"Neo.Header.GetMerkleRoot": {Func: ic.headerGetMerkleRoot, Price: 1}, {Name: "Neo.Header.GetPrevHash", Func: (*interopContext).headerGetPrevHash, Price: 1},
"Neo.Header.GetNextConsensus": {Func: ic.headerGetNextConsensus, Price: 1}, {Name: "Neo.Header.GetTimestamp", Func: (*interopContext).headerGetTimestamp, Price: 1},
"Neo.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1}, {Name: "Neo.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1},
"Neo.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1}, {Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
"Neo.Header.GetVersion": {Func: ic.headerGetVersion, Price: 1}, {Name: "Neo.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1},
"Neo.Input.GetHash": {Func: ic.inputGetHash, Price: 1}, {Name: "Neo.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
"Neo.Input.GetIndex": {Func: ic.inputGetIndex, Price: 1}, {Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
"Neo.Output.GetAssetId": {Func: ic.outputGetAssetID, Price: 1}, {Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
"Neo.Output.GetScriptHash": {Func: ic.outputGetScriptHash, Price: 1}, {Name: "Neo.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
"Neo.Output.GetValue": {Func: ic.outputGetValue, Price: 1}, {Name: "Neo.Runtime.GetTime", Func: (*interopContext).runtimeGetTime, Price: 1},
"Neo.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200}, {Name: "Neo.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1},
"Neo.Runtime.GetTime": {Func: ic.runtimeGetTime, Price: 1}, {Name: "Neo.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
"Neo.Runtime.GetTrigger": {Func: ic.runtimeGetTrigger, Price: 1}, {Name: "Neo.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1},
"Neo.Runtime.Log": {Func: ic.runtimeLog, Price: 1}, {Name: "Neo.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100},
"Neo.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1}, {Name: "Neo.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
"Neo.Storage.Delete": {Func: ic.storageDelete, Price: 100}, {Name: "Neo.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
"Neo.Storage.Get": {Func: ic.storageGet, Price: 100}, {Name: "Neo.Storage.GetReadOnlyContext", Func: (*interopContext).storageGetReadOnlyContext, Price: 1},
"Neo.Storage.GetContext": {Func: ic.storageGetContext, Price: 1}, {Name: "Neo.Storage.Put", Func: (*interopContext).storagePut, Price: 0},
"Neo.Storage.GetReadOnlyContext": {Func: ic.storageGetReadOnlyContext, Price: 1}, {Name: "Neo.StorageContext.AsReadOnly", Func: (*interopContext).storageContextAsReadOnly, Price: 1},
"Neo.Storage.Put": {Func: ic.storagePut, Price: 0}, {Name: "Neo.Transaction.GetAttributes", Func: (*interopContext).txGetAttributes, Price: 1},
"Neo.StorageContext.AsReadOnly": {Func: ic.storageContextAsReadOnly, Price: 1}, {Name: "Neo.Transaction.GetHash", Func: (*interopContext).txGetHash, Price: 1},
"Neo.Transaction.GetAttributes": {Func: ic.txGetAttributes, Price: 1}, {Name: "Neo.Transaction.GetInputs", Func: (*interopContext).txGetInputs, Price: 1},
"Neo.Transaction.GetHash": {Func: ic.txGetHash, Price: 1}, {Name: "Neo.Transaction.GetOutputs", Func: (*interopContext).txGetOutputs, Price: 1},
"Neo.Transaction.GetInputs": {Func: ic.txGetInputs, Price: 1}, {Name: "Neo.Transaction.GetReferences", Func: (*interopContext).txGetReferences, Price: 200},
"Neo.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1}, {Name: "Neo.Transaction.GetType", Func: (*interopContext).txGetType, Price: 1},
"Neo.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200}, {Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200},
"Neo.Transaction.GetType": {Func: ic.txGetType, Price: 1}, {Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200},
"Neo.Transaction.GetUnspentCoins": {Func: ic.txGetUnspentCoins, Price: 200}, // {Name: "Neo.Enumerator.Concat", Func: (*interopContext).enumeratorConcat, Price: 1},
"Neo.Transaction.GetWitnesses": {Func: ic.txGetWitnesses, Price: 200}, // {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1},
// "Neo.Enumerator.Concat": {Func: ic.enumeratorConcat, Price: 1}, // {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
// "Neo.Enumerator.Create": {Func: ic.enumeratorCreate, Price: 1}, // {Name: "Neo.Enumerator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
// "Neo.Enumerator.Next": {Func: ic.enumeratorNext, Price: 1}, // {Name: "Neo.InvocationTransaction.GetScript", {ic.invocationTx_GetScript, 1},
// "Neo.Enumerator.Value": {Func: ic.enumeratorValue, Price: 1}, // {Name: "Neo.Iterator.Concat", Func: (*interopContext).iteratorConcat, Price: 1},
// "Neo.InvocationTransaction.GetScript": {ic.invocationTx_GetScript, 1}, // {Name: "Neo.Iterator.Create", Func: (*interopContext).iteratorCreate, Price: 1},
// "Neo.Iterator.Concat": {Func: ic.iteratorConcat, Price: 1}, // {Name: "Neo.Iterator.Key", Func: (*interopContext).iteratorKey, Price: 1},
// "Neo.Iterator.Create": {Func: ic.iteratorCreate, Price: 1}, // {Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1},
// "Neo.Iterator.Key": {Func: ic.iteratorKey, Price: 1}, // {Name: "Neo.Iterator.Values", Func: (*interopContext).iteratorValues, Price: 1},
// "Neo.Iterator.Keys": {Func: ic.iteratorKeys, Price: 1}, {Name: "Neo.Runtime.Deserialize", Func: (*interopContext).runtimeDeserialize, Price: 1},
// "Neo.Iterator.Values": {Func: ic.iteratorValues, Price: 1}, {Name: "Neo.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1},
"Neo.Runtime.Deserialize": {Func: ic.runtimeDeserialize, Price: 1}, // {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1},
"Neo.Runtime.Serialize": {Func: ic.runtimeSerialize, Price: 1}, // {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100},
// "Neo.Storage.Find": {Func: ic.storageFind, Price: 1},
// "Neo.Witness.GetVerificationScript": {Func: ic.witnessGetVerificationScript, Price: 100},
// Aliases. // Aliases.
// "Neo.Iterator.Next": {Func: ic.enumeratorNext, Price: 1}, // {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
// "Neo.Iterator.Value": {Func: ic.enumeratorValue, Price: 1}, // {Name: "Neo.Iterator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
// Old compatibility APIs. // Old compatibility APIs.
"AntShares.Account.GetBalance": {Func: ic.accountGetBalance, Price: 1}, {Name: "AntShares.Account.GetBalance", Func: (*interopContext).accountGetBalance, Price: 1},
"AntShares.Account.GetScriptHash": {Func: ic.accountGetScriptHash, Price: 1}, {Name: "AntShares.Account.GetScriptHash", Func: (*interopContext).accountGetScriptHash, Price: 1},
"AntShares.Account.GetVotes": {Func: ic.accountGetVotes, Price: 1}, {Name: "AntShares.Account.GetVotes", Func: (*interopContext).accountGetVotes, Price: 1},
"AntShares.Asset.Create": {Func: ic.assetCreate, Price: 0}, {Name: "AntShares.Asset.Create", Func: (*interopContext).assetCreate, Price: 0},
"AntShares.Asset.GetAdmin": {Func: ic.assetGetAdmin, Price: 1}, {Name: "AntShares.Asset.GetAdmin", Func: (*interopContext).assetGetAdmin, Price: 1},
"AntShares.Asset.GetAmount": {Func: ic.assetGetAmount, Price: 1}, {Name: "AntShares.Asset.GetAmount", Func: (*interopContext).assetGetAmount, Price: 1},
"AntShares.Asset.GetAssetId": {Func: ic.assetGetAssetID, Price: 1}, {Name: "AntShares.Asset.GetAssetId", Func: (*interopContext).assetGetAssetID, Price: 1},
"AntShares.Asset.GetAssetType": {Func: ic.assetGetAssetType, Price: 1}, {Name: "AntShares.Asset.GetAssetType", Func: (*interopContext).assetGetAssetType, Price: 1},
"AntShares.Asset.GetAvailable": {Func: ic.assetGetAvailable, Price: 1}, {Name: "AntShares.Asset.GetAvailable", Func: (*interopContext).assetGetAvailable, Price: 1},
"AntShares.Asset.GetIssuer": {Func: ic.assetGetIssuer, Price: 1}, {Name: "AntShares.Asset.GetIssuer", Func: (*interopContext).assetGetIssuer, Price: 1},
"AntShares.Asset.GetOwner": {Func: ic.assetGetOwner, Price: 1}, {Name: "AntShares.Asset.GetOwner", Func: (*interopContext).assetGetOwner, Price: 1},
"AntShares.Asset.GetPrecision": {Func: ic.assetGetPrecision, Price: 1}, {Name: "AntShares.Asset.GetPrecision", Func: (*interopContext).assetGetPrecision, Price: 1},
"AntShares.Asset.Renew": {Func: ic.assetRenew, Price: 0}, {Name: "AntShares.Asset.Renew", Func: (*interopContext).assetRenew, Price: 0},
"AntShares.Attribute.GetData": {Func: ic.attrGetData, Price: 1}, {Name: "AntShares.Attribute.GetData", Func: (*interopContext).attrGetData, Price: 1},
"AntShares.Attribute.GetUsage": {Func: ic.attrGetUsage, Price: 1}, {Name: "AntShares.Attribute.GetUsage", Func: (*interopContext).attrGetUsage, Price: 1},
"AntShares.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1}, {Name: "AntShares.Block.GetTransaction", Func: (*interopContext).blockGetTransaction, Price: 1},
"AntShares.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1}, {Name: "AntShares.Block.GetTransactionCount", Func: (*interopContext).blockGetTransactionCount, Price: 1},
"AntShares.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1}, {Name: "AntShares.Block.GetTransactions", Func: (*interopContext).blockGetTransactions, Price: 1},
"AntShares.Blockchain.GetAccount": {Func: ic.bcGetAccount, Price: 100}, {Name: "AntShares.Blockchain.GetAccount", Func: (*interopContext).bcGetAccount, Price: 100},
"AntShares.Blockchain.GetAsset": {Func: ic.bcGetAsset, Price: 100}, {Name: "AntShares.Blockchain.GetAsset", Func: (*interopContext).bcGetAsset, Price: 100},
"AntShares.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200}, {Name: "AntShares.Blockchain.GetBlock", Func: (*interopContext).bcGetBlock, Price: 200},
"AntShares.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100}, {Name: "AntShares.Blockchain.GetContract", Func: (*interopContext).bcGetContract, Price: 100},
"AntShares.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100}, {Name: "AntShares.Blockchain.GetHeader", Func: (*interopContext).bcGetHeader, Price: 100},
"AntShares.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1}, {Name: "AntShares.Blockchain.GetHeight", Func: (*interopContext).bcGetHeight, Price: 1},
"AntShares.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100}, {Name: "AntShares.Blockchain.GetTransaction", Func: (*interopContext).bcGetTransaction, Price: 100},
"AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200}, {Name: "AntShares.Blockchain.GetValidators", Func: (*interopContext).bcGetValidators, Price: 200},
"AntShares.Contract.Create": {Func: ic.contractCreate, Price: 0}, {Name: "AntShares.Contract.Create", Func: (*interopContext).contractCreate, Price: 0},
"AntShares.Contract.Destroy": {Func: ic.contractDestroy, Price: 1}, {Name: "AntShares.Contract.Destroy", Func: (*interopContext).contractDestroy, Price: 1},
"AntShares.Contract.GetScript": {Func: ic.contractGetScript, Price: 1}, {Name: "AntShares.Contract.GetScript", Func: (*interopContext).contractGetScript, Price: 1},
"AntShares.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1}, {Name: "AntShares.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
"AntShares.Contract.Migrate": {Func: ic.contractMigrate, Price: 0}, {Name: "AntShares.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0},
"AntShares.Header.GetConsensusData": {Func: ic.headerGetConsensusData, Price: 1}, {Name: "AntShares.Header.GetConsensusData", Func: (*interopContext).headerGetConsensusData, Price: 1},
"AntShares.Header.GetHash": {Func: ic.headerGetHash, Price: 1}, {Name: "AntShares.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
"AntShares.Header.GetMerkleRoot": {Func: ic.headerGetMerkleRoot, Price: 1}, {Name: "AntShares.Header.GetMerkleRoot", Func: (*interopContext).headerGetMerkleRoot, Price: 1},
"AntShares.Header.GetNextConsensus": {Func: ic.headerGetNextConsensus, Price: 1}, {Name: "AntShares.Header.GetNextConsensus", Func: (*interopContext).headerGetNextConsensus, Price: 1},
"AntShares.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1}, {Name: "AntShares.Header.GetPrevHash", Func: (*interopContext).headerGetPrevHash, Price: 1},
"AntShares.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1}, {Name: "AntShares.Header.GetTimestamp", Func: (*interopContext).headerGetTimestamp, Price: 1},
"AntShares.Header.GetVersion": {Func: ic.headerGetVersion, Price: 1}, {Name: "AntShares.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1},
"AntShares.Input.GetHash": {Func: ic.inputGetHash, Price: 1}, {Name: "AntShares.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
"AntShares.Input.GetIndex": {Func: ic.inputGetIndex, Price: 1}, {Name: "AntShares.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1},
"AntShares.Output.GetAssetId": {Func: ic.outputGetAssetID, Price: 1}, {Name: "AntShares.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
"AntShares.Output.GetScriptHash": {Func: ic.outputGetScriptHash, Price: 1}, {Name: "AntShares.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
"AntShares.Output.GetValue": {Func: ic.outputGetValue, Price: 1}, {Name: "AntShares.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
"AntShares.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200}, {Name: "AntShares.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
"AntShares.Runtime.Log": {Func: ic.runtimeLog, Price: 1}, {Name: "AntShares.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
"AntShares.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1}, {Name: "AntShares.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1},
"AntShares.Storage.Delete": {Func: ic.storageDelete, Price: 100}, {Name: "AntShares.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100},
"AntShares.Storage.Get": {Func: ic.storageGet, Price: 100}, {Name: "AntShares.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
"AntShares.Storage.GetContext": {Func: ic.storageGetContext, Price: 1}, {Name: "AntShares.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
"AntShares.Storage.Put": {Func: ic.storagePut, Price: 0}, {Name: "AntShares.Storage.Put", Func: (*interopContext).storagePut, Price: 0},
"AntShares.Transaction.GetAttributes": {Func: ic.txGetAttributes, Price: 1}, {Name: "AntShares.Transaction.GetAttributes", Func: (*interopContext).txGetAttributes, Price: 1},
"AntShares.Transaction.GetHash": {Func: ic.txGetHash, Price: 1}, {Name: "AntShares.Transaction.GetHash", Func: (*interopContext).txGetHash, Price: 1},
"AntShares.Transaction.GetInputs": {Func: ic.txGetInputs, Price: 1}, {Name: "AntShares.Transaction.GetInputs", Func: (*interopContext).txGetInputs, Price: 1},
"AntShares.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1}, {Name: "AntShares.Transaction.GetOutputs", Func: (*interopContext).txGetOutputs, Price: 1},
"AntShares.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200}, {Name: "AntShares.Transaction.GetReferences", Func: (*interopContext).txGetReferences, Price: 200},
"AntShares.Transaction.GetType": {Func: ic.txGetType, Price: 1}, {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 package vm
import ( import (
"crypto/sha256"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"sort"
) )
// InteropFunc allows to hook into the VM. // InteropFunc allows to hook into the VM.
type InteropFunc func(vm *VM) error 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. // runtimeLog handles the syscall "Neo.Runtime.Log" for printing and logging stuff.
func runtimeLog(vm *VM) error { func runtimeLog(vm *VM) error {
item := vm.Estack().Pop() item := vm.Estack().Pop()
@ -50,3 +100,10 @@ func RuntimeDeserialize(vm *VM) error {
return nil 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)") 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) { func testFile(t *testing.T, filename string) {
data, err := ioutil.ReadFile(filename) data, err := ioutil.ReadFile(filename)
if err != nil { 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 // FIXME remove when NEO 3.0 https://github.com/nspcc-dev/neo-go/issues/477
vm.getScript = getScript(test.ScriptTable) vm.getScript = getScript(test.ScriptTable)
vm.RegisterInteropGetter(getTestingInterop)
// 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)
for i := range test.Steps { for i := range test.Steps {
execStep(t, vm, test.Steps[i]) execStep(t, vm, test.Steps[i])

View file

@ -48,9 +48,7 @@ func vmAndCompile(t *testing.T, src string) *vm.VM {
vm := vm.New() vm := vm.New()
storePlugin := newStoragePlugin() storePlugin := newStoragePlugin()
vm.RegisterInteropFunc("Neo.Storage.Get", storePlugin.Get, 1) vm.RegisterInteropGetter(storePlugin.getInterop)
vm.RegisterInteropFunc("Neo.Storage.Put", storePlugin.Put, 1)
vm.RegisterInteropFunc("Neo.Storage.GetContext", storePlugin.GetContext, 1)
b, err := compiler.Compile(strings.NewReader(src)) b, err := compiler.Compile(strings.NewReader(src))
if err != nil { if err != nil {
@ -61,13 +59,28 @@ func vmAndCompile(t *testing.T, src string) *vm.VM {
} }
type storagePlugin struct { type storagePlugin struct {
mem map[string][]byte mem map[string][]byte
interops map[uint32]vm.InteropFunc
} }
func newStoragePlugin() *storagePlugin { func newStoragePlugin() *storagePlugin {
return &storagePlugin{ s := &storagePlugin{
mem: make(map[string][]byte), 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 { func (s *storagePlugin) Delete(vm *vm.VM) error {

View file

@ -60,8 +60,8 @@ const (
type VM struct { type VM struct {
state State state State
// registered interop hooks. // callbacks to get interops.
interop map[string]InteropFuncPrice getInterop []InteropGetterFunc
// callback to get scripts. // callback to get scripts.
getScript func(util.Uint160) []byte getScript func(util.Uint160) []byte
@ -80,19 +80,13 @@ type VM struct {
keys map[string]*keys.PublicKey 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. // New returns a new VM object ready to load .avm bytecode scripts.
func New() *VM { func New() *VM {
vm := &VM{ vm := &VM{
interop: make(map[string]InteropFuncPrice), getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
getScript: nil, getScript: nil,
state: haltState, state: haltState,
istack: NewStack("invocation"), istack: NewStack("invocation"),
itemCount: make(map[StackItem]int), itemCount: make(map[StackItem]int),
keys: make(map[string]*keys.PublicKey), keys: make(map[string]*keys.PublicKey),
@ -101,14 +95,7 @@ func New() *VM {
vm.estack = vm.newItemStack("evaluation") vm.estack = vm.newItemStack("evaluation")
vm.astack = vm.newItemStack("alt") vm.astack = vm.newItemStack("alt")
// Register native interop hooks. vm.RegisterInteropGetter(getDefaultVMInterop)
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)
return vm return vm
} }
@ -120,18 +107,11 @@ func (v *VM) newItemStack(n string) *Stack {
return s return s
} }
// RegisterInteropFunc registers the given InteropFunc to the VM. // RegisterInteropGetter registers the given InteropGetterFunc into VM. There
func (v *VM) RegisterInteropFunc(name string, f InteropFunc, price int) { // can be many interop getters and they're probed in LIFO order wrt their
v.interop[name] = InteropFuncPrice{f, price} // registration time.
} func (v *VM) RegisterInteropGetter(f InteropGetterFunc) {
v.getInterop = append(v.getInterop, f)
// 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]
}
} }
// Estack returns the evaluation stack so interop hooks can utilize this. // 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: case opcode.SYSCALL:
ifunc, ok := v.interop[string(parameter)] var ifunc *InteropFuncPrice
if !ok { var interopID uint32
panic(fmt.Sprintf("interop hook (%q) not registered", parameter))
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 { if err := ifunc.Func(v); err != nil {
panic(fmt.Sprintf("failed to invoke syscall: %s", err)) panic(fmt.Sprintf("failed to invoke syscall: %s", err))

View file

@ -16,12 +16,19 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func fooInteropGetter(id uint32) *InteropFuncPrice {
if id == InteropNameToID([]byte("foo")) {
return &InteropFuncPrice{func(evm *VM) error {
evm.Estack().PushVal(1)
return nil
}, 1}
}
return nil
}
func TestInteropHook(t *testing.T) { func TestInteropHook(t *testing.T) {
v := New() v := New()
v.RegisterInteropFunc("foo", func(evm *VM) error { v.RegisterInteropGetter(fooInteropGetter)
evm.Estack().PushVal(1)
return nil
}, 1)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
EmitSyscall(buf, "foo") EmitSyscall(buf, "foo")
@ -32,13 +39,27 @@ func TestInteropHook(t *testing.T) {
assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value()) assert.Equal(t, big.NewInt(1), v.estack.Pop().value.Value())
} }
func TestRegisterInterop(t *testing.T) { func TestInteropHookViaID(t *testing.T) {
v := New() v := New()
currRegistered := len(v.interop) v.RegisterInteropGetter(fooInteropGetter)
v.RegisterInteropFunc("foo", func(evm *VM) error { return nil }, 1)
assert.Equal(t, currRegistered+1, len(v.interop)) buf := new(bytes.Buffer)
_, ok := v.interop["foo"] fooid := InteropNameToID([]byte("foo"))
assert.Equal(t, true, ok) 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) { func TestBytesToPublicKey(t *testing.T) {