From e1e586f18be2452a5fdf395a8ebf812276d7a29c Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 2 Oct 2020 17:25:55 +0300 Subject: [PATCH] core: restrict the muximum number of contents per block --- pkg/consensus/prepare_request.go | 3 +- pkg/consensus/prepare_request_test.go | 31 ++++++++++++++ pkg/core/block/block.go | 18 ++++++++ pkg/core/block/block_test.go | 25 +++++++++++ pkg/network/payload/merkleblock.go | 9 +++- pkg/network/payload/merkleblock_test.go | 57 +++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 pkg/network/payload/merkleblock_test.go diff --git a/pkg/consensus/prepare_request.go b/pkg/consensus/prepare_request.go index cded41ed2..ad745f9c6 100644 --- a/pkg/consensus/prepare_request.go +++ b/pkg/consensus/prepare_request.go @@ -2,6 +2,7 @@ package consensus import ( "github.com/nspcc-dev/dbft/payload" + "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -26,7 +27,7 @@ func (p *prepareRequest) EncodeBinary(w *io.BinWriter) { func (p *prepareRequest) DecodeBinary(r *io.BinReader) { p.timestamp = r.ReadU64LE() p.nonce = r.ReadU64LE() - r.ReadArray(&p.transactionHashes) + r.ReadArray(&p.transactionHashes, block.MaxTransactionsPerBlock) } // Timestamp implements payload.PrepareRequest interface. diff --git a/pkg/consensus/prepare_request_test.go b/pkg/consensus/prepare_request_test.go index 188e32d97..ef51a7d56 100644 --- a/pkg/consensus/prepare_request_test.go +++ b/pkg/consensus/prepare_request_test.go @@ -3,7 +3,9 @@ package consensus import ( "testing" + "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/internal/random" + "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" ) @@ -30,3 +32,32 @@ func TestPrepareRequest_Setters(t *testing.T) { p.SetTransactionHashes(hashes[:]) require.Equal(t, hashes[:], p.TransactionHashes()) } + +func TestPrepareRequest_EncodeDecodeBinary(t *testing.T) { + t.Run("positive", func(t *testing.T) { + expected := &prepareRequest{ + timestamp: 112, + nonce: 1325, + transactionHashes: []util.Uint256{ + random.Uint256(), + random.Uint256(), + }, + } + testserdes.EncodeDecodeBinary(t, expected, new(prepareRequest)) + }) + + t.Run("bad hashes count", func(t *testing.T) { + hashes := make([]util.Uint256, block.MaxTransactionsPerBlock+1) + for i := range hashes { + hashes[i] = random.Uint256() + } + expected := &prepareRequest{ + timestamp: 112, + nonce: 1325, + transactionHashes: hashes, + } + data, err := testserdes.EncodeBinary(expected) + require.NoError(t, err) + require.Error(t, testserdes.DecodeBinary(data, new(prepareRequest))) + }) +} diff --git a/pkg/core/block/block.go b/pkg/core/block/block.go index d06e44575..650df993e 100644 --- a/pkg/core/block/block.go +++ b/pkg/core/block/block.go @@ -3,6 +3,7 @@ package block import ( "encoding/json" "errors" + "math" "github.com/Workiva/go-datastructures/queue" "github.com/nspcc-dev/neo-go/pkg/config/netmode" @@ -12,6 +13,16 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" ) +const ( + // MaxContentsPerBlock is the maximum number of contents (transactions + consensus data) per block. + MaxContentsPerBlock = math.MaxUint16 + // MaxTransactionsPerBlock is the maximum number of transactions per block. + MaxTransactionsPerBlock = MaxContentsPerBlock - 1 +) + +// 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") + // Block represents one block in the chain. type Block struct { // The base of the block. @@ -82,6 +93,9 @@ func NewBlockFromTrimmedBytes(network netmode.Magic, b []byte) (*Block, error) { block.Script.DecodeBinary(br) lenHashes := br.ReadVarUint() + if lenHashes > MaxContentsPerBlock { + return nil, ErrMaxContentsPerBlock + } if lenHashes > 0 { var consensusDataHash util.Uint256 consensusDataHash.DecodeBinary(br) @@ -142,6 +156,10 @@ func (b *Block) DecodeBinary(br *io.BinReader) { br.Err = errors.New("invalid block format") return } + if contentsCount > MaxContentsPerBlock { + br.Err = ErrMaxContentsPerBlock + return + } b.ConsensusData.DecodeBinary(br) txes := make([]*transaction.Transaction, contentsCount-1) for i := 0; i < int(contentsCount)-1; i++ { diff --git a/pkg/core/block/block_test.go b/pkg/core/block/block_test.go index 02df2403e..ab1052944 100644 --- a/pkg/core/block/block_test.go +++ b/pkg/core/block/block_test.go @@ -3,6 +3,7 @@ package block import ( "encoding/base64" "encoding/hex" + "errors" "strings" "testing" @@ -218,3 +219,27 @@ func TestBlockCompare(t *testing.T) { assert.Equal(t, 0, b2.Compare(&b2)) assert.Equal(t, -1, b2.Compare(&b3)) } + +func TestBlockEncodeDecode(t *testing.T) { + t.Run("positive", func(t *testing.T) { + b := newDumbBlock() + b.Transactions = []*transaction.Transaction{} + _ = b.Hash() + testserdes.EncodeDecodeBinary(t, b, new(Block)) + }) + + t.Run("bad contents count", func(t *testing.T) { + b := newDumbBlock() + b.Transactions = make([]*transaction.Transaction, MaxContentsPerBlock) + for i := range b.Transactions { + b.Transactions[i] = &transaction.Transaction{ + Script: []byte("my_pretty_script"), + } + } + _ = b.Hash() + data, err := testserdes.EncodeBinary(b) + require.NoError(t, err) + + require.True(t, errors.Is(testserdes.DecodeBinary(data, new(Block)), ErrMaxContentsPerBlock)) + }) +} diff --git a/pkg/network/payload/merkleblock.go b/pkg/network/payload/merkleblock.go index a870450ec..0fef31742 100644 --- a/pkg/network/payload/merkleblock.go +++ b/pkg/network/payload/merkleblock.go @@ -19,8 +19,13 @@ func (m *MerkleBlock) DecodeBinary(br *io.BinReader) { m.Base = &block.Base{} m.Base.DecodeBinary(br) - m.TxCount = int(br.ReadVarUint()) - br.ReadArray(&m.Hashes) + txCount := int(br.ReadVarUint()) + if txCount > block.MaxContentsPerBlock { + br.Err = block.ErrMaxContentsPerBlock + return + } + m.TxCount = txCount + br.ReadArray(&m.Hashes, m.TxCount) m.Flags = br.ReadVarBytes() } diff --git a/pkg/network/payload/merkleblock_test.go b/pkg/network/payload/merkleblock_test.go new file mode 100644 index 000000000..807367744 --- /dev/null +++ b/pkg/network/payload/merkleblock_test.go @@ -0,0 +1,57 @@ +package payload + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +func newDumbBlock() *block.Base { + return &block.Base{ + Version: 0, + PrevHash: hash.Sha256([]byte("a")), + MerkleRoot: hash.Sha256([]byte("b")), + Timestamp: 100500, + Index: 1, + NextConsensus: hash.Hash160([]byte("a")), + Script: transaction.Witness{ + VerificationScript: []byte{0x51}, // PUSH1 + InvocationScript: []byte{0x61}, // NOP + }, + } +} + +func TestMerkleBlock_EncodeDecodeBinary(t *testing.T) { + t.Run("positive", func(t *testing.T) { + b := newDumbBlock() + _ = b.Hash() + expected := &MerkleBlock{ + Base: b, + TxCount: 0, + Hashes: []util.Uint256{}, + Flags: []byte{}, + } + testserdes.EncodeDecodeBinary(t, expected, new(MerkleBlock)) + }) + + t.Run("bad contents count", func(t *testing.T) { + b := newDumbBlock() + _ = b.Hash() + expected := &MerkleBlock{ + Base: b, + TxCount: block.MaxContentsPerBlock + 1, + Hashes: make([]util.Uint256, block.MaxContentsPerBlock), + Flags: []byte{}, + } + data, err := testserdes.EncodeBinary(expected) + require.NoError(t, err) + require.True(t, errors.Is(block.ErrMaxContentsPerBlock, testserdes.DecodeBinary(data, new(MerkleBlock)))) + }) +}