diff --git a/consensus/state.go b/consensus/state.go index 9d4eaf393f..9c0f96415d 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -37,6 +37,7 @@ var ( ErrInvalidProposalPOLRound = errors.New("error invalid proposal POL round") ErrAddingVote = errors.New("error adding vote") ErrSignatureFoundInPastBlocks = errors.New("found signature from the same key") + ErrProposalTooManyParts = errors.New("proposal block has too many parts") errPubKeyIsNotSet = errors.New("pubkey is not set. Look for \"Can't get private validator pubkey\" errors") ) @@ -1922,6 +1923,15 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error { return ErrInvalidProposalSignature } + // Validate the proposed block size, derived from its PartSetHeader + maxBytes := cs.state.ConsensusParams.Block.MaxBytes + if maxBytes == -1 { + maxBytes = int64(types.MaxBlockSizeBytes) + } + if int64(proposal.BlockID.PartSetHeader.Total) > (maxBytes-1)/int64(types.BlockPartSizeBytes)+1 { + return ErrProposalTooManyParts + } + proposal.Signature = p.Signature cs.Proposal = proposal // We don't update cs.ProposalBlockParts if it is already set. diff --git a/consensus/state_test.go b/consensus/state_test.go index 65eac76e6d..66169bb3b3 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -257,7 +257,7 @@ func TestStateBadProposal(t *testing.T) { } func TestStateOversizedBlock(t *testing.T) { - const maxBytes = 2000 + const maxBytes = int64(types.BlockPartSizeBytes) for _, testCase := range []struct { name string @@ -303,6 +303,12 @@ func TestStateOversizedBlock(t *testing.T) { totalBytes += len(part.Bytes) } + maxBlockParts := maxBytes / int64(types.BlockPartSizeBytes) + if maxBytes > maxBlockParts*int64(types.BlockPartSizeBytes) { + maxBlockParts++ + } + numBlockParts := int64(propBlockParts.Total()) + if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { t.Fatal(err) } @@ -310,7 +316,8 @@ func TestStateOversizedBlock(t *testing.T) { // start the machine startTestRound(cs1, height, round) - t.Log("Block Sizes;", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes) + t.Log("Block Sizes;", "Limit", maxBytes, "Current", totalBytes) + t.Log("Proposal Parts;", "Maximum", maxBlockParts, "Current", numBlockParts) validateHash := propBlock.Hash() lockedRound := int32(1) @@ -326,6 +333,11 @@ func TestStateOversizedBlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], validateHash) + // Should not accept a Proposal with too many block parts + if numBlockParts > maxBlockParts { + require.Nil(t, cs1.Proposal) + } + bps, err := propBlock.MakePartSet(partSize) require.NoError(t, err) diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 85b2db1e91..2c53abf3d4 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -24,10 +24,10 @@ const ( // everything. This also affects the generalized proof system as // well. type Proof struct { - Total int64 `json:"total"` // Total number of items. - Index int64 `json:"index"` // Index of item to prove. - LeafHash []byte `json:"leaf_hash"` // Hash of item value. - Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. + Total int64 `json:"total"` // Total number of items. + Index int64 `json:"index"` // Index of item to prove. + LeafHash []byte `json:"leaf_hash"` // Hash of item value. + Aunts [][]byte `json:"aunts,omitempty"` // Hashes from leaf's sibling to a root's child. } // ProofsFromByteSlices computes inclusion proof for given items. diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 815e366613..2b8a8e886a 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -416,8 +416,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo block := state.MakeBlock(i, test.MakeNTxs(i, 1), lastCommit.ToCommit(), nil, state.Validators.Proposer.Address) block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute) block.Header.Version = cmtversion.Consensus{Block: version.BlockProtocol, App: 1} - const parts = 1 - partSet, err := block.MakePartSet(parts) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) if err != nil { return nil, err } diff --git a/state/execution_test.go b/state/execution_test.go index cbc39f1fe2..797bd8a577 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -36,7 +36,7 @@ import ( var ( chainID = "execution_chain" - testPartSize uint32 = 65536 + testPartSize uint32 = types.BlockPartSizeBytes ) func TestApplyBlock(t *testing.T) { diff --git a/store/store_test.go b/store/store_test.go index a53b9b15e3..63b6d5492a 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -1,6 +1,7 @@ package store import ( + "encoding/json" "fmt" "os" "runtime/debug" @@ -154,10 +155,12 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } } - // save a block - block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) - validPartSet, err := block.MakePartSet(2) + // save a block big enough to have two block parts + txs := []types.Tx{make([]byte, types.BlockPartSizeBytes)} // TX taking one block part alone + block := state.MakeBlock(bs.Height()+1, txs, new(types.Commit), nil, state.Validators.GetProposer().Address) + validPartSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) + require.GreaterOrEqual(t, validPartSet.Total(), uint32(2)) part2 := validPartSet.GetPart(1) seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now()) @@ -399,7 +402,7 @@ func TestSaveBlockWithExtendedCommitPanicOnAbsentExtension(t *testing.T) { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now()) - ps, err := block.MakePartSet(2) + ps, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) testCase.malleateCommit(seenCommit) if testCase.shouldPanic { @@ -439,7 +442,7 @@ func TestLoadBlockExtendedCommit(t *testing.T) { h := bs.Height() + 1 block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now()) - ps, err := block.MakePartSet(2) + ps, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) if testCase.saveExtended { bs.SaveBlockWithExtendedCommit(block, ps, seenCommit) @@ -468,7 +471,7 @@ func TestLoadBaseMeta(t *testing.T) { for h := int64(1); h <= 10; h++ { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestExtCommit(h, cmttime.Now()) bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) @@ -513,7 +516,7 @@ func TestLoadBlockPart(t *testing.T) { // 3. A good block serialized and saved to the DB should be retrievable block := state.MakeBlock(height, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) part1 := partSet.GetPart(0) @@ -524,7 +527,13 @@ func TestLoadBlockPart(t *testing.T) { gotPart, _, panicErr := doFn(loadPart) require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, res, "a properly saved block should return a proper block") - require.Equal(t, gotPart.(*types.Part), part1, + + // Having to do this because of https://github.com/stretchr/testify/issues/1141 + gotPartJSON, err := json.Marshal(gotPart.(*types.Part)) + require.NoError(t, err) + part1JSON, err := json.Marshal(part1) + require.NoError(t, err) + require.JSONEq(t, string(gotPartJSON), string(part1JSON), "expecting successful retrieval of previously saved block") } @@ -552,7 +561,7 @@ func TestPruneBlocks(t *testing.T) { // make more than 1000 blocks, to test batch deletions for h := int64(1); h <= 1500; h++ { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestExtCommit(h, cmttime.Now()) bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) @@ -681,7 +690,7 @@ func TestLoadBlockMetaByHash(t *testing.T) { bs := NewBlockStore(dbm.NewMemDB()) b1 := state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := b1.MakePartSet(2) + partSet, err := b1.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestExtCommit(1, cmttime.Now()) bs.SaveBlock(b1, partSet, seenCommit.ToCommit()) @@ -698,7 +707,7 @@ func TestBlockFetchAtHeight(t *testing.T) { require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now()) bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) diff --git a/types/event_bus_test.go b/types/event_bus_test.go index 3058a80d40..de9e61ed28 100644 --- a/types/event_bus_test.go +++ b/types/event_bus_test.go @@ -96,7 +96,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { }() var ps *PartSet - ps, err = block.MakePartSet(MaxBlockSizeBytes) + ps, err = block.MakePartSet(BlockPartSizeBytes) require.NoError(t, err) err = eventBus.PublishEventNewBlock(EventDataNewBlock{ diff --git a/types/part_set.go b/types/part_set.go index d2ef3f0742..87a81f1c6b 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -18,6 +18,8 @@ import ( var ( ErrPartSetUnexpectedIndex = errors.New("error part set unexpected index") ErrPartSetInvalidProof = errors.New("error part set invalid proof") + ErrPartTooBig = errors.New("error part size too big") + ErrPartInvalidSize = errors.New("error inner part with invalid size") ) type Part struct { @@ -29,7 +31,11 @@ type Part struct { // ValidateBasic performs basic validation. func (part *Part) ValidateBasic() error { if len(part.Bytes) > int(BlockPartSizeBytes) { - return fmt.Errorf("too big: %d bytes, max: %d", len(part.Bytes), BlockPartSizeBytes) + return ErrPartTooBig + } + // All parts except the last one should have the same constant size. + if int64(part.Index) < part.Proof.Total-1 && len(part.Bytes) != int(BlockPartSizeBytes) { + return ErrPartInvalidSize } if err := part.Proof.ValidateBasic(); err != nil { return fmt.Errorf("wrong Proof: %w", err) @@ -289,6 +295,11 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) { return false, nil } + // The proof should be compatible with the number of parts. + if part.Proof.Total != int64(ps.total) { + return false, ErrPartSetInvalidProof + } + // Check hash proof if part.Proof.Verify(ps.Hash(), part.Bytes) != nil { return false, ErrPartSetInvalidProof diff --git a/types/part_set_test.go b/types/part_set_test.go index c1f885260c..260618dab1 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -86,6 +86,22 @@ func TestWrongProof(t *testing.T) { if added || err == nil { t.Errorf("expected to fail adding a part with bad bytes.") } + + // Test adding a part with wrong proof index. + part = partSet.GetPart(2) + part.Proof.Index = 1 + added, err = partSet2.AddPart(part) + if added || err == nil { + t.Errorf("expected to fail adding a part with bad proof index.") + } + + // Test adding a part with wrong proof total. + part = partSet.GetPart(3) + part.Proof.Total = int64(partSet.Total() - 1) + added, err = partSet2.AddPart(part) + if added || err == nil { + t.Errorf("expected to fail adding a part with bad proof total.") + } } func TestPartSetHeaderValidateBasic(t *testing.T) { @@ -117,9 +133,19 @@ func TestPartValidateBasic(t *testing.T) { }{ {"Good Part", func(pt *Part) {}, false}, {"Too big part", func(pt *Part) { pt.Bytes = make([]byte, BlockPartSizeBytes+1) }, true}, + {"Good small last part", func(pt *Part) { + pt.Index = 1 + pt.Bytes = make([]byte, BlockPartSizeBytes-1) + pt.Proof.Total = 2 + }, false}, + {"Too small inner part", func(pt *Part) { + pt.Index = 0 + pt.Bytes = make([]byte, BlockPartSizeBytes-1) + pt.Proof.Total = 2 + }, true}, {"Too big proof", func(pt *Part) { pt.Proof = merkle.Proof{ - Total: 1, + Total: 2, Index: 1, LeafHash: make([]byte, 1024*1024), }