Browse Source

consensus: check block parts don't exceed maximum block bytes (#5431)

pull/5440/head
Callum Waters 4 years ago
committed by GitHub
parent
commit
433bdf5063
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 6 deletions
  1. +1
    -1
      consensus/common_test.go
  2. +7
    -0
      consensus/state.go
  3. +59
    -0
      consensus/state_test.go
  4. +13
    -0
      types/part_set.go
  5. +8
    -5
      types/part_set_test.go

+ 1
- 1
consensus/common_test.go View File

@ -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))
}
}
}


+ 7
- 0
consensus/state.go View File

@ -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


+ 59
- 0
consensus/state_test.go View File

@ -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
@ -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


+ 13
- 0
types/part_set.go View File

@ -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.
@ -186,6 +189,7 @@ func NewPartSetFromData(data []byte, partSize uint32) *PartSet {
parts: parts,
partsBitArray: partsBitArray,
count: total,
byteSize: int64(len(data)),
}
}
@ -197,6 +201,7 @@ func NewPartSetFromHeader(header PartSetHeader) *PartSet {
parts: make([]*Part, header.Total),
partsBitArray: bits.NewBitArray(int(header.Total)),
count: 0,
byteSize: 0,
}
}
@ -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
@ -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
}


+ 8
- 5
types/part_set_test.go View File

@ -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())
@ -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.


Loading…
Cancel
Save