core: decouple native contracts from interop service

Closes #1191.
This commit is contained in:
Anna Shaleva 2020-07-22 19:03:05 +03:00
parent b187dfe3ce
commit 4bf88ba6b0
14 changed files with 110 additions and 83 deletions

View file

@ -177,8 +177,10 @@ func TestCreateBasicChain(t *testing.T) {
gasHash := bc.contracts.GAS.Hash gasHash := bc.contracts.GAS.Hash
neoHash := bc.contracts.NEO.Hash neoHash := bc.contracts.NEO.Hash
policyHash := bc.contracts.Policy.Hash
t.Logf("native GAS hash: %v", gasHash) t.Logf("native GAS hash: %v", gasHash)
t.Logf("native NEO hash: %v", neoHash) t.Logf("native NEO hash: %v", neoHash)
t.Logf("native Policy hash: %v", policyHash)
priv0 := testchain.PrivateKeyByID(0) priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash() priv0ScriptHash := priv0.GetScriptHash()

View file

@ -82,25 +82,25 @@ type Contract interface {
// ContractMD represents native contract instance. // ContractMD represents native contract instance.
type ContractMD struct { type ContractMD struct {
Manifest manifest.Manifest Manifest manifest.Manifest
ServiceName string Name string
ServiceID uint32 ContractID int32
ContractID int32 Script []byte
Script []byte Hash util.Uint160
Hash util.Uint160 Methods map[string]MethodAndPrice
Methods map[string]MethodAndPrice
} }
// NewContractMD returns Contract with the specified list of methods. // NewContractMD returns Contract with the specified list of methods.
func NewContractMD(name string) *ContractMD { func NewContractMD(name string) *ContractMD {
c := &ContractMD{ c := &ContractMD{
ServiceName: name, Name: name,
ServiceID: emit.InteropNameToID([]byte(name)), Methods: make(map[string]MethodAndPrice),
Methods: make(map[string]MethodAndPrice),
} }
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.Syscall(w.BinWriter, c.ServiceName) emit.String(w.BinWriter, c.Name)
emit.Syscall(w.BinWriter, "Neo.Native.Call")
c.Script = w.Bytes() c.Script = w.Bytes()
c.Hash = hash.Hash160(c.Script) c.Hash = hash.Hash160(c.Script)
c.Manifest = *manifest.DefaultManifest(c.Hash) c.Manifest = *manifest.DefaultManifest(c.Hash)

View file

@ -28,9 +28,6 @@ func SpawnVM(ic *interop.Context) *vm.VM {
vm := vm.NewWithTrigger(ic.Trigger) vm := vm.NewWithTrigger(ic.Trigger)
vm.RegisterInteropGetter(getSystemInterop(ic)) vm.RegisterInteropGetter(getSystemInterop(ic))
vm.RegisterInteropGetter(getNeoInterop(ic)) vm.RegisterInteropGetter(getNeoInterop(ic))
if ic.Chain != nil {
vm.RegisterInteropGetter(ic.Chain.(*Blockchain).contracts.GetNativeInterop(ic))
}
ic.ScriptGetter = vm ic.ScriptGetter = vm
return vm return vm
} }
@ -129,6 +126,7 @@ var neoInterops = []interop.Function{
{Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0}, {Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0},
{Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000}, {Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000},
{Name: "Neo.Crypto.RIPEMD160", Func: crypto.RipeMD160, Price: 1000000}, {Name: "Neo.Crypto.RIPEMD160", Func: crypto.RipeMD160, Price: 1000000},
{Name: "Neo.Native.Call", Func: native.Call, Price: 0},
{Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.AllowModifyStates}, {Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.AllowModifyStates},
} }

View file

@ -1,15 +1,11 @@
package native package native
import ( import (
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/pkg/errors"
) )
// Contracts is a set of registered native contracts. // Contracts is a set of registered native contracts.
@ -32,16 +28,6 @@ func (cs *Contracts) ByHash(h util.Uint160) interop.Contract {
return nil return nil
} }
// ByID returns native contract with the specified id.
func (cs *Contracts) ByID(id uint32) interop.Contract {
for _, ctr := range cs.Contracts {
if ctr.Metadata().ServiceID == id {
return ctr
}
}
return nil
}
// NewContracts returns new set of native contracts with new GAS, NEO and Policy // NewContracts returns new set of native contracts with new GAS, NEO and Policy
// contracts. // contracts.
func NewContracts() *Contracts { func NewContracts() *Contracts {
@ -79,40 +65,3 @@ func (cs *Contracts) GetPersistScript() []byte {
cs.persistScript = w.Bytes() cs.persistScript = w.Bytes()
return cs.persistScript return cs.persistScript
} }
// GetNativeInterop returns an interop getter for a given set of contracts.
func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice {
return func(id uint32) *vm.InteropFuncPrice {
if c := cs.ByID(id); c != nil {
return &vm.InteropFuncPrice{
Func: getNativeInterop(ic, c),
}
}
return nil
}
}
// getNativeInterop returns native contract interop.
func getNativeInterop(ic *interop.Context, c interop.Contract) func(v *vm.VM) error {
return func(v *vm.VM) error {
h := v.GetCurrentScriptHash()
if !h.Equals(c.Metadata().Hash) {
return errors.New("invalid hash")
}
name := string(v.Estack().Pop().Bytes())
args := v.Estack().Pop().Array()
m, ok := c.Metadata().Methods[name]
if !ok {
return fmt.Errorf("method %s not found", name)
}
if !v.Context().GetCallFlags().Has(m.RequiredFlags) {
return errors.New("missing call flags")
}
if !v.AddGas(m.Price) {
return errors.New("gas limit exceeded")
}
result := m.Func(ic, args)
v.Estack().PushVal(result)
return nil
}
}

View file

@ -27,8 +27,42 @@ func Deploy(ic *interop.Context, _ *vm.VM) error {
return err return err
} }
if err := native.Initialize(ic); err != nil { if err := native.Initialize(ic); err != nil {
return fmt.Errorf("initializing %s native contract: %v", md.ServiceName, err) return fmt.Errorf("initializing %s native contract: %v", md.Name, err)
} }
} }
return nil return nil
} }
// Call calls specified native contract method.
func Call(ic *interop.Context, v *vm.VM) error {
name := string(v.Estack().Pop().Bytes())
var c interop.Contract
for _, ctr := range ic.Natives {
if ctr.Metadata().Name == name {
c = ctr
break
}
}
if c == nil {
return fmt.Errorf("native contract %s not found", name)
}
h := v.GetCurrentScriptHash()
if !h.Equals(c.Metadata().Hash) {
return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used")
}
operation := string(v.Estack().Pop().Bytes())
args := v.Estack().Pop().Array()
m, ok := c.Metadata().Methods[operation]
if !ok {
return fmt.Errorf("method %s not found", operation)
}
if !v.Context().GetCallFlags().Has(m.RequiredFlags) {
return errors.New("missing call flags")
}
if !v.AddGas(m.Price) {
return errors.New("gas limit exceeded")
}
result := m.Func(ic, args)
v.Estack().PushVal(result)
return nil
}

View file

@ -19,7 +19,7 @@ type GAS struct {
NEO *NEO NEO *NEO
} }
const gasSyscallName = "Neo.Native.Tokens.GAS" const gasName = "GAS"
const gasContractID = -2 const gasContractID = -2
// GASFactor is a divisor for finding GAS integral value. // GASFactor is a divisor for finding GAS integral value.
@ -29,8 +29,7 @@ const initialGAS = 30000000
// NewGAS returns GAS native contract. // NewGAS returns GAS native contract.
func NewGAS() *GAS { func NewGAS() *GAS {
g := &GAS{} g := &GAS{}
nep5 := newNEP5Native(gasSyscallName) nep5 := newNEP5Native(gasName)
nep5.name = "GAS"
nep5.symbol = "gas" nep5.symbol = "gas"
nep5.decimals = 8 nep5.decimals = 8
nep5.factor = GASFactor nep5.factor = GASFactor

View file

@ -38,8 +38,8 @@ type keyWithVotes struct {
} }
const ( const (
neoSyscallName = "Neo.Native.Tokens.NEO" neoName = "NEO"
neoContractID = -1 neoContractID = -1
// NEOTotalSupply is the total amount of NEO in the system. // NEOTotalSupply is the total amount of NEO in the system.
NEOTotalSupply = 100000000 NEOTotalSupply = 100000000
// prefixValidator is a prefix used to store validator's data. // prefixValidator is a prefix used to store validator's data.
@ -68,8 +68,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
// NewNEO returns NEO native contract. // NewNEO returns NEO native contract.
func NewNEO() *NEO { func NewNEO() *NEO {
n := &NEO{} n := &NEO{}
nep5 := newNEP5Native(neoSyscallName) nep5 := newNEP5Native(neoName)
nep5.name = "NEO"
nep5.symbol = "neo" nep5.symbol = "neo"
nep5.decimals = 0 nep5.decimals = 0
nep5.factor = 1 nep5.factor = 1

View file

@ -29,7 +29,6 @@ func makeAccountKey(h util.Uint160) []byte {
// nep5TokenNative represents NEP-5 token contract. // nep5TokenNative represents NEP-5 token contract.
type nep5TokenNative struct { type nep5TokenNative struct {
interop.ContractMD interop.ContractMD
name string
symbol string symbol string
decimals int64 decimals int64
factor int64 factor int64
@ -92,7 +91,7 @@ func (c *nep5TokenNative) Initialize(_ *interop.Context) error {
} }
func (c *nep5TokenNative) Name(_ *interop.Context, _ []stackitem.Item) stackitem.Item { func (c *nep5TokenNative) Name(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewByteArray([]byte(c.name)) return stackitem.NewByteArray([]byte(c.ContractMD.Name))
} }
func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item { func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {

View file

@ -19,8 +19,8 @@ import (
) )
const ( const (
policySyscallName = "Neo.Native.Policy" policyName = "Policy"
policyContractID = -3 policyContractID = -3
defaultMaxBlockSize = 1024 * 256 defaultMaxBlockSize = 1024 * 256
defaultMaxTransactionsPerBlock = 512 defaultMaxTransactionsPerBlock = 512
@ -59,7 +59,7 @@ var _ interop.Contract = (*Policy)(nil)
// newPolicy returns Policy native contract. // newPolicy returns Policy native contract.
func newPolicy() *Policy { func newPolicy() *Policy {
p := &Policy{ContractMD: *interop.NewContractMD(policySyscallName)} p := &Policy{ContractMD: *interop.NewContractMD(policyName)}
p.ContractID = policyContractID p.ContractID = policyContractID
p.Manifest.Features |= smartcontract.HasStorage p.Manifest.Features |= smartcontract.HasStorage

View file

@ -1,15 +1,21 @@
package core package core
import ( import (
"math/big"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -140,3 +146,44 @@ func TestNativeContract_Invoke(t *testing.T) {
require.Fail(t, "onPersist wasn't called") require.Fail(t, "onPersist wasn't called")
} }
} }
func TestNativeContract_InvokeInternal(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
tn := newTestNative()
chain.registerNative(tn)
err := chain.dao.PutContractState(&state.Contract{
Script: tn.meta.Script,
Manifest: tn.meta.Manifest,
})
require.NoError(t, err)
v := vm.New()
v.GasLimit = -1
ic := chain.newInteropContext(trigger.Application,
dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil)
t.Run("fail, bad current script hash", func(t *testing.T) {
v.LoadScriptWithHash([]byte{1}, util.Uint160{1, 2, 3}, smartcontract.All)
v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(14)), stackitem.NewBigInteger(big.NewInt(28))}))
v.Estack().PushVal("sum")
v.Estack().PushVal(tn.Metadata().Name)
// it's prohibited to call natives directly
require.Error(t, native.Call(ic, v))
})
t.Run("success", func(t *testing.T) {
v.LoadScriptWithHash([]byte{1}, tn.Metadata().Hash, smartcontract.All)
v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(14)), stackitem.NewBigInteger(big.NewInt(28))}))
v.Estack().PushVal("sum")
v.Estack().PushVal(tn.Metadata().Name)
require.NoError(t, native.Call(ic, v))
value := v.Estack().Pop().BigInt()
require.Equal(t, int64(42), value.Int64())
})
}

View file

@ -23,9 +23,9 @@ type AddrAndAmount struct {
var ( var (
// NeoContractHash is a hash of the NEO native contract. // NeoContractHash is a hash of the NEO native contract.
NeoContractHash, _ = util.Uint160DecodeStringLE("9bde8f209c88dd0e7ca3bf0af0f476cdd8207789") NeoContractHash, _ = util.Uint160DecodeStringBE("25059ecb4878d3a875f91c51ceded330d4575fde")
// GasContractHash is a hash of the GAS native contract. // GasContractHash is a hash of the GAS native contract.
GasContractHash, _ = util.Uint160DecodeStringLE("8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b") GasContractHash, _ = util.Uint160DecodeStringBE("bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e66")
) )
// NEP5Decimals invokes `decimals` NEP5 method on a specified contract. // NEP5Decimals invokes `decimals` NEP5 method on a specified contract.

View file

@ -9,8 +9,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// PolicyContractHash represents BE hash of native Policy contract. // PolicyContractHash represents a hash of native Policy contract.
var PolicyContractHash = util.Uint160{154, 97, 164, 110, 236, 151, 184, 147, 6, 215, 206, 129, 241, 91, 70, 32, 145, 208, 9, 50} var PolicyContractHash, _ = util.Uint160DecodeStringBE("e9ff4ca7cc252e1dfddb26315869cd79505906ce")
// GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a // GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a
// native Policy contract. // native Policy contract.

View file

@ -52,7 +52,7 @@ type rpcTestCase struct {
} }
const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672" const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672"
const deploymentTxHash = "dcf4fe429ec84947361c86c2192b14641be7f0c6e2bdf8d150fad731160ed386" const deploymentTxHash = "ef4209bc06e1d8412995c645a8497d3e2c9a05ca52236de94297c6db9c3e94d0"
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {

Binary file not shown.