Skip to content

Commit

Permalink
consensus: check block parts don't exceed maximum block bytes (#5436)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmwaters authored Oct 1, 2020
1 parent 6149f21 commit 52994aa
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 6 deletions.
2 changes: 1 addition & 1 deletion consensus/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ func ensureProposal(proposalCh <-chan tmpubsub.Message, height int64, round int3
panic(fmt.Sprintf("expected round %v, got %v", round, proposalEvent.Round))
}
if !proposalEvent.BlockID.Equals(propID) {
panic("Proposed block does not match expected block")
panic(fmt.Sprintf("Proposed block does not match expected block (%v != %v)", proposalEvent.BlockID, propID))
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -1779,16 +1779,23 @@ func (cs *State) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (add
if err != nil {
return added, err
}
if cs.ProposalBlockParts.ByteSize() > cs.state.ConsensusParams.Block.MaxBytes {
return added, fmt.Errorf("total size of proposal block parts exceeds maximum block bytes (%d > %d)",
cs.ProposalBlockParts.ByteSize(), cs.state.ConsensusParams.Block.MaxBytes,
)
}
if added && cs.ProposalBlockParts.IsComplete() {
bz, err := ioutil.ReadAll(cs.ProposalBlockParts.GetReader())
if err != nil {
return added, err
}

var pbb = new(tmproto.Block)
err = proto.Unmarshal(bz, pbb)
if err != nil {
return added, err
}

block, err := types.BlockFromProto(pbb)
if err != nil {
return added, err
Expand Down
59 changes: 59 additions & 0 deletions consensus/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ x * TestProposerSelection2 - round robin ordering, round 2++
x * TestEnterProposeNoValidator - timeout into prevote round
x * TestEnterPropose - finish propose without timing out (we have the proposal)
x * TestBadProposal - 2 vals, bad proposal (bad block state hash), should prevote and precommit nil
x * TestOversizedBlock - block with too many txs should be rejected
FullRoundSuite
x * TestFullRound1 - 1 val, full successful round
x * TestFullRoundNil - 1 val, full round of nil
Expand Down Expand Up @@ -238,6 +239,64 @@ func TestStateBadProposal(t *testing.T) {
signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2)
}

func TestStateOversizedBlock(t *testing.T) {
cs1, vss := randState(2)
cs1.state.ConsensusParams.Block.MaxBytes = 2000
height, round := cs1.Height, cs1.Round
vs2 := vss[1]

partSize := types.BlockPartSizeBytes

timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
voteCh := subscribe(cs1.eventBus, types.EventQueryVote)

propBlock, _ := cs1.createProposalBlock()
propBlock.Data.Txs = []types.Tx{tmrand.Bytes(2001)}
propBlock.Header.DataHash = propBlock.Data.Hash()

// make the second validator the proposer by incrementing round
round++
incrementRound(vss[1:]...)

propBlockParts := propBlock.MakePartSet(partSize)
blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
proposal := types.NewProposal(height, round, -1, blockID)
p := proposal.ToProto()
if err := vs2.SignProposal(config.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
}
proposal.Signature = p.Signature

totalBytes := 0
for i := 0; i < int(propBlockParts.Total()); i++ {
part := propBlockParts.GetPart(i)
totalBytes += len(part.Bytes)
}

if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil {
t.Fatal(err)
}

// start the machine
startTestRound(cs1, height, round)

t.Log("Block Sizes", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes)

// c1 should log an error with the block part message as it exceeds the consensus params. The
// block is not added to cs.ProposalBlock so the node timeouts.
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())

// and then should send nil prevote and precommit regardless of whether other validators prevote and
// precommit on it
ensurePrevote(voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], nil)
signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2)
ensurePrevote(voteCh, height, round)
ensurePrecommit(voteCh, height, round)
validatePrecommit(t, cs1, round, -1, vss[0], nil, nil)
signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2)
}

//----------------------------------------------------------------------------------------------------
// FullRoundSuite

Expand Down
13 changes: 13 additions & 0 deletions types/part_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ type PartSet struct {
parts []*Part
partsBitArray *bits.BitArray
count uint32
// a count of the total size (in bytes). Used to ensure that the
// part set doesn't exceed the maximum block bytes
byteSize int64
}

// Returns an immutable, full PartSet from the data bytes.
Expand Down Expand Up @@ -186,6 +189,7 @@ func NewPartSetFromData(data []byte, partSize uint32) *PartSet {
parts: parts,
partsBitArray: partsBitArray,
count: total,
byteSize: int64(len(data)),
}
}

Expand All @@ -197,6 +201,7 @@ func NewPartSetFromHeader(header PartSetHeader) *PartSet {
parts: make([]*Part, header.Total),
partsBitArray: bits.NewBitArray(int(header.Total)),
count: 0,
byteSize: 0,
}
}

Expand Down Expand Up @@ -244,6 +249,13 @@ func (ps *PartSet) Count() uint32 {
return ps.count
}

func (ps *PartSet) ByteSize() int64 {
if ps == nil {
return 0
}
return ps.byteSize
}

func (ps *PartSet) Total() uint32 {
if ps == nil {
return 0
Expand Down Expand Up @@ -277,6 +289,7 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) {
ps.parts[part.Index] = part
ps.partsBitArray.SetIndex(int(part.Index), true)
ps.count++
ps.byteSize += int64(len(part.Bytes))
return true, nil
}

Expand Down
13 changes: 8 additions & 5 deletions types/part_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ const (

func TestBasicPartSet(t *testing.T) {
// Construct random data of size partSize * 100
data := tmrand.Bytes(testPartSize * 100)
nParts := 100
data := tmrand.Bytes(testPartSize * nParts)
partSet := NewPartSetFromData(data, testPartSize)

assert.NotEmpty(t, partSet.Hash())
assert.EqualValues(t, 100, partSet.Total())
assert.Equal(t, 100, partSet.BitArray().Size())
assert.EqualValues(t, nParts, partSet.Total())
assert.Equal(t, nParts, partSet.BitArray().Size())
assert.True(t, partSet.HashesTo(partSet.Hash()))
assert.True(t, partSet.IsComplete())
assert.EqualValues(t, 100, partSet.Count())
assert.EqualValues(t, nParts, partSet.Count())
assert.EqualValues(t, testPartSize*nParts, partSet.ByteSize())

// Test adding parts to a new partSet.
partSet2 := NewPartSetFromHeader(partSet.Header())
Expand All @@ -49,7 +51,8 @@ func TestBasicPartSet(t *testing.T) {
assert.Nil(t, err)

assert.Equal(t, partSet.Hash(), partSet2.Hash())
assert.EqualValues(t, 100, partSet2.Total())
assert.EqualValues(t, nParts, partSet2.Total())
assert.EqualValues(t, nParts*testPartSize, partSet.ByteSize())
assert.True(t, partSet2.IsComplete())

// Reconstruct data, assert that they are equal.
Expand Down

0 comments on commit 52994aa

Please sign in to comment.