From 5a382083613ee70282e2411662411ec70cdcd54a Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 26 Aug 2020 13:06:19 +0300 Subject: [PATCH] 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()) + + }) }