mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-06 21:36:26 +00:00
consensus: check block parts don't exceed maximum block bytes (#5431)
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user