native: support postPersist
method
It should be called for NEO contract to distribute committee bounties.
This commit is contained in:
parent
e8eb177c64
commit
c5cdaae87a
10 changed files with 143 additions and 35 deletions
|
@ -365,6 +365,19 @@ func (bc *Blockchain) notificationDispatcher() {
|
|||
ch <- tx
|
||||
}
|
||||
}
|
||||
|
||||
aer = event.appExecResults[aerIdx]
|
||||
if !aer.TxHash.Equals(event.block.Hash()) {
|
||||
panic("inconsistent application execution results")
|
||||
}
|
||||
for ch := range executionFeed {
|
||||
ch <- aer
|
||||
}
|
||||
for i := range aer.Events {
|
||||
for ch := range notificationFeed {
|
||||
ch <- &aer.Events[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
for ch := range blockFeed {
|
||||
ch <- event.block
|
||||
|
@ -528,7 +541,7 @@ func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
|
|||
func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
|
||||
cache := dao.NewCached(bc.dao)
|
||||
writeBuf := io.NewBufBinWriter()
|
||||
appExecResults := make([]*state.AppExecResult, 0, 1+len(block.Transactions))
|
||||
appExecResults := make([]*state.AppExecResult, 0, 2+len(block.Transactions))
|
||||
if err := cache.StoreAsBlock(block, writeBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -540,28 +553,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
writeBuf.Reset()
|
||||
|
||||
if block.Index > 0 {
|
||||
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
||||
v := systemInterop.SpawnVM()
|
||||
v.LoadScriptWithFlags(bc.contracts.GetPersistScript(), smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
||||
v.SetPriceGetter(getPrice)
|
||||
if err := v.Run(); err != nil {
|
||||
return fmt.Errorf("onPersist run failed: %w", err)
|
||||
} else if _, err := systemInterop.DAO.Persist(); err != nil {
|
||||
return fmt.Errorf("can't save onPersist changes: %w", err)
|
||||
}
|
||||
for i := range systemInterop.Notifications {
|
||||
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
||||
}
|
||||
aer := &state.AppExecResult{
|
||||
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
||||
Trigger: trigger.System,
|
||||
VMState: v.State(),
|
||||
GasConsumed: v.GasConsumed(),
|
||||
Stack: v.Estack().ToArray(),
|
||||
Events: systemInterop.Notifications,
|
||||
aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache)
|
||||
if err != nil {
|
||||
return fmt.Errorf("onPersist failed: %w", err)
|
||||
}
|
||||
appExecResults = append(appExecResults, aer)
|
||||
err := cache.PutAppExecResult(aer, writeBuf)
|
||||
err = cache.PutAppExecResult(aer, writeBuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store onPersist exec result: %w", err)
|
||||
}
|
||||
|
@ -611,6 +608,17 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
writeBuf.Reset()
|
||||
}
|
||||
|
||||
aer, err := bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postPersist failed: %w", err)
|
||||
}
|
||||
appExecResults = append(appExecResults, aer)
|
||||
err = cache.PutAppExecResult(aer, writeBuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store postPersist exec result: %w", err)
|
||||
}
|
||||
writeBuf.Reset()
|
||||
|
||||
root := bc.dao.MPT.StateRoot()
|
||||
var prevHash util.Uint256
|
||||
if block.Index > 0 {
|
||||
|
@ -620,7 +628,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
}
|
||||
prevHash = hash.DoubleSha256(prev.GetSignedPart())
|
||||
}
|
||||
err := bc.AddStateRoot(&state.MPTRoot{
|
||||
err = bc.AddStateRoot(&state.MPTRoot{
|
||||
MPTRootBase: state.MPTRootBase{
|
||||
Index: block.Index,
|
||||
PrevHash: prevHash,
|
||||
|
@ -664,6 +672,29 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Cached) (*state.AppExecResult, error) {
|
||||
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
||||
v := systemInterop.SpawnVM()
|
||||
v.LoadScriptWithFlags(script, smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
||||
v.SetPriceGetter(getPrice)
|
||||
if err := v.Run(); err != nil {
|
||||
return nil, fmt.Errorf("VM has failed: %w", err)
|
||||
} else if _, err := systemInterop.DAO.Persist(); err != nil {
|
||||
return nil, fmt.Errorf("can't save changes: %w", err)
|
||||
}
|
||||
for i := range systemInterop.Notifications {
|
||||
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
||||
}
|
||||
return &state.AppExecResult{
|
||||
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
||||
Trigger: trigger.System,
|
||||
VMState: v.State(),
|
||||
GasConsumed: v.GasConsumed(),
|
||||
Stack: v.Estack().ToArray(),
|
||||
Events: systemInterop.Notifications,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
|
||||
if note.Name != "transfer" && note.Name != "Transfer" {
|
||||
return
|
||||
|
|
|
@ -588,7 +588,7 @@ func TestSubscriptions(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
|
||||
assert.Len(t, notificationCh, 1) // validator bounty
|
||||
assert.Len(t, executionCh, 1)
|
||||
assert.Len(t, executionCh, 2)
|
||||
assert.Empty(t, txCh)
|
||||
|
||||
b := <-blockCh
|
||||
|
@ -597,6 +597,8 @@ func TestSubscriptions(t *testing.T) {
|
|||
|
||||
aer := <-executionCh
|
||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
||||
aer = <-executionCh
|
||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
||||
|
||||
notif := <-notificationCh
|
||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||
|
@ -669,11 +671,15 @@ func TestSubscriptions(t *testing.T) {
|
|||
}
|
||||
assert.Empty(t, txCh)
|
||||
assert.Len(t, notificationCh, 1)
|
||||
assert.Empty(t, executionCh)
|
||||
assert.Len(t, executionCh, 1)
|
||||
|
||||
notif = <-notificationCh
|
||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||
|
||||
exec = <-executionCh
|
||||
require.Equal(t, b.Hash(), exec.TxHash)
|
||||
require.Equal(t, exec.VMState, vm.HaltState)
|
||||
|
||||
bc.UnsubscribeFromBlocks(blockCh)
|
||||
bc.UnsubscribeFromTransactions(txCh)
|
||||
bc.UnsubscribeFromNotifications(notificationCh)
|
||||
|
|
|
@ -177,7 +177,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
priv0 := testchain.PrivateKeyByID(0)
|
||||
priv0ScriptHash := priv0.GetScriptHash()
|
||||
|
||||
require.Equal(t, big.NewInt(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
|
||||
require.Equal(t, big.NewInt(2500_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
|
||||
// Move some NEO to one simple account.
|
||||
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
|
||||
txMoveNeo.ValidUntilBlock = validUntilBlock
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"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/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
|
@ -16,6 +19,8 @@ type Contracts struct {
|
|||
Contracts []interop.Contract
|
||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||
persistScript []byte
|
||||
// postPersistScript is vm script which executes "postPersist" method of every native contract.
|
||||
postPersistScript []byte
|
||||
}
|
||||
|
||||
// ByHash returns native contract with the specified hash.
|
||||
|
@ -71,3 +76,33 @@ func (cs *Contracts) GetPersistScript() []byte {
|
|||
cs.persistScript = w.Bytes()
|
||||
return cs.persistScript
|
||||
}
|
||||
|
||||
// GetPostPersistScript returns VM script calling "postPersist" method of some native contracts.
|
||||
func (cs *Contracts) GetPostPersistScript() []byte {
|
||||
if cs.postPersistScript != nil {
|
||||
return cs.postPersistScript
|
||||
}
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range cs.Contracts {
|
||||
md := cs.Contracts[i].Metadata()
|
||||
// Not every contract is persisted:
|
||||
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103
|
||||
if md.ContractID == policyContractID || md.ContractID == gasContractID {
|
||||
continue
|
||||
}
|
||||
emit.Int(w.BinWriter, 0)
|
||||
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
|
||||
emit.String(w.BinWriter, "postPersist")
|
||||
emit.AppCall(w.BinWriter, md.Hash)
|
||||
emit.Opcode(w.BinWriter, opcode.DROP)
|
||||
}
|
||||
cs.postPersistScript = w.Bytes()
|
||||
return cs.postPersistScript
|
||||
}
|
||||
|
||||
func postPersistBase(ic *interop.Context) error {
|
||||
if ic.Trigger != trigger.System {
|
||||
return errors.New("'postPersist' should be trigered by system")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ func NewNEO() *NEO {
|
|||
nep5.decimals = 0
|
||||
nep5.factor = 1
|
||||
nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
|
||||
nep5.postPersist = chainOnPersist(nep5.postPersist, n.PostPersist)
|
||||
nep5.incBalance = n.increaseBalance
|
||||
nep5.ContractID = neoContractID
|
||||
|
||||
|
@ -103,6 +104,10 @@ func NewNEO() *NEO {
|
|||
onp.Func = getOnPersistWrapper(n.onPersist)
|
||||
n.Methods["onPersist"] = onp
|
||||
|
||||
pp := n.Methods["postPersist"]
|
||||
pp.Func = getOnPersistWrapper(n.postPersist)
|
||||
n.Methods["postPersist"] = pp
|
||||
|
||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("end", smartcontract.IntegerType))
|
||||
|
@ -226,7 +231,11 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostPersist implements Contract interface.
|
||||
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||
gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -31,11 +31,12 @@ func makeAccountKey(h util.Uint160) []byte {
|
|||
// nep5TokenNative represents NEP-5 token contract.
|
||||
type nep5TokenNative struct {
|
||||
interop.ContractMD
|
||||
symbol string
|
||||
decimals int64
|
||||
factor int64
|
||||
onPersist func(*interop.Context) error
|
||||
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
||||
symbol string
|
||||
decimals int64
|
||||
factor int64
|
||||
onPersist func(*interop.Context) error
|
||||
postPersist func(*interop.Context) error
|
||||
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
||||
}
|
||||
|
||||
// totalSupplyKey is the key used to store totalSupply value.
|
||||
|
@ -84,6 +85,10 @@ func newNEP5Native(name string) *nep5TokenNative {
|
|||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
n.AddEvent("Transfer", desc.Parameters...)
|
||||
|
||||
return n
|
||||
|
|
|
@ -124,6 +124,10 @@ func newPolicy() *Policy {
|
|||
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||
p.AddMethod(md, desc, false)
|
||||
|
||||
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||
p.AddMethod(md, desc, false)
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
|
@ -212,16 +212,16 @@ func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
|||
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
||||
}
|
||||
|
||||
bs := make(map[int]int64)
|
||||
const singleBounty = 25000000
|
||||
bs := map[int]int64{0: singleBounty}
|
||||
checkBalances := func() {
|
||||
for i := 0; i < testchain.CommitteeSize(); i++ {
|
||||
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64())
|
||||
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64(), i)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
bs[(i+1)%testchain.CommitteeSize()] += 25000000
|
||||
bs[(i+1)%testchain.CommitteeSize()] += singleBounty
|
||||
checkBalances()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1075,7 +1075,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "799.34495030",
|
||||
Amount: "799.59495030",
|
||||
LastUpdated: 7,
|
||||
}},
|
||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||
|
@ -1132,6 +1132,9 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
txReceiveNEO := blockReceiveGAS.Transactions[0]
|
||||
txReceiveGAS := blockReceiveGAS.Transactions[1]
|
||||
|
||||
blockGASBounty0, err := e.chain.GetBlock(e.chain.GetHeaderHash(0))
|
||||
require.NoError(t, err)
|
||||
|
||||
// These are laid out here explicitly for 2 purposes:
|
||||
// * to be able to reference any particular event for paging
|
||||
// * to check chain events consistency
|
||||
|
@ -1260,6 +1263,14 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
NotifyIndex: 0,
|
||||
TxHash: txReceiveNEO.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockGASBounty0.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "",
|
||||
Amount: "0.25000000",
|
||||
Index: 0,
|
||||
TxHash: blockGASBounty0.Hash(),
|
||||
},
|
||||
},
|
||||
Address: testchain.PrivateKeyByID(0).Address(),
|
||||
}
|
||||
|
|
|
@ -120,6 +120,13 @@ func TestSubscriptions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
resp = getNotification(t, respMsgs)
|
||||
require.Equal(t, response.ExecutionEventID, resp.Event)
|
||||
for {
|
||||
resp = getNotification(t, respMsgs)
|
||||
if resp.Event != response.NotificationEventID {
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, response.BlockEventID, resp.Event)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue