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.
This commit is contained in:
Evgenii Stratonikov 2020-06-17 13:57:54 +03:00
parent feb7d26e00
commit 0fa4c49735
7 changed files with 65 additions and 15 deletions

View file

@ -556,6 +556,17 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err 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 { for _, tx := range block.Transactions {
if err := cache.StoreAsTransaction(tx, block.Index); err != nil { if err := cache.StoreAsTransaction(tx, block.Index); err != nil {
return err 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 { if bc.config.SaveStorageBatch {
bc.lastBatch = cache.DAO.GetBatch() bc.lastBatch = cache.DAO.GetBatch()
} }

View file

@ -79,7 +79,6 @@ type MethodAndPrice struct {
type Contract interface { type Contract interface {
Initialize(*Context) error Initialize(*Context) error
Metadata() *ContractMD Metadata() *ContractMD
OnPersist(*Context) error
} }
// ContractMD represents native contract instance. // ContractMD represents native contract instance.

View file

@ -4,8 +4,10 @@ import (
"fmt" "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/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"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -14,6 +16,8 @@ type Contracts struct {
NEO *NEO NEO *NEO
GAS *GAS GAS *GAS
Contracts []interop.Contract 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. // ByHash returns native contract with the specified hash.
@ -53,6 +57,20 @@ func NewContracts() *Contracts {
return cs 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. // GetNativeInterop returns an interop getter for a given set of contracts.
func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice { func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice {
return func(id uint32) *vm.InteropFuncPrice { return func(id uint32) *vm.InteropFuncPrice {

View file

@ -34,12 +34,16 @@ func NewGAS() *GAS {
nep5.symbol = "gas" nep5.symbol = "gas"
nep5.decimals = 8 nep5.decimals = 8
nep5.factor = GASFactor nep5.factor = GASFactor
nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist) nep5.onPersist = chainOnPersist(nep5.OnPersist, g.OnPersist)
nep5.incBalance = g.increaseBalance nep5.incBalance = g.increaseBalance
nep5.ContractID = gasContractID nep5.ContractID = gasContractID
g.nep5TokenNative = *nep5 g.nep5TokenNative = *nep5
onp := g.Methods["onPersist"]
onp.Func = getOnPersistWrapper(g.onPersist)
g.Methods["onPersist"] = onp
return g return g
} }

View file

@ -68,12 +68,16 @@ func NewNEO() *NEO {
nep5.symbol = "neo" nep5.symbol = "neo"
nep5.decimals = 0 nep5.decimals = 0
nep5.factor = 1 nep5.factor = 1
nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist) nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
nep5.incBalance = n.increaseBalance nep5.incBalance = n.increaseBalance
nep5.ContractID = neoContractID nep5.ContractID = neoContractID
n.nep5TokenNative = *nep5 n.nep5TokenNative = *nep5
onp := n.Methods["onPersist"]
onp.Func = getOnPersistWrapper(n.onPersist)
n.Methods["onPersist"] = onp
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType, desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type), manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("end", smartcontract.IntegerType)) manifest.NewParameter("end", smartcontract.IntegerType))

View file

@ -10,6 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "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"
"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/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "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) md = newMethodAndPrice(n.Transfer, 1, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false) 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...) n.AddEvent("Transfer", desc.Parameters...)
return n 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 { 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 { 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 { func (s nep5ScriptHash) GetCurrentScriptHash() util.Uint160 {
return s.currentScriptHash 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)
}
}

View file

@ -1,7 +1,6 @@
package core package core
import ( import (
"errors"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "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/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/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"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -28,12 +28,15 @@ func (tn *testNative) Metadata() *interop.ContractMD {
return &tn.meta 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 { select {
case tn.blocks <- ic.Block.Index: case tn.blocks <- ic.Block.Index:
return nil return stackitem.NewBool(true)
default: default:
return errors.New("error on persist") return stackitem.NewBool(false)
} }
} }
@ -64,6 +67,10 @@ func newTestNative() *testNative {
} }
tn.meta.AddMethod(md, desc, true) 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 return tn
} }