core: add some interops
This also changes Verify to VerifyTx and VerifyWitnesses, because there is a need to pass a block for some interop functions.
This commit is contained in:
parent
667f346c04
commit
74590551c4
7 changed files with 1555 additions and 9 deletions
|
@ -215,7 +215,7 @@ func (bc *Blockchain) AddBlock(block *Block) error {
|
||||||
return fmt.Errorf("block %s is invalid: %s", block.Hash().ReverseString(), err)
|
return fmt.Errorf("block %s is invalid: %s", block.Hash().ReverseString(), err)
|
||||||
}
|
}
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
err := bc.Verify(tx)
|
err := bc.VerifyTx(tx, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("transaction %s failed to verify: %s", tx.Hash().ReverseString(), err)
|
return fmt.Errorf("transaction %s failed to verify: %s", tx.Hash().ReverseString(), err)
|
||||||
}
|
}
|
||||||
|
@ -816,9 +816,11 @@ func (bc *Blockchain) GetMemPool() MemPool {
|
||||||
return bc.memPool
|
return bc.memPool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify verifies whether a transaction is bonafide or not.
|
// VerifyTx verifies whether a transaction is bonafide or not. Block parameter
|
||||||
|
// is used for easy interop access and can be omitted for transactions that are
|
||||||
|
// not yet added into any block.
|
||||||
// Golang implementation of Verify method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L270).
|
// Golang implementation of Verify method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L270).
|
||||||
func (bc *Blockchain) Verify(t *transaction.Transaction) error {
|
func (bc *Blockchain) VerifyTx(t *transaction.Transaction, block *Block) error {
|
||||||
if io.GetVarSize(t) > transaction.MaxTransactionSize {
|
if io.GetVarSize(t) > transaction.MaxTransactionSize {
|
||||||
return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", io.GetVarSize(t), transaction.MaxTransactionSize)
|
return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", io.GetVarSize(t), transaction.MaxTransactionSize)
|
||||||
}
|
}
|
||||||
|
@ -844,7 +846,7 @@ func (bc *Blockchain) Verify(t *transaction.Transaction) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bc.VerifyWitnesses(t)
|
return bc.VerifyWitnesses(t, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) verifyInputs(t *transaction.Transaction) bool {
|
func (bc *Blockchain) verifyInputs(t *transaction.Transaction) bool {
|
||||||
|
@ -1070,10 +1072,12 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([
|
||||||
|
|
||||||
// VerifyWitnesses verify the scripts (witnesses) that come with a given
|
// VerifyWitnesses verify the scripts (witnesses) that come with a given
|
||||||
// transaction. It can reorder them by ScriptHash, because that's required to
|
// transaction. It can reorder them by ScriptHash, because that's required to
|
||||||
// match a slice of script hashes from the Blockchain.
|
// match a slice of script hashes from the Blockchain. Block parameter
|
||||||
|
// is used for easy interop access and can be omitted for transactions that are
|
||||||
|
// not yet added into any block.
|
||||||
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
||||||
// Unfortunately the IVerifiable interface could not be implemented because we can't move the References method in blockchain.go to the transaction.go file
|
// Unfortunately the IVerifiable interface could not be implemented because we can't move the References method in blockchain.go to the transaction.go file
|
||||||
func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
|
func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction, block *Block) error {
|
||||||
hashes, err := bc.GetScriptHashesForVerifying(t)
|
hashes, err := bc.GetScriptHashesForVerifying(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1110,6 +1114,9 @@ func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
|
||||||
}
|
}
|
||||||
return cs.Script
|
return cs.Script
|
||||||
})
|
})
|
||||||
|
systemInterop := newInteropContext(0, bc, block, t)
|
||||||
|
vm.RegisterInteropFuncs(systemInterop.getSystemInteropMap())
|
||||||
|
vm.RegisterInteropFuncs(systemInterop.getNeoInteropMap())
|
||||||
vm.LoadScript(verification)
|
vm.LoadScript(verification)
|
||||||
vm.LoadScript(witnesses[i].InvocationScript)
|
vm.LoadScript(witnesses[i].InvocationScript)
|
||||||
vm.Run()
|
vm.Run()
|
||||||
|
|
|
@ -31,6 +31,6 @@ type Blockchainer interface {
|
||||||
GetUnspentCoinState(util.Uint256) *UnspentCoinState
|
GetUnspentCoinState(util.Uint256) *UnspentCoinState
|
||||||
References(t *transaction.Transaction) map[transaction.Input]*transaction.Output
|
References(t *transaction.Transaction) map[transaction.Input]*transaction.Output
|
||||||
Feer // fee interface
|
Feer // fee interface
|
||||||
Verify(t *transaction.Transaction) error
|
VerifyTx(*transaction.Transaction, *Block) error
|
||||||
GetMemPool() MemPool
|
GetMemPool() MemPool
|
||||||
}
|
}
|
||||||
|
|
738
pkg/core/interop_neo.go
Normal file
738
pkg/core/interop_neo.go
Normal file
|
@ -0,0 +1,738 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
gherr "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxContractScriptSize is the maximum script size for a contract.
|
||||||
|
MaxContractScriptSize = 1024 * 1024
|
||||||
|
// MaxContractParametersNum is the maximum number of parameters for a contract.
|
||||||
|
MaxContractParametersNum = 252
|
||||||
|
// MaxContractStringLen is the maximum length for contract metadata strings.
|
||||||
|
MaxContractStringLen = 252
|
||||||
|
// MaxAssetNameLen is the maximum length of asset name.
|
||||||
|
MaxAssetNameLen = 1024
|
||||||
|
// MaxAssetPrecision is the maximum precision of asset.
|
||||||
|
MaxAssetPrecision = 8
|
||||||
|
// BlocksPerYear is a multiplier for asset renewal.
|
||||||
|
BlocksPerYear = 2000000
|
||||||
|
// DefaultAssetLifetime is the default lifetime of an asset (which differs
|
||||||
|
// from assets created by register tx).
|
||||||
|
DefaultAssetLifetime = 1 + BlocksPerYear
|
||||||
|
)
|
||||||
|
|
||||||
|
// txInOut is used to pushed one key-value pair from References() onto the stack.
|
||||||
|
type txInOut struct {
|
||||||
|
in transaction.Input
|
||||||
|
out transaction.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetVersion returns version from the header.
|
||||||
|
func (ic *interopContext) headerGetVersion(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetConsensusData returns consensus data from the header.
|
||||||
|
func (ic *interopContext) headerGetConsensusData(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.ConsensusData)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetMerkleRoot returns version from the header.
|
||||||
|
func (ic *interopContext) headerGetMerkleRoot(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.MerkleRoot.BytesReverse())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetNextConsensus returns version from the header.
|
||||||
|
func (ic *interopContext) headerGetNextConsensus(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.NextConsensus.BytesReverse())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetAttributes returns current transaction attributes.
|
||||||
|
func (ic *interopContext) txGetAttributes(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
if len(tx.Attributes) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many attributes")
|
||||||
|
}
|
||||||
|
attrs := make([]vm.StackItem, 0, len(tx.Attributes))
|
||||||
|
for _, attr := range tx.Attributes {
|
||||||
|
attrs = append(attrs, vm.NewInteropItem(attr))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(attrs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetInputs returns current transaction inputs.
|
||||||
|
func (ic *interopContext) txGetInputs(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
if len(tx.Inputs) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many inputs")
|
||||||
|
}
|
||||||
|
inputs := make([]vm.StackItem, 0, len(tx.Inputs))
|
||||||
|
for _, input := range tx.Inputs {
|
||||||
|
inputs = append(inputs, vm.NewInteropItem(input))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(inputs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetOutputs returns current transaction outputs.
|
||||||
|
func (ic *interopContext) txGetOutputs(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
if len(tx.Outputs) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many outputs")
|
||||||
|
}
|
||||||
|
outputs := make([]vm.StackItem, 0, len(tx.Outputs))
|
||||||
|
for _, output := range tx.Outputs {
|
||||||
|
outputs = append(outputs, vm.NewInteropItem(output))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(outputs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetReferences returns current transaction references.
|
||||||
|
func (ic *interopContext) txGetReferences(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("type mismatch: %T is not a Transaction", txInterface)
|
||||||
|
}
|
||||||
|
refs := ic.bc.References(tx)
|
||||||
|
if len(refs) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many references")
|
||||||
|
}
|
||||||
|
|
||||||
|
stackrefs := make([]vm.StackItem, 0, len(refs))
|
||||||
|
for k, v := range refs {
|
||||||
|
tio := txInOut{k, *v}
|
||||||
|
stackrefs = append(stackrefs, vm.NewInteropItem(tio))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(stackrefs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetType returns current transaction type.
|
||||||
|
func (ic *interopContext) txGetType(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int(tx.Type))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetUnspentCoins returns current transaction unspent coins.
|
||||||
|
func (ic *interopContext) txGetUnspentCoins(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
ucs := ic.bc.GetUnspentCoinState(tx.Hash())
|
||||||
|
if ucs == nil {
|
||||||
|
return errors.New("no unspent coin state found")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(ucs))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetWitnesses returns current transaction witnesses.
|
||||||
|
func (ic *interopContext) txGetWitnesses(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
if len(tx.Scripts) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many outputs")
|
||||||
|
}
|
||||||
|
scripts := make([]vm.StackItem, 0, len(tx.Scripts))
|
||||||
|
for _, script := range tx.Scripts {
|
||||||
|
scripts = append(scripts, vm.NewInteropItem(script))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(scripts)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// popInputFromVM returns transaction.Input from the first estack element.
|
||||||
|
func popInputFromVM(v *vm.VM) (*transaction.Input, error) {
|
||||||
|
inInterface := v.Estack().Pop().Value()
|
||||||
|
input, ok := inInterface.(*transaction.Input)
|
||||||
|
if !ok {
|
||||||
|
txio, ok := inInterface.(txInOut)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("type mismatch: %T is not an Input or txInOut", inInterface)
|
||||||
|
}
|
||||||
|
input = &txio.in
|
||||||
|
}
|
||||||
|
return input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputGetHash returns hash from the given input.
|
||||||
|
func (ic *interopContext) inputGetHash(v *vm.VM) error {
|
||||||
|
input, err := popInputFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(input.PrevHash.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputGetIndex returns index from the given input.
|
||||||
|
func (ic *interopContext) inputGetIndex(v *vm.VM) error {
|
||||||
|
input, err := popInputFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(input.PrevIndex)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// popOutputFromVM returns transaction.Input from the first estack element.
|
||||||
|
func popOutputFromVM(v *vm.VM) (*transaction.Output, error) {
|
||||||
|
outInterface := v.Estack().Pop().Value()
|
||||||
|
output, ok := outInterface.(*transaction.Output)
|
||||||
|
if !ok {
|
||||||
|
txio, ok := outInterface.(txInOut)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("type mismatch: %T is not an Output or txInOut", outInterface)
|
||||||
|
}
|
||||||
|
output = &txio.out
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputGetAssetId returns asset ID from the given output.
|
||||||
|
func (ic *interopContext) outputGetAssetID(v *vm.VM) error {
|
||||||
|
output, err := popOutputFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(output.AssetID.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputGetScriptHash returns scripthash from the given output.
|
||||||
|
func (ic *interopContext) outputGetScriptHash(v *vm.VM) error {
|
||||||
|
output, err := popOutputFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(output.ScriptHash.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputGetValue returns value (amount) from the given output.
|
||||||
|
func (ic *interopContext) outputGetValue(v *vm.VM) error {
|
||||||
|
output, err := popOutputFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int64(output.Amount))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// attrGetData returns tx attribute data.
|
||||||
|
func (ic *interopContext) attrGetData(v *vm.VM) error {
|
||||||
|
attrInterface := v.Estack().Pop().Value()
|
||||||
|
attr, ok := attrInterface.(*transaction.Attribute)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an attribute", attr)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(attr.Data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// attrGetData returns tx attribute usage field.
|
||||||
|
func (ic *interopContext) attrGetUsage(v *vm.VM) error {
|
||||||
|
attrInterface := v.Estack().Pop().Value()
|
||||||
|
attr, ok := attrInterface.(*transaction.Attribute)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an attribute", attr)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int(attr.Usage))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetAccount returns or creates an account.
|
||||||
|
func (ic *interopContext) bcGetAccount(v *vm.VM) error {
|
||||||
|
accbytes := v.Estack().Pop().Bytes()
|
||||||
|
acchash, err := util.Uint160DecodeBytes(accbytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acc := ic.bc.GetAccountState(acchash)
|
||||||
|
if acc == nil {
|
||||||
|
acc = NewAccountState(acchash)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(acc))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetAsset returns an asset.
|
||||||
|
func (ic *interopContext) bcGetAsset(v *vm.VM) error {
|
||||||
|
asbytes := v.Estack().Pop().Bytes()
|
||||||
|
ashash, err := util.Uint256DecodeBytes(asbytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
as := ic.bc.GetAssetState(ashash)
|
||||||
|
if as == nil {
|
||||||
|
return errors.New("asset not found")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(as))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountGetBalance returns balance for a given account.
|
||||||
|
func (ic *interopContext) accountGetBalance(v *vm.VM) error {
|
||||||
|
accInterface := v.Estack().Pop().Value()
|
||||||
|
acc, ok := accInterface.(*AccountState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an account state", acc)
|
||||||
|
}
|
||||||
|
asbytes := v.Estack().Pop().Bytes()
|
||||||
|
ashash, err := util.Uint256DecodeBytes(asbytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
balance, ok := acc.Balances[ashash]
|
||||||
|
if !ok {
|
||||||
|
balance = util.Fixed8(0)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int64(balance))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountGetScriptHash returns script hash of a given account.
|
||||||
|
func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
|
||||||
|
accInterface := v.Estack().Pop().Value()
|
||||||
|
acc, ok := accInterface.(*AccountState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an account state", acc)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(acc.ScriptHash.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountGetVotes returns votes of a given account.
|
||||||
|
func (ic *interopContext) accountGetVotes(v *vm.VM) error {
|
||||||
|
accInterface := v.Estack().Pop().Value()
|
||||||
|
acc, ok := accInterface.(*AccountState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an account state", acc)
|
||||||
|
}
|
||||||
|
if len(acc.Votes) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many votes")
|
||||||
|
}
|
||||||
|
votes := make([]vm.StackItem, 0, len(acc.Votes))
|
||||||
|
for _, key := range acc.Votes {
|
||||||
|
votes = append(votes, vm.NewByteArrayItem(key.Bytes()))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(votes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountIsStandard checks whether given account is standard.
|
||||||
|
func (ic *interopContext) accountIsStandard(v *vm.VM) error {
|
||||||
|
accbytes := v.Estack().Pop().Bytes()
|
||||||
|
acchash, err := util.Uint160DecodeBytes(accbytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
contract := ic.bc.GetContractState(acchash)
|
||||||
|
res := contract == nil || vm.IsStandardContract(contract.Script)
|
||||||
|
v.Estack().PushVal(res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// storageFind finds stored key-value pair.
|
||||||
|
func (ic *interopContext) storageFind(v *vm.VM) error {
|
||||||
|
stcInterface := v.Estack().Pop().Value()
|
||||||
|
stc, ok := stcInterface.(*StorageContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
|
}
|
||||||
|
err := ic.checkStorageContext(stc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prefix := string(v.Estack().Pop().Bytes())
|
||||||
|
siMap, err := ic.bc.GetStorageItems(stc.ScriptHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, v := range siMap {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
_ = v
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// createContractStateFromVM pops all contract state elements from the VM
|
||||||
|
// evaluation stack, does a lot of checks and returns ContractState if it
|
||||||
|
// succeedes.
|
||||||
|
func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*ContractState, error) {
|
||||||
|
if ic.trigger != 0x10 {
|
||||||
|
return nil, errors.New("can't create contract when not triggered by an application")
|
||||||
|
}
|
||||||
|
script := v.Estack().Pop().Bytes()
|
||||||
|
if len(script) > MaxContractScriptSize {
|
||||||
|
return nil, errors.New("the script is too big")
|
||||||
|
}
|
||||||
|
paramBytes := v.Estack().Pop().Bytes()
|
||||||
|
if len(paramBytes) > MaxContractParametersNum {
|
||||||
|
return nil, errors.New("too many parameters for a script")
|
||||||
|
}
|
||||||
|
paramList := make([]smartcontract.ParamType, len(paramBytes))
|
||||||
|
for k, v := range paramBytes {
|
||||||
|
paramList[k] = smartcontract.ParamType(v)
|
||||||
|
}
|
||||||
|
retType := smartcontract.ParamType(v.Estack().Pop().BigInt().Int64())
|
||||||
|
properties := smartcontract.PropertyState(v.Estack().Pop().BigInt().Int64())
|
||||||
|
name := v.Estack().Pop().Bytes()
|
||||||
|
if len(name) > MaxContractStringLen {
|
||||||
|
return nil, errors.New("too big name")
|
||||||
|
}
|
||||||
|
version := v.Estack().Pop().Bytes()
|
||||||
|
if len(version) > MaxContractStringLen {
|
||||||
|
return nil, errors.New("too big version")
|
||||||
|
}
|
||||||
|
author := v.Estack().Pop().Bytes()
|
||||||
|
if len(author) > MaxContractStringLen {
|
||||||
|
return nil, errors.New("too big author")
|
||||||
|
}
|
||||||
|
email := v.Estack().Pop().Bytes()
|
||||||
|
if len(email) > MaxContractStringLen {
|
||||||
|
return nil, errors.New("too big email")
|
||||||
|
}
|
||||||
|
desc := v.Estack().Pop().Bytes()
|
||||||
|
if len(desc) > MaxContractStringLen {
|
||||||
|
return nil, errors.New("too big description")
|
||||||
|
}
|
||||||
|
contract := &ContractState{
|
||||||
|
Script: script,
|
||||||
|
ParamList: paramList,
|
||||||
|
ReturnType: retType,
|
||||||
|
Properties: properties,
|
||||||
|
Name: string(name),
|
||||||
|
CodeVersion: string(version),
|
||||||
|
Author: string(author),
|
||||||
|
Email: string(email),
|
||||||
|
Description: string(desc),
|
||||||
|
}
|
||||||
|
return contract, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractCreate creates a contract.
|
||||||
|
func (ic *interopContext) contractCreate(v *vm.VM) error {
|
||||||
|
newcontract, err := ic.createContractStateFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
||||||
|
if contract == nil {
|
||||||
|
contract = newcontract
|
||||||
|
err := putContractStateIntoStore(ic.mem, contract)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(contract))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractGetScript returns a script associated with a contract.
|
||||||
|
func (ic *interopContext) contractGetScript(v *vm.VM) error {
|
||||||
|
csInterface := v.Estack().Pop().Value()
|
||||||
|
cs, ok := csInterface.(*ContractState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a contract state", cs)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(cs.Script)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractIsPayable returns whether contract is payable.
|
||||||
|
func (ic *interopContext) contractIsPayable(v *vm.VM) error {
|
||||||
|
csInterface := v.Estack().Pop().Value()
|
||||||
|
cs, ok := csInterface.(*ContractState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a contract state", cs)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(cs.IsPayable())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractMigrate migrates a contract.
|
||||||
|
func (ic *interopContext) contractMigrate(v *vm.VM) error {
|
||||||
|
newcontract, err := ic.createContractStateFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
||||||
|
if contract == nil {
|
||||||
|
contract = newcontract
|
||||||
|
err := putContractStateIntoStore(ic.mem, contract)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if contract.HasStorage() {
|
||||||
|
hash := getContextScriptHash(v, 0)
|
||||||
|
siMap, err := ic.bc.GetStorageItems(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, v := range siMap {
|
||||||
|
v.IsConst = false
|
||||||
|
_ = putStorageItemIntoStore(ic.mem, hash, []byte(k), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(contract))
|
||||||
|
return ic.contractDestroy(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetCreate creates an asset.
|
||||||
|
func (ic *interopContext) assetCreate(v *vm.VM) error {
|
||||||
|
if ic.trigger != 0x10 {
|
||||||
|
return errors.New("can't create asset when not triggered by an application")
|
||||||
|
}
|
||||||
|
atype := transaction.AssetType(v.Estack().Pop().BigInt().Int64())
|
||||||
|
switch atype {
|
||||||
|
case transaction.Currency, transaction.Share, transaction.Invoice, transaction.Token:
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong asset type: %x", atype)
|
||||||
|
}
|
||||||
|
name := string(v.Estack().Pop().Bytes())
|
||||||
|
if len(name) > MaxAssetNameLen {
|
||||||
|
return errors.New("too big name")
|
||||||
|
}
|
||||||
|
amount := util.Fixed8(v.Estack().Pop().BigInt().Int64())
|
||||||
|
if amount == util.Fixed8(0) {
|
||||||
|
return errors.New("asset amount can't be zero")
|
||||||
|
}
|
||||||
|
if amount < -util.Satoshi() {
|
||||||
|
return errors.New("asset amount can't be negative (except special -Satoshi value")
|
||||||
|
}
|
||||||
|
if atype == transaction.Invoice && amount != -util.Satoshi() {
|
||||||
|
return errors.New("invoice assets can only have -Satoshi amount")
|
||||||
|
}
|
||||||
|
precision := byte(v.Estack().Pop().BigInt().Int64())
|
||||||
|
if precision > MaxAssetPrecision {
|
||||||
|
return fmt.Errorf("can't have asset precision of more than %d", MaxAssetPrecision)
|
||||||
|
}
|
||||||
|
if atype == transaction.Share && precision != 0 {
|
||||||
|
return errors.New("share assets can only have zero precision")
|
||||||
|
}
|
||||||
|
if amount != -util.Satoshi() && (int64(amount)%int64(math.Pow10(int(MaxAssetPrecision-precision))) != 0) {
|
||||||
|
return errors.New("given asset amount has fractional component")
|
||||||
|
}
|
||||||
|
owner := &keys.PublicKey{}
|
||||||
|
err := owner.DecodeBytes(v.Estack().Pop().Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return gherr.Wrap(err, "failed to get owner key")
|
||||||
|
}
|
||||||
|
if owner.IsInfinity() {
|
||||||
|
return errors.New("can't have infinity as an owner key")
|
||||||
|
}
|
||||||
|
witnessOk, err := ic.checkKeyedWitness(owner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !witnessOk {
|
||||||
|
return errors.New("witness check didn't succeed")
|
||||||
|
}
|
||||||
|
admin, err := util.Uint160DecodeBytes(v.Estack().Pop().Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return gherr.Wrap(err, "failed to get admin")
|
||||||
|
}
|
||||||
|
issuer, err := util.Uint160DecodeBytes(v.Estack().Pop().Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return gherr.Wrap(err, "failed to get issuer")
|
||||||
|
}
|
||||||
|
asset := &AssetState{
|
||||||
|
ID: ic.tx.Hash(),
|
||||||
|
AssetType: atype,
|
||||||
|
Name: name,
|
||||||
|
Amount: amount,
|
||||||
|
Precision: precision,
|
||||||
|
Owner: owner,
|
||||||
|
Admin: admin,
|
||||||
|
Issuer: issuer,
|
||||||
|
Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime,
|
||||||
|
}
|
||||||
|
err = putAssetStateIntoStore(ic.mem, asset)
|
||||||
|
if err != nil {
|
||||||
|
return gherr.Wrap(err, "failed to store asset")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(asset))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetAdmin returns asset admin.
|
||||||
|
func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(as.Admin.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetAmount returns the overall amount of asset available.
|
||||||
|
func (ic *interopContext) assetGetAmount(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int64(as.Amount))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetAssetId returns the id of an asset.
|
||||||
|
func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(as.ID.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetAssetType returns type of an asset.
|
||||||
|
func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int(as.AssetType))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetAvailable returns available (not yet issued) amount of asset.
|
||||||
|
func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int(as.Available))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetIssuer returns issuer of an asset.
|
||||||
|
func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(as.Issuer.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetOwner returns owner of an asset.
|
||||||
|
func (ic *interopContext) assetGetOwner(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(as.Owner.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetGetPrecision returns precision used to measure this asset.
|
||||||
|
func (ic *interopContext) assetGetPrecision(v *vm.VM) error {
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(int(as.Precision))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assetRenew updates asset expiration date.
|
||||||
|
func (ic *interopContext) assetRenew(v *vm.VM) error {
|
||||||
|
if ic.trigger != 0x10 {
|
||||||
|
return errors.New("can't create asset when not triggered by an application")
|
||||||
|
}
|
||||||
|
asInterface := v.Estack().Pop().Value()
|
||||||
|
as, ok := asInterface.(*AssetState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
|
}
|
||||||
|
years := byte(v.Estack().Pop().BigInt().Int64())
|
||||||
|
// Not sure why C# code regets an asset from the Store, but we also do it.
|
||||||
|
asset := ic.bc.GetAssetState(as.ID)
|
||||||
|
if asset == nil {
|
||||||
|
return errors.New("can't renew non-existent asset")
|
||||||
|
}
|
||||||
|
if asset.Expiration < ic.bc.BlockHeight()+1 {
|
||||||
|
asset.Expiration = ic.bc.BlockHeight() + 1
|
||||||
|
}
|
||||||
|
expiration := uint64(asset.Expiration) + uint64(years)*BlocksPerYear
|
||||||
|
if expiration > math.MaxUint32 {
|
||||||
|
expiration = math.MaxUint32
|
||||||
|
}
|
||||||
|
asset.Expiration = uint32(expiration)
|
||||||
|
err := putAssetStateIntoStore(ic.mem, asset)
|
||||||
|
if err != nil {
|
||||||
|
return gherr.Wrap(err, "failed to store asset")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(expiration)
|
||||||
|
return nil
|
||||||
|
}
|
583
pkg/core/interop_system.go
Normal file
583
pkg/core/interop_system.go
Normal file
|
@ -0,0 +1,583 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
gherr "github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxStorageKeyLen is the maximum length of a key for storage items.
|
||||||
|
MaxStorageKeyLen = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageContext contains storing script hash and read/write flag, it's used as
|
||||||
|
// a context for storage manipulation functions.
|
||||||
|
type StorageContext struct {
|
||||||
|
ScriptHash util.Uint160
|
||||||
|
ReadOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBlockHashFromElement converts given vm.Element to block hash using given
|
||||||
|
// Blockchainer if needed. Interop functions accept both block numbers and
|
||||||
|
// block hashes as parameters, thus this function is needed.
|
||||||
|
func getBlockHashFromElement(bc Blockchainer, element *vm.Element) (util.Uint256, error) {
|
||||||
|
var hash util.Uint256
|
||||||
|
hashbytes := element.Bytes()
|
||||||
|
if len(hashbytes) <= 5 {
|
||||||
|
hashint := element.BigInt().Int64()
|
||||||
|
if hashint < 0 || hashint > math.MaxUint32 {
|
||||||
|
return hash, errors.New("bad block index")
|
||||||
|
}
|
||||||
|
hash = bc.GetHeaderHash(int(hashint))
|
||||||
|
} else {
|
||||||
|
return util.Uint256DecodeReverseBytes(hashbytes)
|
||||||
|
}
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetBlock returns current block.
|
||||||
|
func (ic *interopContext) bcGetBlock(v *vm.VM) error {
|
||||||
|
hash, err := getBlockHashFromElement(ic.bc, v.Estack().Pop())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, err := ic.bc.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
v.Estack().PushVal([]byte{})
|
||||||
|
} else {
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(block))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetContract returns contract.
|
||||||
|
func (ic *interopContext) bcGetContract(v *vm.VM) error {
|
||||||
|
hashbytes := v.Estack().Pop().Bytes()
|
||||||
|
hash, err := util.Uint160DecodeBytes(hashbytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cs := ic.bc.GetContractState(hash)
|
||||||
|
if cs == nil {
|
||||||
|
v.Estack().PushVal([]byte{})
|
||||||
|
} else {
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(cs))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetHeader returns block header.
|
||||||
|
func (ic *interopContext) bcGetHeader(v *vm.VM) error {
|
||||||
|
hash, err := getBlockHashFromElement(ic.bc, v.Estack().Pop())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
header, err := ic.bc.GetHeader(hash)
|
||||||
|
if err != nil {
|
||||||
|
v.Estack().PushVal([]byte{})
|
||||||
|
} else {
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(header))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetHeight returns blockchain height.
|
||||||
|
func (ic *interopContext) bcGetHeight(v *vm.VM) error {
|
||||||
|
v.Estack().PushVal(ic.bc.BlockHeight())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTransactionAndHeight gets parameter from the vm evaluation stack and
|
||||||
|
// returns transaction and its height if it's present in the blockchain.
|
||||||
|
func getTransactionAndHeight(bc Blockchainer, v *vm.VM) (*transaction.Transaction, uint32, error) {
|
||||||
|
hashbytes := v.Estack().Pop().Bytes()
|
||||||
|
hash, err := util.Uint256DecodeReverseBytes(hashbytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return bc.GetTransaction(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetTransaction returns transaction.
|
||||||
|
func (ic *interopContext) bcGetTransaction(v *vm.VM) error {
|
||||||
|
tx, _, err := getTransactionAndHeight(ic.bc, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(tx))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcGetTransactionHeight returns transaction height.
|
||||||
|
func (ic *interopContext) bcGetTransactionHeight(v *vm.VM) error {
|
||||||
|
_, h, err := getTransactionAndHeight(ic.bc, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(h)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// popHeaderFromVM returns pointer to Header or error. It's main feature is
|
||||||
|
// proper treatment of Block structure, because C# code implicitly assumes
|
||||||
|
// that header APIs can also operate on blocks.
|
||||||
|
func popHeaderFromVM(v *vm.VM) (*Header, error) {
|
||||||
|
iface := v.Estack().Pop().Value()
|
||||||
|
header, ok := iface.(*Header)
|
||||||
|
if !ok {
|
||||||
|
block, ok := iface.(*Block)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("value is not a header or block")
|
||||||
|
}
|
||||||
|
return block.Header(), nil
|
||||||
|
}
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetIndex returns block index from the header.
|
||||||
|
func (ic *interopContext) headerGetIndex(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.Index)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetHash returns header hash of the passed header.
|
||||||
|
func (ic *interopContext) headerGetHash(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.Hash().BytesReverse())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetPrevHash returns previous header hash of the passed header.
|
||||||
|
func (ic *interopContext) headerGetPrevHash(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.PrevHash.BytesReverse())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerGetTimestamp returns timestamp of the passed header.
|
||||||
|
func (ic *interopContext) headerGetTimestamp(v *vm.VM) error {
|
||||||
|
header, err := popHeaderFromVM(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.Timestamp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockGetTransactionCount returns transactions count in the given block.
|
||||||
|
func (ic *interopContext) blockGetTransactionCount(v *vm.VM) error {
|
||||||
|
blockInterface := v.Estack().Pop().Value()
|
||||||
|
block, ok := blockInterface.(*Block)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a block")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(len(block.Transactions))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockGetTransactions returns transactions from the given block.
|
||||||
|
func (ic *interopContext) blockGetTransactions(v *vm.VM) error {
|
||||||
|
blockInterface := v.Estack().Pop().Value()
|
||||||
|
block, ok := blockInterface.(*Block)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a block")
|
||||||
|
}
|
||||||
|
if len(block.Transactions) > vm.MaxArraySize {
|
||||||
|
return errors.New("too many transactions")
|
||||||
|
}
|
||||||
|
txes := make([]vm.StackItem, 0, len(block.Transactions))
|
||||||
|
for _, tx := range block.Transactions {
|
||||||
|
txes = append(txes, vm.NewInteropItem(tx))
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(txes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockGetTransaction returns transaction with the given number from the given
|
||||||
|
// block.
|
||||||
|
func (ic *interopContext) blockGetTransaction(v *vm.VM) error {
|
||||||
|
blockInterface := v.Estack().Pop().Value()
|
||||||
|
block, ok := blockInterface.(*Block)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a block")
|
||||||
|
}
|
||||||
|
index := v.Estack().Pop().BigInt().Int64()
|
||||||
|
if index < 0 || index >= int64(len(block.Transactions)) {
|
||||||
|
return errors.New("wrong transaction index")
|
||||||
|
}
|
||||||
|
tx := block.Transactions[index]
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(tx))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txGetHash returns transaction's hash.
|
||||||
|
func (ic *interopContext) txGetHash(v *vm.VM) error {
|
||||||
|
txInterface := v.Estack().Pop().Value()
|
||||||
|
tx, ok := txInterface.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value is not a transaction")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(tx.Hash().BytesReverse())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// engineGetScriptContainer returns transaction that contains the script being
|
||||||
|
// run.
|
||||||
|
func (ic *interopContext) engineGetScriptContainer(v *vm.VM) error {
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(ic.tx))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushContextScriptHash returns script hash of the invocation stack element
|
||||||
|
// number n.
|
||||||
|
func getContextScriptHash(v *vm.VM, n int) util.Uint160 {
|
||||||
|
ctxIface := v.Istack().Peek(n).Value()
|
||||||
|
ctx := ctxIface.(*vm.Context)
|
||||||
|
return hash.Hash160(ctx.Program())
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushContextScriptHash pushes to evaluation stack the script hash of the
|
||||||
|
// invocation stack element number n.
|
||||||
|
func pushContextScriptHash(v *vm.VM, n int) error {
|
||||||
|
h := getContextScriptHash(v, n)
|
||||||
|
v.Estack().PushVal(h.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// engineGetExecutingScriptHash returns executing script hash.
|
||||||
|
func (ic *interopContext) engineGetExecutingScriptHash(v *vm.VM) error {
|
||||||
|
return pushContextScriptHash(v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// engineGetCallingScriptHash returns calling script hash.
|
||||||
|
func (ic *interopContext) engineGetCallingScriptHash(v *vm.VM) error {
|
||||||
|
return pushContextScriptHash(v, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// engineGetEntryScriptHash returns entry script hash.
|
||||||
|
func (ic *interopContext) engineGetEntryScriptHash(v *vm.VM) error {
|
||||||
|
return pushContextScriptHash(v, v.Istack().Len()-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimePlatform returns the name of the platform.
|
||||||
|
func (ic *interopContext) runtimePlatform(v *vm.VM) error {
|
||||||
|
v.Estack().PushVal([]byte("NEO"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeGetTrigger returns the script trigger.
|
||||||
|
func (ic *interopContext) runtimeGetTrigger(v *vm.VM) error {
|
||||||
|
v.Estack().PushVal(ic.trigger)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkHashedWitness checks given hash against current list of script hashes
|
||||||
|
// for verifying in the interop context.
|
||||||
|
func (ic *interopContext) checkHashedWitness(hash util.Uint160) (bool, error) {
|
||||||
|
hashes, err := ic.bc.GetScriptHashesForVerifying(ic.tx)
|
||||||
|
if err != nil {
|
||||||
|
return false, gherr.Wrap(err, "failed to get script hashes")
|
||||||
|
}
|
||||||
|
for _, v := range hashes {
|
||||||
|
if hash.Equals(v) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkKeyedWitness checks hash of signature check contract with a given public
|
||||||
|
// key against current list of script hashes for verifying in the interop context.
|
||||||
|
func (ic *interopContext) checkKeyedWitness(key *keys.PublicKey) (bool, error) {
|
||||||
|
script, err := smartcontract.CreateSignatureRedeemScript(key)
|
||||||
|
if err != nil {
|
||||||
|
return false, gherr.Wrap(err, "failed to create signature script for a key")
|
||||||
|
}
|
||||||
|
return ic.checkHashedWitness(hash.Hash160(script))
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeCheckWitness should check witnesses.
|
||||||
|
func (ic *interopContext) runtimeCheckWitness(v *vm.VM) error {
|
||||||
|
var res bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
hashOrKey := v.Estack().Pop().Bytes()
|
||||||
|
hash, err := util.Uint160DecodeBytes(hashOrKey)
|
||||||
|
if err != nil {
|
||||||
|
key := &keys.PublicKey{}
|
||||||
|
err = key.DecodeBytes(hashOrKey)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("parameter given is neither a key nor a hash")
|
||||||
|
}
|
||||||
|
res, err = ic.checkKeyedWitness(key)
|
||||||
|
} else {
|
||||||
|
res, err = ic.checkHashedWitness(hash)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return gherr.Wrap(err, "failed to check")
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeNotify should pass stack item to the notify plugin to handle it, but
|
||||||
|
// in neo-go the only meaningful thing to do here is to log.
|
||||||
|
func (ic *interopContext) runtimeNotify(v *vm.VM) error {
|
||||||
|
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes())
|
||||||
|
log.Infof("script %s notifies: %s", getContextScriptHash(v, 0), msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeLog log the message passed.
|
||||||
|
func (ic *interopContext) runtimeLog(v *vm.VM) error {
|
||||||
|
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes())
|
||||||
|
log.Infof("script %s logs: %s", getContextScriptHash(v, 0), msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeGetTime returns timestamp of the block being verified, or the latest
|
||||||
|
// one in the blockchain if no block is given to interopContext.
|
||||||
|
func (ic *interopContext) runtimeGetTime(v *vm.VM) error {
|
||||||
|
var header *Header
|
||||||
|
if ic.block == nil {
|
||||||
|
var err error
|
||||||
|
header, err = ic.bc.GetHeader(ic.bc.CurrentBlockHash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header = ic.block.Header()
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(header.Timestamp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// runtimeSerialize should serialize given stack item.
|
||||||
|
func (ic *interopContext) runtimeSerialize(v *vm.VM) error {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeDeserialize should deserialize given stack item.
|
||||||
|
func (ic *interopContext) runtimeDeserialize(v *vm.VM) error {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func (ic *interopContext) checkStorageContext(stc *StorageContext) error {
|
||||||
|
contract := ic.bc.GetContractState(stc.ScriptHash)
|
||||||
|
if contract == nil {
|
||||||
|
return errors.New("no contract found")
|
||||||
|
}
|
||||||
|
if !contract.HasStorage() {
|
||||||
|
return errors.New("contract can't have storage")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageDelete deletes stored key-value pair.
|
||||||
|
func (ic *interopContext) storageDelete(v *vm.VM) error {
|
||||||
|
if ic.trigger != 0x10 && ic.trigger != 0x11 {
|
||||||
|
return errors.New("can't delete when the trigger is not application")
|
||||||
|
}
|
||||||
|
stcInterface := v.Estack().Pop().Value()
|
||||||
|
stc, ok := stcInterface.(*StorageContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
|
}
|
||||||
|
if stc.ReadOnly {
|
||||||
|
return errors.New("StorageContext is read only")
|
||||||
|
}
|
||||||
|
err := ic.checkStorageContext(stc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := v.Estack().Pop().Bytes()
|
||||||
|
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
||||||
|
if si == nil {
|
||||||
|
si = ic.bc.GetStorageItem(stc.ScriptHash, key)
|
||||||
|
}
|
||||||
|
if si != nil && si.IsConst {
|
||||||
|
return errors.New("storage item is constant")
|
||||||
|
}
|
||||||
|
return deleteStorageItemInStore(ic.mem, stc.ScriptHash, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageGet returns stored key-value pair.
|
||||||
|
func (ic *interopContext) storageGet(v *vm.VM) error {
|
||||||
|
stcInterface := v.Estack().Pop().Value()
|
||||||
|
stc, ok := stcInterface.(*StorageContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
|
}
|
||||||
|
err := ic.checkStorageContext(stc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := v.Estack().Pop().Bytes()
|
||||||
|
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
||||||
|
if si == nil {
|
||||||
|
si = ic.bc.GetStorageItem(stc.ScriptHash, key)
|
||||||
|
}
|
||||||
|
if si != nil && si.Value != nil {
|
||||||
|
v.Estack().PushVal(si.Value)
|
||||||
|
} else {
|
||||||
|
v.Estack().PushVal([]byte{})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageGetContext returns storage context (scripthash).
|
||||||
|
func (ic *interopContext) storageGetContext(v *vm.VM) error {
|
||||||
|
sc := &StorageContext{
|
||||||
|
ScriptHash: getContextScriptHash(v, 0),
|
||||||
|
ReadOnly: false,
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(sc))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageGetReadOnlyContext returns read-only context (scripthash).
|
||||||
|
func (ic *interopContext) storageGetReadOnlyContext(v *vm.VM) error {
|
||||||
|
sc := &StorageContext{
|
||||||
|
ScriptHash: getContextScriptHash(v, 0),
|
||||||
|
ReadOnly: true,
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(sc))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ic *interopContext) putWithContextAndFlags(stc *StorageContext, key []byte, value []byte, isConst bool) error {
|
||||||
|
if ic.trigger != 0x10 && ic.trigger != 0x11 {
|
||||||
|
return errors.New("can't delete when the trigger is not application")
|
||||||
|
}
|
||||||
|
if len(key) > MaxStorageKeyLen {
|
||||||
|
return errors.New("key is too big")
|
||||||
|
}
|
||||||
|
if stc.ReadOnly {
|
||||||
|
return errors.New("StorageContext is read only")
|
||||||
|
}
|
||||||
|
err := ic.checkStorageContext(stc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
||||||
|
if si == nil {
|
||||||
|
si = ic.bc.GetStorageItem(stc.ScriptHash, key)
|
||||||
|
if si == nil {
|
||||||
|
si = &StorageItem{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if si.IsConst {
|
||||||
|
return errors.New("storage item exists and is read-only")
|
||||||
|
}
|
||||||
|
si.Value = value
|
||||||
|
si.IsConst = isConst
|
||||||
|
return putStorageItemIntoStore(ic.mem, stc.ScriptHash, key, si)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storagePutInternal is a unified implementation of storagePut and storagePutEx.
|
||||||
|
func (ic *interopContext) storagePutInternal(v *vm.VM, getFlag bool) error {
|
||||||
|
stcInterface := v.Estack().Pop().Value()
|
||||||
|
stc, ok := stcInterface.(*StorageContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
|
}
|
||||||
|
key := v.Estack().Pop().Bytes()
|
||||||
|
value := v.Estack().Pop().Bytes()
|
||||||
|
var flag int
|
||||||
|
if getFlag {
|
||||||
|
flag = int(v.Estack().Pop().BigInt().Int64())
|
||||||
|
}
|
||||||
|
return ic.putWithContextAndFlags(stc, key, value, flag == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storagePut puts key-value pair into the storage.
|
||||||
|
func (ic *interopContext) storagePut(v *vm.VM) error {
|
||||||
|
return ic.storagePutInternal(v, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storagePutEx puts key-value pair with given flags into the storage.
|
||||||
|
func (ic *interopContext) storagePutEx(v *vm.VM) error {
|
||||||
|
return ic.storagePutInternal(v, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageContextAsReadOnly sets given context to read-only mode.
|
||||||
|
func (ic *interopContext) storageContextAsReadOnly(v *vm.VM) error {
|
||||||
|
stcInterface := v.Estack().Pop().Value()
|
||||||
|
stc, ok := stcInterface.(*StorageContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
|
}
|
||||||
|
if !stc.ReadOnly {
|
||||||
|
stx := &StorageContext{
|
||||||
|
ScriptHash: stc.ScriptHash,
|
||||||
|
ReadOnly: true,
|
||||||
|
}
|
||||||
|
stc = stx
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(stc))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractDestroy destroys a contract.
|
||||||
|
func (ic *interopContext) contractDestroy(v *vm.VM) error {
|
||||||
|
if ic.trigger != 0x10 {
|
||||||
|
return errors.New("can't destroy contract when not triggered by application")
|
||||||
|
}
|
||||||
|
hash := getContextScriptHash(v, 0)
|
||||||
|
cs := ic.bc.GetContractState(hash)
|
||||||
|
if cs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := deleteContractStateInStore(ic.mem, hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cs.HasStorage() {
|
||||||
|
siMap, err := ic.bc.GetStorageItems(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k := range siMap {
|
||||||
|
_ = deleteStorageItemInStore(ic.mem, hash, []byte(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractGetStorageContext retrieves StorageContext of a contract.
|
||||||
|
func (ic *interopContext) contractGetStorageContext(v *vm.VM) error {
|
||||||
|
csInterface := v.Estack().Pop().Value()
|
||||||
|
cs, ok := csInterface.(*ContractState)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a contract state", cs)
|
||||||
|
}
|
||||||
|
if getContractStateFromStore(ic.mem, cs.ScriptHash()) == nil {
|
||||||
|
return fmt.Errorf("contract was not created in this transaction")
|
||||||
|
}
|
||||||
|
stc := &StorageContext{
|
||||||
|
ScriptHash: cs.ScriptHash(),
|
||||||
|
}
|
||||||
|
v.Estack().PushVal(vm.NewInteropItem(stc))
|
||||||
|
return nil
|
||||||
|
}
|
218
pkg/core/interops.go
Normal file
218
pkg/core/interops.go
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
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 (
|
||||||
|
"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
|
||||||
|
mem *storage.MemoryStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInteropContext(trigger byte, bc Blockchainer, block *Block, tx *transaction.Transaction) *interopContext {
|
||||||
|
mem := storage.NewMemoryStore()
|
||||||
|
return &interopContext{bc, trigger, block, tx, mem}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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.Blockchain.GetValidators": {Func: ic.bcGetValidators, 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},
|
||||||
|
|
||||||
|
// Aliases.
|
||||||
|
// "Neo.Iterator.Next": {Func: ic.enumeratorNext, Price: 1},
|
||||||
|
// "Neo.Iterator.Value": {Func: ic.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.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},
|
||||||
|
// "AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200},
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,7 +109,7 @@ func (chain testChain) IsLowPriority(*transaction.Transaction) bool {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain testChain) Verify(*transaction.Transaction) error {
|
func (chain testChain) VerifyTx(*transaction.Transaction, *core.Block) error {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -399,7 +399,7 @@ func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason {
|
||||||
if s.chain.HasTransaction(t.Hash()) {
|
if s.chain.HasTransaction(t.Hash()) {
|
||||||
return RelayAlreadyExists
|
return RelayAlreadyExists
|
||||||
}
|
}
|
||||||
if err := s.chain.Verify(t); err != nil {
|
if err := s.chain.VerifyTx(t, nil); err != nil {
|
||||||
return RelayInvalid
|
return RelayInvalid
|
||||||
}
|
}
|
||||||
// TODO: Implement Plugin.CheckPolicy?
|
// TODO: Implement Plugin.CheckPolicy?
|
||||||
|
|
Loading…
Reference in a new issue