diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 921da8659..d41373af4 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -341,7 +341,19 @@ func (s *service) getTx(h util.Uint256) block.Transaction { func (s *service) verifyBlock(b block.Block) bool { coreb := &b.(*neoBlock).Block + + maxBlockSize := int(s.Chain.GetMaxBlockSize()) + size := io.GetVarSize(coreb) + if size > maxBlockSize { + s.log.Warn("proposed block size exceeds policy max block size", + zap.Int("max size allowed", maxBlockSize), + zap.Int("block size", size)) + return false + } + + var fee int64 for _, tx := range coreb.Transactions { + fee += tx.SystemFee if err := s.Chain.VerifyTx(tx, coreb); err != nil { s.log.Warn("invalid transaction in proposed block", zap.Stringer("hash", tx.Hash()), @@ -350,6 +362,14 @@ func (s *service) verifyBlock(b block.Block) bool { } } + maxBlockSysFee := s.Chain.GetMaxBlockSystemFee() + if fee > maxBlockSysFee { + s.log.Warn("proposed block system fee exceeds policy max block system fee", + zap.Int("max system fee allowed", int(maxBlockSysFee)), + zap.Int("block system fee", int(fee))) + return false + } + return true } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index cd85cb01d..bd479dba1 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1147,6 +1147,16 @@ func (bc *Blockchain) FeePerByte() int64 { return bc.contracts.Policy.GetFeePerByteInternal(bc.dao) } +// GetMaxBlockSize returns maximum allowed block size from native Policy contract. +func (bc *Blockchain) GetMaxBlockSize() uint32 { + return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao) +} + +// GetMaxBlockSystemFee returns maximum block system fee from native Policy contract. +func (bc *Blockchain) GetMaxBlockSystemFee() int64 { + return bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao) +} + // GetMemPool returns the memory pool of the blockchain. func (bc *Blockchain) GetMemPool() *mempool.Pool { return &bc.memPool @@ -1159,6 +1169,21 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra if maxTx != 0 && len(txes) > int(maxTx) { txes = txes[:maxTx] } + maxBlockSize := bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao) + maxBlockSysFee := bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao) + var ( + blockSize uint32 + sysFee int64 + ) + blockSize = uint32(io.GetVarSize(new(block.Block)) + io.GetVarSize(len(txes)+1)) + for i, tx := range txes { + blockSize += uint32(io.GetVarSize(tx)) + sysFee += tx.SystemFee + if blockSize > maxBlockSize || sysFee > maxBlockSysFee { + txes = txes[:i] + break + } + } return txes } @@ -1194,9 +1219,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/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index fc9ca0110..b1ff94f12 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -50,6 +50,8 @@ type Blockchainer interface { GetTestVM(tx *transaction.Transaction) *vm.VM GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) mempool.Feer // fee interface + GetMaxBlockSize() uint32 + GetMaxBlockSystemFee() int64 PoolTx(*transaction.Transaction) error SubscribeForBlocks(ch chan<- *block.Block) SubscribeForExecutions(ch chan<- *state.AppExecResult) diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 04ff6a72a..9d7c31cad 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 ( @@ -38,7 +41,9 @@ var ( // blockedAccountsKey is a key used to store the list of blocked accounts. blockedAccountsKey = []byte{15} // maxBlockSizeKey is a key used to store the maximum block size value. - maxBlockSizeKey = []byte{16} + 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 } @@ -65,44 +71,53 @@ func newPolicy() *Policy { p.Manifest.Features |= smartcontract.HasStorage desc := newDescriptor("getMaxTransactionsPerBlock", smartcontract.IntegerType) - md := newMethodAndPrice(p.getMaxTransactionsPerBlock, 1000000, smartcontract.NoneFlag) + md := newMethodAndPrice(p.getMaxTransactionsPerBlock, 1000000, smartcontract.AllowStates) p.AddMethod(md, desc, true) desc = newDescriptor("getMaxBlockSize", smartcontract.IntegerType) - md = newMethodAndPrice(p.getMaxBlockSize, 1000000, smartcontract.NoneFlag) + md = newMethodAndPrice(p.getMaxBlockSize, 1000000, smartcontract.AllowStates) p.AddMethod(md, desc, true) desc = newDescriptor("getFeePerByte", smartcontract.IntegerType) - md = newMethodAndPrice(p.getFeePerByte, 1000000, smartcontract.NoneFlag) + md = newMethodAndPrice(p.getFeePerByte, 1000000, smartcontract.AllowStates) p.AddMethod(md, desc, true) desc = newDescriptor("getBlockedAccounts", smartcontract.ArrayType) - md = newMethodAndPrice(p.getBlockedAccounts, 1000000, smartcontract.NoneFlag) + 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.NoneFlag) + md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.AllowModifyStates) p.AddMethod(md, desc, false) desc = newDescriptor("setMaxTransactionsPerBlock", smartcontract.BoolType, manifest.NewParameter("value", smartcontract.IntegerType)) - md = newMethodAndPrice(p.setMaxTransactionsPerBlock, 3000000, smartcontract.NoneFlag) + md = newMethodAndPrice(p.setMaxTransactionsPerBlock, 3000000, smartcontract.AllowModifyStates) p.AddMethod(md, desc, false) desc = newDescriptor("setFeePerByte", smartcontract.BoolType, manifest.NewParameter("value", smartcontract.IntegerType)) - md = newMethodAndPrice(p.setFeePerByte, 3000000, smartcontract.NoneFlag) + 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.NoneFlag) + md = newMethodAndPrice(p.blockAccount, 3000000, smartcontract.AllowModifyStates) p.AddMethod(md, desc, false) desc = newDescriptor("unblockAccount", smartcontract.BoolType, manifest.NewParameter("account", smartcontract.Hash160Type)) - md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.NoneFlag) + md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.AllowModifyStates) p.AddMethod(md, desc, false) desc = newDescriptor("onPersist", smartcontract.VoidType) @@ -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 } @@ -200,12 +225,17 @@ func (p *Policy) GetMaxTransactionsPerBlockInternal(dao dao.DAO) uint32 { // getMaxBlockSize is Policy contract method and returns maximum block size. func (p *Policy) getMaxBlockSize(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(int64(p.GetMaxBlockSizeInternal(ic.DAO)))) +} + +// GetMaxBlockSizeInternal returns maximum block size. +func (p *Policy) GetMaxBlockSizeInternal(dao dao.DAO) uint32 { p.lock.RLock() defer p.lock.RUnlock() if p.isValid { - return stackitem.NewBigInteger(big.NewInt(int64(p.maxBlockSize))) + return p.maxBlockSize } - return stackitem.NewBigInteger(big.NewInt(int64(p.getUint32WithKey(ic.DAO, maxBlockSizeKey)))) + return p.getUint32WithKey(dao, maxBlockSizeKey) } // getFeePerByte is Policy contract method and returns required transaction's fee @@ -229,6 +259,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 +362,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 88b246335..e38b789d3 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -47,7 +47,12 @@ func TestMaxBlockSize(t *testing.T) { chain := newTestChain(t) defer chain.Close() - t.Run("get", func(t *testing.T) { + t.Run("get, internal method", func(t *testing.T) { + n := chain.contracts.Policy.GetMaxBlockSizeInternal(chain.dao) + require.Equal(t, 1024*256, int(n)) + }) + + t.Run("get, contract method", func(t *testing.T) { res, err := invokeNativePolicyMethod(chain, "getMaxBlockSize") require.NoError(t, err) checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1024*256))) @@ -92,6 +97,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() diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 2e7d336e6..45ccc3615 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -40,6 +40,14 @@ func (chain testChain) FeePerByte() int64 { panic("TODO") } +func (chain testChain) GetMaxBlockSystemFee() int64 { + panic("TODO") +} + +func (chain testChain) GetMaxBlockSize() uint32 { + panic("TODO") +} + func (chain testChain) AddHeaders(...*block.Header) error { panic("TODO") }