forked from TrueCloudLab/neoneo-go
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:
parent
1a26548be8
commit
a7457d08a1
7 changed files with 384 additions and 247 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
||||
// 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},
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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},
|
||||
// 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.
|
||||
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},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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 {
|
||||
|
|
58
pkg/vm/vm.go
58
pkg/vm/vm.go
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue