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:
parent
feb7d26e00
commit
0fa4c49735
7 changed files with 65 additions and 15 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -79,7 +79,6 @@ type MethodAndPrice struct {
|
|||
type Contract interface {
|
||||
Initialize(*Context) error
|
||||
Metadata() *ContractMD
|
||||
OnPersist(*Context) error
|
||||
}
|
||||
|
||||
// ContractMD represents native contract instance.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue