neoneo-go/pkg/core/interops.go
Roman Khimov a7457d08a1 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.
2019-12-19 13:35:42 +03:00

270 lines
18 KiB
Go

package core
/*
Interops are designed to run under VM's execute() panic protection, so it's OK
for them to do things like
smth := v.Estack().Pop().Bytes()
even though technically Pop() can return a nil pointer.
*/
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"
"github.com/CityOfZion/neo-go/pkg/vm"
)
type interopContext struct {
bc Blockchainer
trigger byte
block *Block
tx *transaction.Transaction
dao *cachedDao
notifications []state.NotificationEvent
}
func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext {
dao := newCachedDao(s)
nes := make([]state.NotificationEvent, 0)
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.
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.
// {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
// {Name: "Neo.Iterator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
// Old compatibility APIs.
{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)
}