From c647f8e4ede76b0cda7627babd42e65ba1d3652d Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 3 Aug 2020 16:35:05 +0300 Subject: [PATCH] core: add maxBlockSystemFee to native Policy Closes #1195 --- pkg/core/blockchain.go | 15 +++++++- pkg/core/native/policy.go | 64 ++++++++++++++++++++++++++++++++++ pkg/core/native_policy_test.go | 46 ++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 68d849882..f2b27734d 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1159,6 +1159,15 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra if maxTx != 0 && len(txes) > int(maxTx) { txes = txes[:maxTx] } + maxBlockSysFee := bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao) + var sysFee int64 + for i, tx := range txes { + sysFee += tx.SystemFee + if sysFee > maxBlockSysFee { + txes = txes[:i] + break + } + } return txes } @@ -1194,9 +1203,13 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e return !blockedAccounts[i].Less(h) }) if i != len(blockedAccounts) && blockedAccounts[i].Equals(h) { - return errors.Errorf("policy check failed") + return errors.Errorf("policy check failed: account %s is blocked", h.StringLE()) } } + maxBlockSystemFee := bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao) + if maxBlockSystemFee < t.SystemFee { + return errors.Errorf("policy check failed: transaction's fee shouldn't exceed maximum block system fee %d", maxBlockSystemFee) + } balance := bc.GetUtilityTokenBalance(t.Sender) need := t.SystemFee + t.NetworkFee if balance.Cmp(big.NewInt(need)) < 0 { diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 73d5fc457..c7258da0c 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -26,6 +26,9 @@ const ( defaultMaxTransactionsPerBlock = 512 defaultFeePerByte = 1000 defaultMaxVerificationGas = 50000000 + defaultMaxBlockSystemFee = 9000 * GASFactor + // minBlockSystemFee is the minimum allowed system fee per block. + minBlockSystemFee = 4007600 ) var ( @@ -39,6 +42,8 @@ var ( blockedAccountsKey = []byte{15} // maxBlockSizeKey is a key used to store the maximum block size value. maxBlockSizeKey = []byte{12} + // maxBlockSystemFeeKey is a key used to store the maximum block system fee value. + maxBlockSystemFeeKey = []byte{17} ) // Policy represents Policy native contract. @@ -52,6 +57,7 @@ type Policy struct { maxTransactionsPerBlock uint32 maxBlockSize uint32 feePerByte int64 + maxBlockSystemFee int64 maxVerificationGas int64 } @@ -80,6 +86,10 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.getBlockedAccounts, 1000000, smartcontract.AllowStates) p.AddMethod(md, desc, true) + desc = newDescriptor("getMaxBlockSystemFee", smartcontract.IntegerType) + md = newMethodAndPrice(p.getMaxBlockSystemFee, 1000000, smartcontract.AllowStates) + p.AddMethod(md, desc, true) + desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType, manifest.NewParameter("value", smartcontract.IntegerType)) md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.AllowModifyStates) @@ -95,6 +105,11 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.setFeePerByte, 3000000, smartcontract.AllowModifyStates) p.AddMethod(md, desc, false) + desc = newDescriptor("setMaxBlockSystemFee", smartcontract.BoolType, + manifest.NewParameter("value", smartcontract.IntegerType)) + md = newMethodAndPrice(p.setMaxBlockSystemFee, 3000000, smartcontract.AllowModifyStates) + p.AddMethod(md, desc, false) + desc = newDescriptor("blockAccount", smartcontract.BoolType, manifest.NewParameter("account", smartcontract.Hash160Type)) md = newMethodAndPrice(p.blockAccount, 3000000, smartcontract.AllowModifyStates) @@ -140,6 +155,12 @@ func (p *Policy) Initialize(ic *interop.Context) error { return err } + binary.LittleEndian.PutUint64(si.Value, defaultMaxBlockSystemFee) + err = ic.DAO.PutStorageItem(p.ContractID, maxBlockSystemFeeKey, si) + if err != nil { + return err + } + ba := new(BlockedAccounts) si.Value = ba.Bytes() err = ic.DAO.PutStorageItem(p.ContractID, blockedAccountsKey, si) @@ -151,6 +172,7 @@ func (p *Policy) Initialize(ic *interop.Context) error { p.maxTransactionsPerBlock = defaultMaxTransactionsPerBlock p.maxBlockSize = defaultMaxBlockSize p.feePerByte = defaultFeePerByte + p.maxBlockSystemFee = defaultMaxBlockSystemFee p.maxVerificationGas = defaultMaxVerificationGas return nil @@ -178,6 +200,9 @@ func (p *Policy) OnPersistEnd(dao dao.DAO) { feePerByte := p.getInt64WithKey(dao, feePerByteKey) p.feePerByte = feePerByte + maxBlockSystemFee := p.getInt64WithKey(dao, maxBlockSystemFeeKey) + p.maxBlockSystemFee = maxBlockSystemFee + p.isValid = true } @@ -229,6 +254,22 @@ func (p *Policy) GetMaxVerificationGas(_ dao.DAO) int64 { return p.maxVerificationGas } +// getMaxBlockSystemFee is Policy contract method and returns the maximum overall +// system fee per block. +func (p *Policy) getMaxBlockSystemFee(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(p.GetMaxBlockSystemFeeInternal(ic.DAO))) +} + +// GetMaxBlockSystemFeeInternal the maximum overall system fee per block. +func (p *Policy) GetMaxBlockSystemFeeInternal(dao dao.DAO) int64 { + p.lock.RLock() + defer p.lock.RUnlock() + if p.isValid { + return p.maxBlockSystemFee + } + return p.getInt64WithKey(dao, maxBlockSystemFeeKey) +} + // getBlockedAccounts is Policy contract method and returns list of blocked // accounts hashes. func (p *Policy) getBlockedAccounts(ic *interop.Context, _ []stackitem.Item) stackitem.Item { @@ -316,6 +357,29 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack return stackitem.NewBool(true) } +// setMaxBlockSystemFee is Policy contract method and sets the maximum system fee per block. +func (p *Policy) setMaxBlockSystemFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { + ok, err := p.checkValidators(ic) + if err != nil { + panic(err) + } + if !ok { + return stackitem.NewBool(false) + } + value := toBigInt(args[0]).Int64() + if value <= minBlockSystemFee { + return stackitem.NewBool(false) + } + p.lock.Lock() + defer p.lock.Unlock() + err = p.setInt64WithKey(ic.DAO, maxBlockSystemFeeKey, value) + if err != nil { + panic(err) + } + p.isValid = false + return stackitem.NewBool(true) +} + // blockAccount is Policy contract method and adds given account hash to the list // of blocked accounts. func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index b0fd12964..58388dbb7 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -113,6 +113,52 @@ func TestFeePerByte(t *testing.T) { }) } +func TestBlockSystemFee(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + + t.Run("get, internal method", func(t *testing.T) { + n := chain.contracts.Policy.GetMaxBlockSystemFeeInternal(chain.dao) + require.Equal(t, 9000*native.GASFactor, int(n)) + }) + + t.Run("get", func(t *testing.T) { + res, err := invokeNativePolicyMethod(chain, "getMaxBlockSystemFee") + require.NoError(t, err) + checkResult(t, res, smartcontract.Parameter{ + Type: smartcontract.IntegerType, + Value: 9000 * native.GASFactor, + }) + require.NoError(t, chain.persist()) + }) + + t.Run("set, too low fee", func(t *testing.T) { + res, err := invokeNativePolicyMethod(chain, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(4007600))) + require.NoError(t, err) + checkResult(t, res, smartcontract.Parameter{ + Type: smartcontract.BoolType, + Value: false, + }) + }) + + t.Run("set, success", func(t *testing.T) { + res, err := invokeNativePolicyMethod(chain, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000))) + require.NoError(t, err) + checkResult(t, res, smartcontract.Parameter{ + Type: smartcontract.BoolType, + Value: true, + }) + require.NoError(t, chain.persist()) + res, err = invokeNativePolicyMethod(chain, "getMaxBlockSystemFee") + require.NoError(t, err) + checkResult(t, res, smartcontract.Parameter{ + Type: smartcontract.IntegerType, + Value: 100000000, + }) + require.NoError(t, chain.persist()) + }) +} + func TestBlockedAccounts(t *testing.T) { chain := newTestChain(t) defer chain.Close()