From 8f06bf21d74f2fcd08a2c03c2b2348ceeac07327 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 15 Mar 2021 13:00:04 +0300 Subject: [PATCH] config: add MaxBlockSize setting --- pkg/config/protocol_config.go | 2 ++ pkg/consensus/consensus.go | 8 ++++++ pkg/consensus/consensus_test.go | 10 ++++++++ pkg/core/block/block.go | 26 +++++++++++++++++++ pkg/core/block/block_test.go | 44 +++++++++++++++++++++++++++++++++ pkg/core/blockchain.go | 31 +++++++++++++++++++++++ 6 files changed, 121 insertions(+) diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index 0dafadb6e..39bb96cf0 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -18,6 +18,8 @@ type ( KeepOnlyLatestState bool `yaml:"KeepOnlyLatestState"` // RemoveUntraceableBlocks specifies if old blocks should be removed. RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"` + // MaxBlockSize is the maximum block size in bytes. + MaxBlockSize uint32 `yaml:"MaxBlockSize"` // MaxTraceableBlocks is the length of the chain accessible to smart contracts. MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"` // MaxTransactionsPerBlock is the maximum amount of transactions per block. diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 298346fc5..a5c90787c 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -437,6 +437,14 @@ func (s *service) verifyBlock(b block.Block) bool { return false } + size := coreb.GetExpectedBlockSize() + if size > int(s.ProtocolConfiguration.MaxBlockSize) { + s.log.Warn("proposed block size exceeds config MaxBlockSize", + zap.Uint32("max size allowed", s.ProtocolConfiguration.MaxBlockSize), + zap.Int("block size", size)) + return false + } + var pool = mempool.New(len(coreb.Transactions), 0, false) var mainPool = s.Chain.GetMemPool() for _, tx := range coreb.Transactions { diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index ce287eed8..2efabd301 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -409,6 +409,16 @@ func TestVerifyBlock(t *testing.T) { b.Index = srv.Chain.BlockHeight() require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) }) + t.Run("bad big size", func(t *testing.T) { + script := make([]byte, int(srv.ProtocolConfiguration.MaxBlockSize)) + script[0] = byte(opcode.RET) + tx := transaction.New(netmode.UnitTestNet, script, 100000) + tx.ValidUntilBlock = 1 + addSender(t, tx) + signTx(t, srv.Chain, tx) + b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) + require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) + }) t.Run("bad timestamp", func(t *testing.T) { b := testchain.NewBlock(t, srv.Chain, 1, 0) b.Timestamp = srv.lastTimestamp - 1 diff --git a/pkg/core/block/block.go b/pkg/core/block/block.go index b0907650e..9291b3482 100644 --- a/pkg/core/block/block.go +++ b/pkg/core/block/block.go @@ -21,6 +21,12 @@ const ( // ErrMaxContentsPerBlock is returned when the maximum number of contents per block is reached. var ErrMaxContentsPerBlock = errors.New("the number of contents exceeds the maximum number of contents per block") +var expectedHeaderSizeWithEmptyWitness int + +func init() { + expectedHeaderSizeWithEmptyWitness = io.GetVarSize(new(Header)) +} + // Block represents one block in the chain. type Block struct { // The base of the block. @@ -211,3 +217,23 @@ func (b *Block) UnmarshalJSON(data []byte) error { } return nil } + +// GetExpectedBlockSize returns expected block size which should be equal to io.GetVarSize(b) +func (b *Block) GetExpectedBlockSize() int { + var transactionsSize int + for _, tx := range b.Transactions { + transactionsSize += tx.Size() + } + return b.GetExpectedBlockSizeWithoutTransactions(len(b.Transactions)) + transactionsSize +} + +// GetExpectedBlockSizeWithoutTransactions returns expected block size without transactions size. +func (b *Block) GetExpectedBlockSizeWithoutTransactions(txCount int) int { + size := expectedHeaderSizeWithEmptyWitness - 1 - 1 + // 1 is for the zero-length (new(Header)).Script.Invocation/Verification + io.GetVarSize(&b.Script) + + io.GetVarSize(txCount) + if b.StateRootEnabled { + size += util.Uint256Size + } + return size +} diff --git a/pkg/core/block/block_test.go b/pkg/core/block/block_test.go index e00d22366..7c033e1e4 100644 --- a/pkg/core/block/block_test.go +++ b/pkg/core/block/block_test.go @@ -13,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -234,3 +235,46 @@ func TestBlockEncodeDecode(t *testing.T) { require.True(t, errors.Is(testserdes.DecodeBinary(data, new(Block)), ErrMaxContentsPerBlock)) }) } + +func TestGetExpectedBlockSize(t *testing.T) { + check := func(t *testing.T, stateRootEnabled bool) { + + t.Run("without transactions", func(t *testing.T) { + b := newDumbBlock() + b.StateRootEnabled = stateRootEnabled + b.Transactions = []*transaction.Transaction{} + require.Equal(t, io.GetVarSize(b), b.GetExpectedBlockSize()) + require.Equal(t, io.GetVarSize(b), b.GetExpectedBlockSizeWithoutTransactions(0)) + }) + t.Run("with one transaction", func(t *testing.T) { + b := newDumbBlock() + b.StateRootEnabled = stateRootEnabled + expected := io.GetVarSize(b) + require.Equal(t, expected, b.GetExpectedBlockSize()) + require.Equal(t, expected-b.Transactions[0].Size(), b.GetExpectedBlockSizeWithoutTransactions(len(b.Transactions))) + }) + t.Run("with multiple transactions", func(t *testing.T) { + b := newDumbBlock() + b.StateRootEnabled = stateRootEnabled + b.Transactions = make([]*transaction.Transaction, 123) + for i := range b.Transactions { + tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, int64(i)) + tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} + tx.Scripts = []transaction.Witness{{}} + b.Transactions[i] = tx + } + expected := io.GetVarSize(b) + require.Equal(t, expected, b.GetExpectedBlockSize()) + for _, tx := range b.Transactions { + expected -= tx.Size() + } + require.Equal(t, expected, b.GetExpectedBlockSizeWithoutTransactions(len(b.Transactions))) + }) + } + t.Run("StateRoot enabled", func(t *testing.T) { + check(t, true) + }) + t.Run("StateRoot disabled", func(t *testing.T) { + check(t, false) + }) +} diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 5af463146..706cd4fde 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -46,6 +46,7 @@ const ( defaultMemPoolSize = 50000 defaultP2PNotaryRequestPayloadPoolSize = 1000 + defaultMaxBlockSize = 262144 defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks defaultMaxTransactionsPerBlock = 512 verificationGasLimit = 100000000 // 1 GAS @@ -134,6 +135,10 @@ type Blockchain struct { extensible atomic.Value + // defaultBlockWitness stores transaction.Witness with m out of n multisig, + // where n = ValidatorsCount. + defaultBlockWitness atomic.Value + stateRoot *stateroot.Module // Notification subsystem. @@ -167,6 +172,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L cfg.P2PNotaryRequestPayloadPoolSize = defaultP2PNotaryRequestPayloadPoolSize log.Info("P2PNotaryRequestPayloadPool size is not set or wrong, setting default value", zap.Int("P2PNotaryRequestPayloadPoolSize", cfg.P2PNotaryRequestPayloadPoolSize)) } + if cfg.MaxBlockSize == 0 { + cfg.MaxBlockSize = defaultMaxBlockSize + log.Info("MaxBlockSize is not set or wrong, setting default value", zap.Uint32("MaxBlockSize", cfg.MaxBlockSize)) + } if cfg.MaxTraceableBlocks == 0 { cfg.MaxTraceableBlocks = defaultMaxTraceableBlocks log.Info("MaxTraceableBlocks is not set or wrong, using default value", zap.Uint32("MaxTraceableBlocks", cfg.MaxTraceableBlocks)) @@ -1335,6 +1344,28 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra if maxTx != 0 && len(txes) > int(maxTx) { txes = txes[:maxTx] } + maxBlockSize := bc.GetConfig().MaxBlockSize + defaultWitness := bc.defaultBlockWitness.Load() + if defaultWitness == nil { + m := smartcontract.GetDefaultHonestNodeCount(bc.config.ValidatorsCount) + verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal()) + defaultWitness = transaction.Witness{ + InvocationScript: make([]byte, 66*m), + VerificationScript: verification, + } + bc.defaultBlockWitness.Store(defaultWitness) + } + var ( + b = &block.Block{Header: block.Header{Script: defaultWitness.(transaction.Witness)}} + blockSize = uint32(b.GetExpectedBlockSizeWithoutTransactions(len(txes))) + ) + for i, tx := range txes { + blockSize += uint32(tx.Size()) + if blockSize > maxBlockSize { + txes = txes[:i] + break + } + } return txes }