From 7d90d79ae61e5826d9f7e436e5cf734dd78c9703 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 26 Aug 2020 12:07:30 +0300 Subject: [PATCH 1/8] core: update claimable GAS calculation --- pkg/core/blockchain.go | 43 ++--------------- pkg/core/blockchain_test.go | 19 +------- pkg/core/native/native_neo.go | 56 ++++++++++++++++++++++- pkg/core/native_neo_test.go | 17 +++++++ pkg/core/state/native_state.go | 71 +++++++++++++++++++++++++++++ pkg/core/state/native_state_test.go | 40 ++++++++++++++++ pkg/core/util.go | 8 ---- pkg/rpc/server/server_test.go | 6 +-- 8 files changed, 191 insertions(+), 69 deletions(-) create mode 100644 pkg/core/state/native_state_test.go diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 0e07669ee..ec94e57e4 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -57,9 +57,7 @@ var ( ErrInvalidBlockIndex error = errors.New("invalid block index") ) var ( - genAmount = []int{6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - decrementInterval = 2000000 - persistInterval = 1 * time.Second + persistInterval = 1 * time.Second ) // Blockchain represents the blockchain. It maintans internal state representing @@ -98,9 +96,6 @@ type Blockchain struct { // Number of headers stored in the chain file. storedHeaderCount uint32 - generationAmount []int - decrementInterval int - // Header hashes list with associated lock. headerHashesLock sync.RWMutex headerHashes []util.Uint256 @@ -162,9 +157,6 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L subCh: make(chan interface{}), unsubCh: make(chan interface{}), - generationAmount: genAmount, - decrementInterval: decrementInterval, - contracts: *native.NewContracts(), } @@ -1083,36 +1075,11 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult) } // CalculateClaimable calculates the amount of GAS generated by owning specified -// amount of NEO between specified blocks. The amount of NEO being passed is in -// its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by -// 10⁸ is needed as for Fixed8). +// amount of NEO between specified blocks. func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int { - var amount int64 - di := uint32(bc.decrementInterval) - - ustart := startHeight / di - if genSize := uint32(len(bc.generationAmount)); ustart < genSize { - uend := endHeight / di - iend := endHeight % di - if uend >= genSize { - uend = genSize - 1 - iend = di - } else if iend == 0 { - uend-- - iend = di - } - - istart := startHeight % di - for ustart < uend { - amount += int64(di-istart) * int64(bc.generationAmount[ustart]) - ustart++ - istart = 0 - } - - amount += int64(iend-istart) * int64(bc.generationAmount[ustart]) - } - - return new(big.Int).Mul(big.NewInt(amount), value) + ic := bc.newInteropContext(trigger.System, bc.dao, nil, nil) + res, _ := bc.contracts.NEO.CalculateBonus(ic, value, startHeight, endHeight) + return res } // FeePerByte returns transaction network fee per byte. diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 7d2732ed1..d5b7b5db4 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -536,29 +536,12 @@ func TestGetClaimable(t *testing.T) { bc := newTestChain(t) defer bc.Close() - bc.generationAmount = []int{4, 3, 2, 1} - bc.decrementInterval = 2 _, err := bc.genBlocks(10) require.NoError(t, err) t.Run("first generation period", func(t *testing.T) { amount := bc.CalculateClaimable(big.NewInt(1), 0, 2) - require.EqualValues(t, big.NewInt(8), amount) - }) - - t.Run("a number of full periods", func(t *testing.T) { - amount := bc.CalculateClaimable(big.NewInt(1), 0, 6) - require.EqualValues(t, big.NewInt(4+4+3+3+2+2), amount) - }) - - t.Run("start from the 2-nd block", func(t *testing.T) { - amount := bc.CalculateClaimable(big.NewInt(1), 1, 7) - require.EqualValues(t, big.NewInt(4+3+3+2+2+1), amount) - }) - - t.Run("end height after generation has ended", func(t *testing.T) { - amount := bc.CalculateClaimable(big.NewInt(1), 1, 10) - require.EqualValues(t, big.NewInt(4+3+3+2+2+1+1), amount) + require.EqualValues(t, big.NewInt(1), amount) }) } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index c8486d005..8680ae5ef 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -48,9 +48,17 @@ const ( prefixCandidate = 33 // prefixVotersCount is a prefix for storing total amount of NEO of voters. prefixVotersCount = 1 + // prefixGasPerBlock is a prefix for storing amount of GAS generated per block. + prefixGASPerBlock = 29 // effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value // which is require to use non-standby validators. effectiveVoterTurnout = 5 + // neoHolderRewardRatio is a percent of generated GAS that is distributed to NEO holders. + neoHolderRewardRatio = 10 + // neoHolderRewardRatio is a percent of generated GAS that is distributed to committee. + committeeRewardRatio = 5 + // neoHolderRewardRatio is a percent of generated GAS that is distributed to voters. + voterRewardRatio = 85 ) var ( @@ -146,6 +154,11 @@ func (n *NEO) Initialize(ic *interop.Context) error { } n.mint(ic, h, big.NewInt(NEOTotalSupply)) + gr := &state.GASRecord{{Index: 0, GASPerBlock: *big.NewInt(5 * GASFactor)}} + err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()}) + if err != nil { + return err + } err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{}}) if err != nil { return err @@ -219,7 +232,10 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB if ic.Block == nil || ic.Block.Index == 0 { return nil } - gen := ic.Chain.CalculateClaimable(&acc.Balance, acc.BalanceHeight, ic.Block.Index) + gen, err := n.CalculateBonus(ic, &acc.Balance, acc.BalanceHeight, ic.Block.Index) + if err != nil { + return err + } acc.BalanceHeight = ic.Block.Index n.GAS.mint(ic, h, gen) return nil @@ -234,10 +250,46 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem } tr := bs.Trackers[n.ContractID] - gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end) + gen, err := n.CalculateBonus(ic, &tr.Balance, tr.LastUpdatedBlock, end) + if err != nil { + panic(err) + } return stackitem.NewBigInteger(gen) } +// CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block. +func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) { + if value.Sign() == 0 || start >= end { + return big.NewInt(0), nil + } else if value.Sign() < 0 { + return nil, errors.New("negative value") + } + si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) + var gr state.GASRecord + if err := gr.FromBytes(si.Value); err != nil { + return nil, err + } + var sum, tmp big.Int + for i := len(gr) - 1; i >= 0; i-- { + if gr[i].Index >= end { + continue + } else if gr[i].Index <= start { + tmp.SetInt64(int64(end - start)) + tmp.Mul(&tmp, &gr[i].GASPerBlock) + sum.Add(&sum, &tmp) + break + } + tmp.SetInt64(int64(end - gr[i].Index)) + tmp.Mul(&tmp, &gr[i].GASPerBlock) + sum.Add(&sum, &tmp) + end = gr[i].Index + } + res := new(big.Int).Mul(value, &sum) + res.Mul(res, tmp.SetInt64(neoHolderRewardRatio)) + res.Div(res, tmp.SetInt64(100*NEOTotalSupply)) + return res, nil +} + func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item { pub := toPublicKey(args[0]) ok, err := runtime.CheckKeyedWitness(ic, pub) diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 2fc60d29b..becdf5ce9 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -108,3 +108,20 @@ func TestNEO_Vote(t *testing.T) { require.NotEqual(t, candidates[0], pubs[i]) } } + +func TestNEO_CalculateBonus(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + neo := bc.contracts.NEO + ic := bc.newInteropContext(trigger.System, bc.dao, nil, nil) + t.Run("Invalid", func(t *testing.T) { + _, err := neo.CalculateBonus(ic, new(big.Int).SetInt64(-1), 0, 1) + require.Error(t, err) + }) + t.Run("Zero", func(t *testing.T) { + res, err := neo.CalculateBonus(ic, big.NewInt(0), 0, 100) + require.NoError(t, err) + require.EqualValues(t, 0, res.Int64()) + }) +} diff --git a/pkg/core/state/native_state.go b/pkg/core/state/native_state.go index 408daddb5..c6cfcd1ff 100644 --- a/pkg/core/state/native_state.go +++ b/pkg/core/state/native_state.go @@ -2,6 +2,7 @@ package state import ( "crypto/elliptic" + "errors" "math/big" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -21,6 +22,76 @@ type NEOBalanceState struct { VoteTo *keys.PublicKey } +// GASIndexPair contains block index together with generated gas per block. +type GASIndexPair struct { + Index uint32 + GASPerBlock big.Int +} + +// GASRecord contains history of gas per block changes. +type GASRecord []GASIndexPair + +// Bytes serializes g to []byte. +func (g *GASRecord) Bytes() []byte { + w := io.NewBufBinWriter() + g.EncodeBinary(w.BinWriter) + return w.Bytes() +} + +// FromBytes deserializes g from data. +func (g *GASRecord) FromBytes(data []byte) error { + r := io.NewBinReaderFromBuf(data) + g.DecodeBinary(r) + return r.Err +} + +// DecodeBinary implements io.Serializable. +func (g *GASRecord) DecodeBinary(r *io.BinReader) { + item := stackitem.DecodeBinaryStackItem(r) + if r.Err == nil { + r.Err = g.fromStackItem(item) + } +} + +// EncodeBinary implements io.Serializable. +func (g *GASRecord) EncodeBinary(w *io.BinWriter) { + item := g.toStackItem() + stackitem.EncodeBinaryStackItem(item, w) +} + +// toStackItem converts GASRecord to a stack item. +func (g *GASRecord) toStackItem() stackitem.Item { + items := make([]stackitem.Item, len(*g)) + for i := range items { + items[i] = stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64((*g)[i].Index))), + stackitem.NewBigInteger(&(*g)[i].GASPerBlock), + }) + } + return stackitem.NewArray(items) +} + +var errInvalidFormat = errors.New("invalid item format") + +// fromStackItem converts item to a GASRecord. +func (g *GASRecord) fromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errInvalidFormat + } + for i := range arr { + s, ok := arr[i].Value().([]stackitem.Item) + if !ok || len(s) != 2 || s[0].Type() != stackitem.IntegerT || s[1].Type() != stackitem.IntegerT { + return errInvalidFormat + } + *g = append(*g, GASIndexPair{ + Index: uint32(s[0].Value().(*big.Int).Uint64()), + GASPerBlock: *s[1].Value().(*big.Int), + }) + } + return nil +} + // NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure. func NEP5BalanceStateFromBytes(b []byte) (*NEP5BalanceState, error) { balance := new(NEP5BalanceState) diff --git a/pkg/core/state/native_state_test.go b/pkg/core/state/native_state_test.go new file mode 100644 index 000000000..920a7f331 --- /dev/null +++ b/pkg/core/state/native_state_test.go @@ -0,0 +1,40 @@ +package state + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestGASRecord_EncodeBinary(t *testing.T) { + expected := &GASRecord{ + GASIndexPair{ + Index: 1, + GASPerBlock: *big.NewInt(123), + }, + GASIndexPair{ + Index: 2, + GASPerBlock: *big.NewInt(7), + }, + } + testserdes.EncodeDecodeBinary(t, expected, new(GASRecord)) +} + +func TestGASRecord_fromStackItem(t *testing.T) { + t.Run("NotArray", func(t *testing.T) { + item := stackitem.Null{} + require.Error(t, new(GASRecord).fromStackItem(item)) + }) + t.Run("InvalidFormat", func(t *testing.T) { + item := stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(1)), + stackitem.NewBool(true), + }), + }) + require.Error(t, new(GASRecord).fromStackItem(item)) + }) +} diff --git a/pkg/core/util.go b/pkg/core/util.go index 5b6cbcfa0..9ce81e47b 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -123,14 +123,6 @@ func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, er return hash.Hash160(raw), nil } -func calculateUtilityAmount() util.Fixed8 { - sum := 0 - for i := 0; i < len(genAmount); i++ { - sum += genAmount[i] - } - return util.Fixed8FromInt64(int64(sum * decrementInterval)) -} - // headerSliceReverse reverses the given slice of *Header. func headerSliceReverse(dest []*block.Header) { for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index cb1df850a..eb2bc89c7 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -485,7 +485,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) expected := result.UnclaimedGas{ Address: testchain.MultisigScriptHash(), - Unclaimed: *big.NewInt(42000), + Unclaimed: *big.NewInt(3500), } assert.Equal(t, expected, *actual) }, @@ -1075,7 +1075,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "815.59478530", + Amount: "799.09495030", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), @@ -1227,7 +1227,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv Timestamp: blockSendNEO.Timestamp, Asset: e.chain.UtilityTokenHash(), Address: "", // Minted GAS. - Amount: "17.99982000", + Amount: "1.49998500", Index: 4, NotifyIndex: 0, TxHash: txSendNEO.Hash(), From 5a382083613ee70282e2411662411ec70cdcd54a Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 26 Aug 2020 13:06:19 +0300 Subject: [PATCH 2/8] native: implement `NEO.Get/SetMaxGasPerBlock()` --- pkg/core/native/native_neo.go | 84 +++++++++++++++++++++++++++++++++++ pkg/core/native_neo_test.go | 70 ++++++++++++++++++++++++++++- 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 8680ae5ef..277594215 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -13,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -135,6 +136,15 @@ func NewNEO() *NEO { md = newMethodAndPrice(n.getNextBlockValidators, 100000000, smartcontract.AllowStates) n.AddMethod(md, desc, true) + desc = newDescriptor("getGasPerBlock", smartcontract.IntegerType) + md = newMethodAndPrice(n.getGASPerBlock, 100_0000, smartcontract.AllowStates) + n.AddMethod(md, desc, false) + + desc = newDescriptor("setGasPerBlock", smartcontract.BoolType, + manifest.NewParameter("gasPerBlock", smartcontract.IntegerType)) + md = newMethodAndPrice(n.setGASPerBlock, 500_0000, smartcontract.AllowModifyStates) + n.AddMethod(md, desc, false) + return n } @@ -257,6 +267,80 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem return stackitem.NewBigInteger(gen) } +func (n *NEO) getGASPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + gas, err := n.GetGASPerBlock(ic, ic.Block.Index) + if err != nil { + panic(err) + } + return stackitem.NewBigInteger(gas) +} + +// GetGASPerBlock returns gas generated for block with provided index. +func (n *NEO) GetGASPerBlock(ic *interop.Context, index uint32) (*big.Int, error) { + si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) + var gr state.GASRecord + if err := gr.FromBytes(si.Value); err != nil { + return nil, err + } + for i := len(gr) - 1; i >= 0; i-- { + if gr[i].Index <= index { + return &gr[i].GASPerBlock, nil + } + } + return nil, errors.New("contract not initialized") +} + +// GetCommitteeAddress returns address of the committee. +func (n *NEO) GetCommitteeAddress(bc blockchainer.Blockchainer, d dao.DAO) (util.Uint160, error) { + pubs, err := n.GetCommitteeMembers(bc, d) + if err != nil { + return util.Uint160{}, err + } + script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs) + if err != nil { + return util.Uint160{}, err + } + return hash.Hash160(script), nil +} + +func (n *NEO) setGASPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item { + gas := toBigInt(args[0]) + ok, err := n.SetGASPerBlock(ic, ic.Block.Index+1, gas) + if err != nil { + panic(err) + } + return stackitem.NewBool(ok) +} + +// SetGASPerBlock sets gas generated for blocks after index. +func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) (bool, error) { + if gas.Sign() == -1 || gas.Cmp(big.NewInt(10*GASFactor)) == 1 { + return false, errors.New("invalid value for GASPerBlock") + } + h, err := n.GetCommitteeAddress(ic.Chain, ic.DAO) + if err != nil { + return false, err + } + ok, err := runtime.CheckHashedWitness(ic, h) + if err != nil || !ok { + return ok, err + } + si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) + var gr state.GASRecord + if err := gr.FromBytes(si.Value); err != nil { + return false, err + } + if len(gr) > 0 && gr[len(gr)-1].Index == index { + gr[len(gr)-1].GASPerBlock = *gas + } else { + gr = append(gr, state.GASIndexPair{ + Index: index, + GASPerBlock: *gas, + }) + } + return true, ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()}) +} + // CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block. func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) { if value.Sign() == 0 || start >= end { diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index becdf5ce9..f8017a877 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/internal/testchain" @@ -109,12 +110,66 @@ func TestNEO_Vote(t *testing.T) { } } +func TestNEO_SetGasPerBlock(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + neo := bc.contracts.NEO + tx := transaction.New(netmode.UnitTestNet, []byte{}, 0) + ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx) + ic.VM = vm.New() + + h, err := neo.GetCommitteeAddress(bc, bc.dao) + require.NoError(t, err) + + t.Run("Default", func(t *testing.T) { + g, err := neo.GetGASPerBlock(ic, 0) + require.NoError(t, err) + require.EqualValues(t, 5*native.GASFactor, g.Int64()) + }) + t.Run("Invalid", func(t *testing.T) { + t.Run("InvalidSignature", func(t *testing.T) { + setSigner(tx, util.Uint160{}) + ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor)) + require.NoError(t, err) + require.False(t, ok) + }) + t.Run("TooBigValue", func(t *testing.T) { + setSigner(tx, h) + _, err := neo.SetGASPerBlock(ic, 10, big.NewInt(10*native.GASFactor+1)) + require.Error(t, err) + }) + }) + t.Run("Valid", func(t *testing.T) { + setSigner(tx, h) + ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor)) + require.NoError(t, err) + require.True(t, ok) + + t.Run("Again", func(t *testing.T) { + setSigner(tx, h) + ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor)) + require.NoError(t, err) + require.True(t, ok) + }) + + g, err := neo.GetGASPerBlock(ic, 9) + require.NoError(t, err) + require.EqualValues(t, 5*native.GASFactor, g.Int64()) + + g, err = neo.GetGASPerBlock(ic, 10) + require.NoError(t, err) + require.EqualValues(t, native.GASFactor, g.Int64()) + }) +} + func TestNEO_CalculateBonus(t *testing.T) { bc := newTestChain(t) defer bc.Close() neo := bc.contracts.NEO - ic := bc.newInteropContext(trigger.System, bc.dao, nil, nil) + tx := transaction.New(netmode.UnitTestNet, []byte{}, 0) + ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx) t.Run("Invalid", func(t *testing.T) { _, err := neo.CalculateBonus(ic, new(big.Int).SetInt64(-1), 0, 1) require.Error(t, err) @@ -124,4 +179,17 @@ func TestNEO_CalculateBonus(t *testing.T) { require.NoError(t, err) require.EqualValues(t, 0, res.Int64()) }) + t.Run("ManyBlocks", func(t *testing.T) { + h, err := neo.GetCommitteeAddress(bc, bc.dao) + require.NoError(t, err) + setSigner(tx, h) + ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(1*native.GASFactor)) + require.NoError(t, err) + require.True(t, ok) + + res, err := neo.CalculateBonus(ic, big.NewInt(100), 5, 15) + require.NoError(t, err) + require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64()) + + }) } From 43b3e15330e439b822b7ca59810809382b1a2267 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 26 Aug 2020 16:16:57 +0300 Subject: [PATCH 3/8] native: send GAS to a committee member on persist --- pkg/core/blockchain_test.go | 10 ++++++++-- pkg/core/native/native_neo.go | 14 +++++++++++++- pkg/core/native_neo_test.go | 27 ++++++++++++++++++++++++++- pkg/rpc/server/server_test.go | 24 +++++++++++++++++------- 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index d5b7b5db4..d73076ee0 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -587,7 +587,7 @@ func TestSubscriptions(t *testing.T) { blocks, err := bc.genBlocks(1) require.NoError(t, err) require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond) - assert.Empty(t, notificationCh) + assert.Len(t, notificationCh, 1) // validator bounty assert.Len(t, executionCh, 1) assert.Empty(t, txCh) @@ -598,6 +598,9 @@ func TestSubscriptions(t *testing.T) { aer := <-executionCh assert.Equal(t, b.Hash(), aer.TxHash) + notif := <-notificationCh + require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) + script := io.NewBufBinWriter() emit.Bytes(script.BinWriter, []byte("yay!")) emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) @@ -665,9 +668,12 @@ func TestSubscriptions(t *testing.T) { } } assert.Empty(t, txCh) - assert.Empty(t, notificationCh) + assert.Len(t, notificationCh, 1) assert.Empty(t, executionCh) + notif = <-notificationCh + require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) + bc.UnsubscribeFromBlocks(blockCh) bc.UnsubscribeFromTransactions(txCh) bc.UnsubscribeFromNotifications(notificationCh) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 277594215..8e0185a8a 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -179,10 +179,22 @@ func (n *NEO) Initialize(ic *interop.Context) error { // OnPersist implements Contract interface. func (n *NEO) OnPersist(ic *interop.Context) error { + gas, err := n.GetGASPerBlock(ic, ic.Block.Index) + if err != nil { + return err + } + pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO) + if err != nil { + return err + } + index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee) + gas.Mul(gas, big.NewInt(committeeRewardRatio)) + n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100))) + if !n.votesChanged.Load().(bool) { return nil } - pubs, err := n.GetValidatorsInternal(ic.Chain, ic.DAO) + pubs, err = n.GetValidatorsInternal(ic.Chain, ic.DAO) if err != nil { return err } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index f8017a877..9917123ee 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -14,6 +14,7 @@ import ( "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" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/require" ) @@ -29,9 +30,10 @@ func TestNEO_Vote(t *testing.T) { defer bc.Close() neo := bc.contracts.NEO - tx := transaction.New(netmode.UnitTestNet, []byte{}, 0) + tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx) ic.VM = vm.New() + ic.Block = bc.newBlock(tx) standBySorted := bc.GetStandByValidators() sort.Sort(standBySorted) @@ -193,3 +195,26 @@ func TestNEO_CalculateBonus(t *testing.T) { }) } + +func TestNEO_CommitteeBountyOnPersist(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + hs := make([]util.Uint160, testchain.CommitteeSize()) + for i := range hs { + hs[i] = testchain.PrivateKeyByID(i).GetScriptHash() + } + + bs := make(map[int]int64) + checkBalances := func() { + for i := 0; i < testchain.CommitteeSize(); i++ { + require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64()) + } + } + + for i := 0; i < testchain.CommitteeSize()*2; i++ { + require.NoError(t, bc.AddBlock(bc.newBlock())) + bs[(i+1)%testchain.CommitteeSize()] += 25000000 + checkBalances() + } +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index eb2bc89c7..4f7be0b24 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -980,12 +980,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoError(t, json.Unmarshal(res, actual)) checkNep5TransfersAux(t, e, actual, sent, rcvd) } - t.Run("time frame only", func(t *testing.T) { testNEP5T(t, 4, 5, 0, 0, []int{3, 4, 5, 6}, []int{0, 1}) }) + t.Run("time frame only", func(t *testing.T) { testNEP5T(t, 4, 5, 0, 0, []int{3, 4, 5, 6}, []int{1, 2}) }) t.Run("no res", func(t *testing.T) { testNEP5T(t, 100, 100, 0, 0, []int{}, []int{}) }) - t.Run("limit", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 0, []int{0, 1, 2}, []int{}) }) - t.Run("limit 2", func(t *testing.T) { testNEP5T(t, 4, 5, 2, 0, []int{3}, []int{0}) }) - t.Run("limit with page", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 1, []int{3, 4}, []int{0}) }) - t.Run("limit with page 2", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 2, []int{5, 6}, []int{1}) }) + t.Run("limit", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 0, []int{0, 1}, []int{0}) }) + t.Run("limit 2", func(t *testing.T) { testNEP5T(t, 4, 5, 2, 0, []int{3}, []int{1}) }) + t.Run("limit with page", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 1, []int{2, 3}, []int{1}) }) + t.Run("limit with page 2", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 2, []int{4, 5}, []int{2}) }) }) } @@ -1075,7 +1075,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "799.09495030", + Amount: "799.34495030", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), @@ -1085,7 +1085,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) { } func checkNep5Transfers(t *testing.T, e *executor, acc interface{}) { - checkNep5TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, []int{0, 1, 2, 3}) + checkNep5TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, []int{0, 1, 2, 3, 4, 5, 6}) } func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { @@ -1103,6 +1103,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv require.NoError(t, err) require.Equal(t, 1, len(blockSendRubles.Transactions)) txSendRubles := blockSendRubles.Transactions[0] + blockGASBounty := blockSendRubles // index 6 = size of committee blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5)) require.NoError(t, err) @@ -1214,6 +1215,15 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv }, }, Received: []result.NEP5Transfer{ + { + Timestamp: blockGASBounty.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", + Amount: "0.25000000", + Index: 6, + NotifyIndex: 0, + TxHash: blockGASBounty.Hash(), + }, { Timestamp: blockReceiveRubles.Timestamp, Asset: rublesHash, From 83e94d3bbc8af3911277db5dcc917d3846f3689b Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 28 Aug 2020 10:24:54 +0300 Subject: [PATCH 4/8] native: cache committee members --- pkg/core/blockchain.go | 12 +-- pkg/core/native/native_gas.go | 6 +- pkg/core/native/native_neo.go | 135 ++++++++++++++++------------------ pkg/core/native_neo_test.go | 11 +-- 4 files changed, 73 insertions(+), 91 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index ec94e57e4..660381f27 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1212,10 +1212,7 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error { for i := range tx.Attributes { switch tx.Attributes[i].Type { case transaction.HighPriority: - pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao) - if err != nil { - return err - } + pubs := bc.contracts.NEO.GetCommitteeMembers() s, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs) if err != nil { return err @@ -1376,10 +1373,7 @@ func (bc *Blockchain) GetStandByCommittee() keys.PublicKeys { // GetCommittee returns the sorted list of public keys of nodes in committee. func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) { - pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao) - if err != nil { - return nil, err - } + pubs := bc.contracts.NEO.GetCommitteeMembers() sort.Sort(pubs) return pubs, nil } @@ -1391,7 +1385,7 @@ func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) { // GetNextBlockValidators returns next block validators. func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) { - return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc, bc.dao) + return bc.contracts.NEO.GetNextBlockValidatorsInternal(), nil } // GetEnrollments returns all registered validators. diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 524d6879d..310131086 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -2,7 +2,6 @@ package native import ( "errors" - "fmt" "math/big" "github.com/nspcc-dev/neo-go/pkg/core/interop" @@ -89,10 +88,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error { absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee) g.burn(ic, tx.Sender(), absAmount) } - validators, err := g.NEO.getNextBlockValidatorsInternal(ic.Chain, ic.DAO) - if err != nil { - return fmt.Errorf("can't get block validators: %w", err) - } + validators := g.NEO.GetNextBlockValidatorsInternal() primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash() var netFee int64 for _, tx := range ic.Block.Transactions { diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 8e0185a8a..88e908985 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -30,6 +30,10 @@ type NEO struct { votesChanged atomic.Value nextValidators atomic.Value validators atomic.Value + // committee contains cached committee members and + // is updated during block persist. It's value + // is always equal to value stored by `prefixCommittee`. + committee atomic.Value } // keyWithVotes is a serialized key with votes balance. It's not deserialized @@ -63,9 +67,8 @@ const ( ) var ( - // nextValidatorsKey is a key used to store validators for the - // next block. - nextValidatorsKey = []byte{14} + // prefixCommittee is a key used to store committee. + prefixCommittee = []byte{14} ) // makeValidatorKey creates a key from account script hash. @@ -93,6 +96,7 @@ func NewNEO() *NEO { n.votesChanged.Store(true) n.nextValidators.Store(keys.PublicKeys(nil)) n.validators.Store(keys.PublicKeys(nil)) + n.committee.Store(keys.PublicKeys(nil)) onp := n.Methods["onPersist"] onp.Func = getOnPersistWrapper(n.onPersist) @@ -158,6 +162,15 @@ func (n *NEO) Initialize(ic *interop.Context) error { return errors.New("already initialized") } + committee := ic.Chain.GetStandByCommittee() + n.committee.Store(committee) + n.updateNextValidators(committee, ic.Chain) + + err := ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, &state.StorageItem{Value: committee.Bytes()}) + if err != nil { + return err + } + h, err := getStandbyValidatorsHash(ic) if err != nil { return err @@ -177,45 +190,47 @@ func (n *NEO) Initialize(ic *interop.Context) error { return nil } +func (n *NEO) updateNextValidators(committee keys.PublicKeys, bc blockchainer.Blockchainer) { + nextVals := committee[:bc.GetConfig().ValidatorsCount].Copy() + sort.Sort(nextVals) + n.nextValidators.Store(nextVals) +} + +func (n *NEO) updateCommittee(ic *interop.Context) error { + votesChanged := n.votesChanged.Load().(bool) + if !votesChanged { + // We need to put in storage anyway, as it affects dumps + committee := n.committee.Load().(keys.PublicKeys) + si := &state.StorageItem{Value: committee.Bytes()} + return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si) + } + + committee, err := n.ComputeCommitteeMembers(ic.Chain, ic.DAO) + if err != nil { + return err + } + n.committee.Store(committee) + n.updateNextValidators(committee, ic.Chain) + n.votesChanged.Store(false) + si := &state.StorageItem{Value: committee.Bytes()} + return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si) +} + // OnPersist implements Contract interface. func (n *NEO) OnPersist(ic *interop.Context) error { + if err := n.updateCommittee(ic); err != nil { + return err + } + gas, err := n.GetGASPerBlock(ic, ic.Block.Index) if err != nil { return err } - pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO) - if err != nil { - return err - } + pubs := n.GetCommitteeMembers() index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee) gas.Mul(gas, big.NewInt(committeeRewardRatio)) n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100))) - - if !n.votesChanged.Load().(bool) { - return nil - } - pubs, err = n.GetValidatorsInternal(ic.Chain, ic.DAO) - if err != nil { - return err - } - prev := n.nextValidators.Load().(keys.PublicKeys) - if len(prev) == len(pubs) { - var needUpdate bool - for i := range pubs { - if !pubs[i].Equal(prev[i]) { - needUpdate = true - break - } - } - if !needUpdate { - return nil - } - } - n.votesChanged.Store(false) - n.nextValidators.Store(pubs) - si := new(state.StorageItem) - si.Value = pubs.Bytes() - return ic.DAO.PutStorageItem(n.ContractID, nextValidatorsKey, si) + return nil } func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { @@ -304,10 +319,7 @@ func (n *NEO) GetGASPerBlock(ic *interop.Context, index uint32) (*big.Int, error // GetCommitteeAddress returns address of the committee. func (n *NEO) GetCommitteeAddress(bc blockchainer.Blockchainer, d dao.DAO) (util.Uint160, error) { - pubs, err := n.GetCommitteeMembers(bc, d) - if err != nil { - return util.Uint160{}, err - } + pubs := n.GetCommitteeMembers() script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs) if err != nil { return util.Uint160{}, err @@ -578,10 +590,7 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke if vals := n.validators.Load().(keys.PublicKeys); vals != nil { return vals.Copy(), nil } - result, err := n.GetCommitteeMembers(bc, d) - if err != nil { - return nil, err - } + result := n.GetCommitteeMembers() count := bc.GetConfig().ValidatorsCount if len(result) < count { count = len(result) @@ -601,10 +610,7 @@ func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.I } func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO) - if err != nil { - panic(err) - } + pubs := n.GetCommitteeMembers() sort.Sort(pubs) return pubsToArray(pubs) } @@ -621,8 +627,13 @@ func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error { return d.PutStorageItem(n.ContractID, key, si) } -// GetCommitteeMembers returns public keys of nodes in committee. -func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { +// GetCommitteeMembers returns public keys of nodes in committee using cached value. +func (n *NEO) GetCommitteeMembers() keys.PublicKeys { + return n.committee.Load().(keys.PublicKeys).Copy() +} + +// ComputeCommitteeMembers returns public keys of nodes in committee. +func (n *NEO) ComputeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { key := []byte{prefixVotersCount} si := d.GetStorageItem(n.ContractID, key) if si == nil { @@ -633,7 +644,8 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout)) voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d)) if voterTurnout.Sign() != 1 { - return bc.GetStandByCommittee(), nil + pubs := bc.GetStandByCommittee() + return pubs, nil } cs, err := n.getCandidates(d) if err != nil { @@ -664,34 +676,13 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys } func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - result, err := n.getNextBlockValidatorsInternal(ic.Chain, ic.DAO) - if err != nil { - panic(err) - } + result := n.GetNextBlockValidatorsInternal() return pubsToArray(result) } // GetNextBlockValidatorsInternal returns next block validators. -func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { - pubs, err := n.getNextBlockValidatorsInternal(bc, d) - if err != nil { - return nil, err - } - return pubs.Copy(), nil -} - -// getNextBlockValidatorsInternal returns next block validators. -func (n *NEO) getNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { - si := d.GetStorageItem(n.ContractID, nextValidatorsKey) - if si == nil { - return n.GetValidatorsInternal(bc, d) - } - pubs := keys.PublicKeys{} - err := pubs.DecodeBytes(si.Value) - if err != nil { - return nil, err - } - return pubs, nil +func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys { + return n.nextValidators.Load().(keys.PublicKeys).Copy() } func pubsToArray(pubs keys.PublicKeys) stackitem.Item { diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 9917123ee..d4d049370 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -68,14 +68,15 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, neo.VoteInternal(ic, h, candidates[i])) } + require.NoError(t, neo.OnPersist(ic)) + // We still haven't voted enough validators in. pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) require.Equal(t, standBySorted, pubs) require.NoError(t, neo.OnPersist(ic)) - pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO) - require.NoError(t, err) + pubs = neo.GetNextBlockValidatorsInternal() require.EqualValues(t, standBySorted, pubs) // Register and give some value to the last validator. @@ -91,19 +92,19 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, neo.RegisterCandidateInternal(ic, priv.PublicKey())) } + require.NoError(t, neo.OnPersist(ic)) pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) sortedCandidates := candidates.Copy() sort.Sort(sortedCandidates) require.EqualValues(t, sortedCandidates, pubs) - require.NoError(t, neo.OnPersist(ic)) - pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO) - require.NoError(t, err) + pubs = neo.GetNextBlockValidatorsInternal() require.EqualValues(t, sortedCandidates, pubs) require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0])) require.Error(t, neo.VoteInternal(ic, h, candidates[0])) + require.NoError(t, neo.OnPersist(ic)) pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) From 81a11c629a5ec073f9021dc7659719fcce98c85f Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 22 Sep 2020 12:53:44 +0300 Subject: [PATCH 5/8] native: remove `getValidators` method Follow https://github.com/neo-project/neo/pull/1920 . --- pkg/core/blockchain.go | 2 +- pkg/core/native/native_neo.go | 25 ++++++------------------- pkg/core/native_neo_test.go | 10 ++++------ 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 660381f27..ff0505e00 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1380,7 +1380,7 @@ func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) { // GetValidators returns current validators. func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) { - return bc.contracts.NEO.GetValidatorsInternal(bc, bc.dao) + return bc.contracts.NEO.ComputeNextBlockValidators(bc, bc.dao) } // GetNextBlockValidators returns next block validators. diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 88e908985..fd9053782 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -132,10 +132,6 @@ func NewNEO() *NEO { md = newMethodAndPrice(n.getCommittee, 100000000, smartcontract.AllowStates) n.AddMethod(md, desc, true) - desc = newDescriptor("getValidators", smartcontract.ArrayType) - md = newMethodAndPrice(n.getValidators, 100000000, smartcontract.AllowStates) - n.AddMethod(md, desc, true) - desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType) md = newMethodAndPrice(n.getNextBlockValidators, 100000000, smartcontract.AllowStates) n.AddMethod(md, desc, true) @@ -585,30 +581,21 @@ func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackit return stackitem.NewArray(arr) } -// GetValidatorsInternal returns a list of current validators. -func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { +// ComputeNextBlockValidators returns an actual list of current validators. +func (n *NEO) ComputeNextBlockValidators(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { if vals := n.validators.Load().(keys.PublicKeys); vals != nil { return vals.Copy(), nil } - result := n.GetCommitteeMembers() - count := bc.GetConfig().ValidatorsCount - if len(result) < count { - count = len(result) + result, err := n.ComputeCommitteeMembers(bc, d) + if err != nil { + return nil, err } - result = result[:count] + result = result[:bc.GetConfig().ValidatorsCount] sort.Sort(result) n.validators.Store(result) return result, nil } -func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - result, err := n.GetValidatorsInternal(ic.Chain, ic.DAO) - if err != nil { - panic(err) - } - return pubsToArray(result) -} - func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item { pubs := n.GetCommitteeMembers() sort.Sort(pubs) diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index d4d049370..531119ff5 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -37,7 +37,7 @@ func TestNEO_Vote(t *testing.T) { standBySorted := bc.GetStandByValidators() sort.Sort(standBySorted) - pubs, err := neo.GetValidatorsInternal(bc, ic.DAO) + pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO) require.NoError(t, err) require.Equal(t, standBySorted, pubs) @@ -68,10 +68,8 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, neo.VoteInternal(ic, h, candidates[i])) } - require.NoError(t, neo.OnPersist(ic)) - // We still haven't voted enough validators in. - pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) + pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO) require.NoError(t, err) require.Equal(t, standBySorted, pubs) @@ -93,7 +91,7 @@ func TestNEO_Vote(t *testing.T) { } require.NoError(t, neo.OnPersist(ic)) - pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) + pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO) require.NoError(t, err) sortedCandidates := candidates.Copy() sort.Sort(sortedCandidates) @@ -106,7 +104,7 @@ func TestNEO_Vote(t *testing.T) { require.Error(t, neo.VoteInternal(ic, h, candidates[0])) require.NoError(t, neo.OnPersist(ic)) - pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) + pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO) require.NoError(t, err) for i := range pubs { require.NotEqual(t, candidates[0], pubs[i]) From af16519413fcdcad5d6b86b5138b33c052ba22f9 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 22 Sep 2020 13:03:34 +0300 Subject: [PATCH 6/8] native: do not update committee every block Update frequency depends on committee size and amount of validators. For mainnet it is 28. --- pkg/core/native/native_neo.go | 15 ++++++++++++--- pkg/core/native_neo_test.go | 14 +++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index fd9053782..395319d0a 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -31,7 +31,8 @@ type NEO struct { nextValidators atomic.Value validators atomic.Value // committee contains cached committee members and - // is updated during block persist. It's value + // is updated once in a while depending on committee size + // (every 28 blocks for mainnet). It's value // is always equal to value stored by `prefixCommittee`. committee atomic.Value } @@ -212,10 +213,18 @@ func (n *NEO) updateCommittee(ic *interop.Context) error { return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si) } +func shouldUpdateCommittee(h uint32, bc blockchainer.Blockchainer) bool { + cfg := bc.GetConfig() + r := cfg.ValidatorsCount + len(cfg.StandbyCommittee) + return h%uint32(r) == 0 +} + // OnPersist implements Contract interface. func (n *NEO) OnPersist(ic *interop.Context) error { - if err := n.updateCommittee(ic); err != nil { - return err + if shouldUpdateCommittee(ic.Block.Index, ic.Chain) { + if err := n.updateCommittee(ic); err != nil { + return err + } } gas, err := n.GetGASPerBlock(ic, ic.Block.Index) diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 531119ff5..6544f5112 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -35,6 +35,14 @@ func TestNEO_Vote(t *testing.T) { ic.VM = vm.New() ic.Block = bc.newBlock(tx) + freq := testchain.ValidatorsCount + testchain.CommitteeSize() + advanceChain := func(t *testing.T) { + for i := 0; i < freq; i++ { + require.NoError(t, neo.OnPersist(ic)) + ic.Block.Index++ + } + } + standBySorted := bc.GetStandByValidators() sort.Sort(standBySorted) pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO) @@ -73,7 +81,7 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, err) require.Equal(t, standBySorted, pubs) - require.NoError(t, neo.OnPersist(ic)) + advanceChain(t) pubs = neo.GetNextBlockValidatorsInternal() require.EqualValues(t, standBySorted, pubs) @@ -90,7 +98,7 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, neo.RegisterCandidateInternal(ic, priv.PublicKey())) } - require.NoError(t, neo.OnPersist(ic)) + advanceChain(t) pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO) require.NoError(t, err) sortedCandidates := candidates.Copy() @@ -102,7 +110,7 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0])) require.Error(t, neo.VoteInternal(ic, h, candidates[0])) - require.NoError(t, neo.OnPersist(ic)) + advanceChain(t) pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO) require.NoError(t, err) From e8eb177c648f0acddde7e082910f7d609ed7768d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 23 Sep 2020 11:47:10 +0300 Subject: [PATCH 7/8] rpc/server: fix small bug in subscription test --- pkg/rpc/server/subscription_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rpc/server/subscription_test.go b/pkg/rpc/server/subscription_test.go index 0cb1c8197..d41b6c584 100644 --- a/pkg/rpc/server/subscription_test.go +++ b/pkg/rpc/server/subscription_test.go @@ -100,7 +100,7 @@ func TestSubscriptions(t *testing.T) { resp := getNotification(t, respMsgs) require.Equal(t, response.ExecutionEventID, resp.Event) for { - resp := getNotification(t, respMsgs) + resp = getNotification(t, respMsgs) if resp.Event != response.NotificationEventID { break } From c5cdaae87afd3b4b84ad958ba97dfecb5780d025 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 23 Sep 2020 11:48:31 +0300 Subject: [PATCH 8/8] native: support `postPersist` method It should be called for NEO contract to distribute committee bounties. --- pkg/core/blockchain.go | 75 ++++++++++++++++++++--------- pkg/core/blockchain_test.go | 10 +++- pkg/core/helper_test.go | 2 +- pkg/core/native/contract.go | 35 ++++++++++++++ pkg/core/native/native_neo.go | 9 ++++ pkg/core/native/native_nep5.go | 15 ++++-- pkg/core/native/policy.go | 4 ++ pkg/core/native_neo_test.go | 8 +-- pkg/rpc/server/server_test.go | 13 ++++- pkg/rpc/server/subscription_test.go | 7 +++ 10 files changed, 143 insertions(+), 35 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index ff0505e00..16fc98460 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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 diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index d73076ee0..1524ff6a2 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -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) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index d327eb2d5..1293365e9 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -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 diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index cb8323be6..1dbcd5517 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -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 +} diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 395319d0a..e3868cdf1 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -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 diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index 49ebe3908..56ab2d864 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -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 diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 6b661e4c9..5a172fbbb 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -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 } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 6544f5112..0a6bd7e24 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -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() } } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 4f7be0b24..8b66cc3c5 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -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(), } diff --git a/pkg/rpc/server/subscription_test.go b/pkg/rpc/server/subscription_test.go index d41b6c584..c7ab570c7 100644 --- a/pkg/rpc/server/subscription_test.go +++ b/pkg/rpc/server/subscription_test.go @@ -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) }