forked from TrueCloudLab/neoneo-go
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
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue