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
|
return cs.Script
|
||||||
})
|
})
|
||||||
vm.RegisterInteropFuncs(interopCtx.getSystemInteropMap())
|
vm.RegisterInteropGetter(interopCtx.getSystemInterop)
|
||||||
vm.RegisterInteropFuncs(interopCtx.getNeoInteropMap())
|
vm.RegisterInteropGetter(interopCtx.getNeoInterop)
|
||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
64
pkg/vm/vm.go
64
pkg/vm/vm.go
|
@ -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))
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue