From 0fa4c497352bd225c5035c0aa453d5fc3ee5d80f Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 17 Jun 2020 13:57:54 +0300 Subject: [PATCH] core,native: persist native contract via VM After native contracts are deployed, single persist script is created and executed at the start of every block persisting. --- pkg/core/blockchain.go | 18 +++++++++++------- pkg/core/interop/context.go | 1 - pkg/core/native/contract.go | 18 ++++++++++++++++++ pkg/core/native/native_gas.go | 6 +++++- pkg/core/native/native_neo.go | 6 +++++- pkg/core/native/native_nep5.go | 16 +++++++++++++++- pkg/core/native_contract_test.go | 15 +++++++++++---- 7 files changed, 65 insertions(+), 15 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index fdf1a97f8..6beb28af2 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -556,6 +556,17 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { return err } + if block.Index > 0 { + systemInterop := bc.newInteropContext(trigger.System, cache, block, nil) + v := SpawnVM(systemInterop) + v.LoadScriptWithFlags(bc.contracts.GetPersistScript(), smartcontract.AllowModifyStates|smartcontract.AllowCall) + if err := v.Run(); err != nil { + return errors.Wrap(err, "can't persist native contracts") + } else if _, err := systemInterop.DAO.Persist(); err != nil { + return errors.Wrap(err, "can't persist `onPersist` changes") + } + } + for _, tx := range block.Transactions { if err := cache.StoreAsTransaction(tx, block.Index); err != nil { return err @@ -633,13 +644,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { } } - for i := range bc.contracts.Contracts { - systemInterop := bc.newInteropContext(trigger.Application, cache, block, nil) - if err := bc.contracts.Contracts[i].OnPersist(systemInterop); err != nil { - return err - } - } - if bc.config.SaveStorageBatch { bc.lastBatch = cache.DAO.GetBatch() } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index b0b59c586..af39d9aca 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -79,7 +79,6 @@ type MethodAndPrice struct { type Contract interface { Initialize(*Context) error Metadata() *ContractMD - OnPersist(*Context) error } // ContractMD represents native contract instance. diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 34b5cf578..3338551ab 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -4,8 +4,10 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/io" "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/pkg/errors" ) @@ -14,6 +16,8 @@ type Contracts struct { NEO *NEO GAS *GAS Contracts []interop.Contract + // persistScript is vm script which executes "onPersist" method of every native contract. + persistScript []byte } // ByHash returns native contract with the specified hash. @@ -53,6 +57,20 @@ func NewContracts() *Contracts { return cs } +// GetPersistScript returns VM script calling "onPersist" method of every native contract. +func (cs *Contracts) GetPersistScript() []byte { + if cs.persistScript != nil { + return cs.persistScript + } + w := io.NewBufBinWriter() + for i := range cs.Contracts { + md := cs.Contracts[i].Metadata() + emit.AppCallWithOperationAndArgs(w.BinWriter, md.Hash, "onPersist") + } + cs.persistScript = w.Bytes() + 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 { diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index ea98b80f3..4d76ceac7 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -34,12 +34,16 @@ func NewGAS() *GAS { nep5.symbol = "gas" nep5.decimals = 8 nep5.factor = GASFactor - nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist) + nep5.onPersist = chainOnPersist(nep5.OnPersist, g.OnPersist) nep5.incBalance = g.increaseBalance nep5.ContractID = gasContractID g.nep5TokenNative = *nep5 + onp := g.Methods["onPersist"] + onp.Func = getOnPersistWrapper(g.onPersist) + g.Methods["onPersist"] = onp + return g } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 65024ee12..bf48b7baf 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -68,12 +68,16 @@ func NewNEO() *NEO { nep5.symbol = "neo" nep5.decimals = 0 nep5.factor = 1 - nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist) + nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist) nep5.incBalance = n.increaseBalance nep5.ContractID = neoContractID n.nep5TokenNative = *nep5 + onp := n.Methods["onPersist"] + onp.Func = getOnPersistWrapper(n.onPersist) + n.Methods["onPersist"] = onp + desc := newDescriptor("unclaimedGas", smartcontract.IntegerType, manifest.NewParameter("account", smartcontract.Hash160Type), manifest.NewParameter("end", smartcontract.IntegerType)) diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index ada5bbf43..6bf39228f 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -10,6 +10,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "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/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -77,6 +78,10 @@ func newNEP5Native(name string) *nep5TokenNative { md = newMethodAndPrice(n.Transfer, 1, smartcontract.AllowModifyStates) n.AddMethod(md, desc, false) + desc = newDescriptor("onPersist", smartcontract.BoolType) + md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates) + n.AddMethod(md, desc, false) + n.AddEvent("Transfer", desc.Parameters...) return n @@ -250,7 +255,10 @@ func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount } func (c *nep5TokenNative) OnPersist(ic *interop.Context) error { - return c.onPersist(ic) + if ic.Trigger != trigger.System { + return errors.New("onPersist should be triggerred by system") + } + return nil } func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method { @@ -311,3 +319,9 @@ func (s nep5ScriptHash) GetEntryScriptHash() util.Uint160 { func (s nep5ScriptHash) GetCurrentScriptHash() util.Uint160 { return s.currentScriptHash } + +func getOnPersistWrapper(f func(ic *interop.Context) error) interop.Method { + return func(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBool(f(ic) == nil) + } +} diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 2d9d901aa..b3eed1ae7 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -1,7 +1,6 @@ package core import ( - "errors" "testing" "github.com/nspcc-dev/neo-go/pkg/core/interop" @@ -10,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "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/trigger" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" @@ -28,12 +28,15 @@ func (tn *testNative) Metadata() *interop.ContractMD { return &tn.meta } -func (tn *testNative) OnPersist(ic *interop.Context) error { +func (tn *testNative) OnPersist(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + if ic.Trigger != trigger.System { + panic("invalid trigger") + } select { case tn.blocks <- ic.Block.Index: - return nil + return stackitem.NewBool(true) default: - return errors.New("error on persist") + return stackitem.NewBool(false) } } @@ -64,6 +67,10 @@ func newTestNative() *testNative { } tn.meta.AddMethod(md, desc, true) + desc = &manifest.Method{Name: "onPersist", ReturnType: smartcontract.BoolType} + md = &interop.MethodAndPrice{Func: tn.OnPersist, RequiredFlags: smartcontract.AllowModifyStates} + tn.meta.AddMethod(md, desc, false) + return tn }