|
4 | 4 | "bytes"
|
5 | 5 | "context"
|
6 | 6 | "fmt"
|
| 7 | + "strings" |
7 | 8 | "testing"
|
8 | 9 | "time"
|
9 | 10 |
|
@@ -256,73 +257,88 @@ func TestStateBadProposal(t *testing.T) {
|
256 | 257 | }
|
257 | 258 |
|
258 | 259 | func TestStateOversizedBlock(t *testing.T) {
|
259 |
| - ctx, cancel := context.WithCancel(context.Background()) |
260 |
| - defer cancel() |
| 260 | + const maxBytes = 2000 |
261 | 261 |
|
262 |
| - cs1, vss := randState(2) |
263 |
| - cs1.state.ConsensusParams.Block.MaxBytes = 2000 |
264 |
| - height, round := cs1.Height, cs1.Round |
265 |
| - vs2 := vss[1] |
266 |
| - |
267 |
| - partSize := types.BlockPartSizeBytes |
268 |
| - |
269 |
| - timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) |
270 |
| - voteCh := subscribe(cs1.eventBus, types.EventQueryVote) |
271 |
| - |
272 |
| - propBlock, err := cs1.createProposalBlock(ctx) |
273 |
| - require.NoError(t, err) |
274 |
| - propBlock.Data.Txs = []types.Tx{cmtrand.Bytes(2001)} |
275 |
| - propBlock.Header.DataHash = propBlock.Data.Hash() |
| 262 | + for _, testCase := range []struct { |
| 263 | + name string |
| 264 | + oversized bool |
| 265 | + }{ |
| 266 | + { |
| 267 | + name: "max size, correct block", |
| 268 | + oversized: false, |
| 269 | + }, |
| 270 | + { |
| 271 | + name: "off-by-1 max size, incorrect block", |
| 272 | + oversized: true, |
| 273 | + }, |
| 274 | + } { |
| 275 | + t.Run(testCase.name, func(t *testing.T) { |
| 276 | + cs1, vss := randState(2) |
| 277 | + cs1.state.ConsensusParams.Block.MaxBytes = maxBytes |
| 278 | + height, round := cs1.Height, cs1.Round |
| 279 | + vs2 := vss[1] |
276 | 280 |
|
277 |
| - // make the second validator the proposer by incrementing round |
278 |
| - round++ |
279 |
| - incrementRound(vss[1:]...) |
| 281 | + partSize := types.BlockPartSizeBytes |
280 | 282 |
|
281 |
| - propBlockParts, err := propBlock.MakePartSet(partSize) |
282 |
| - require.NoError(t, err) |
283 |
| - blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} |
284 |
| - proposal := types.NewProposal(height, round, -1, blockID) |
285 |
| - p := proposal.ToProto() |
286 |
| - if err := vs2.SignProposal(cs1.state.ChainID, p); err != nil { |
287 |
| - t.Fatal("failed to sign bad proposal", err) |
288 |
| - } |
289 |
| - proposal.Signature = p.Signature |
| 283 | + propBlock, propBlockParts := findBlockSizeLimit(t, height, maxBytes, cs1, partSize, testCase.oversized) |
290 | 284 |
|
291 |
| - totalBytes := 0 |
292 |
| - for i := 0; i < int(propBlockParts.Total()); i++ { |
293 |
| - part := propBlockParts.GetPart(i) |
294 |
| - totalBytes += len(part.Bytes) |
295 |
| - } |
| 285 | + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) |
| 286 | + voteCh := subscribe(cs1.eventBus, types.EventQueryVote) |
296 | 287 |
|
297 |
| - if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { |
298 |
| - t.Fatal(err) |
299 |
| - } |
| 288 | + // make the second validator the proposer by incrementing round |
| 289 | + round++ |
| 290 | + incrementRound(vss[1:]...) |
300 | 291 |
|
301 |
| - // start the machine |
302 |
| - startTestRound(cs1, height, round) |
| 292 | + blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} |
| 293 | + proposal := types.NewProposal(height, round, -1, blockID) |
| 294 | + p := proposal.ToProto() |
| 295 | + if err := vs2.SignProposal(cs1.state.ChainID, p); err != nil { |
| 296 | + t.Fatal("failed to sign bad proposal", err) |
| 297 | + } |
| 298 | + proposal.Signature = p.Signature |
303 | 299 |
|
304 |
| - t.Log("Block Sizes", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes) |
| 300 | + totalBytes := 0 |
| 301 | + for i := 0; i < int(propBlockParts.Total()); i++ { |
| 302 | + part := propBlockParts.GetPart(i) |
| 303 | + totalBytes += len(part.Bytes) |
| 304 | + } |
305 | 305 |
|
306 |
| - // c1 should log an error with the block part message as it exceeds the consensus params. The |
307 |
| - // block is not added to cs.ProposalBlock so the node timeouts. |
308 |
| - ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) |
| 306 | + if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { |
| 307 | + t.Fatal(err) |
| 308 | + } |
309 | 309 |
|
310 |
| - // and then should send nil prevote and precommit regardless of whether other validators prevote and |
311 |
| - // precommit on it |
312 |
| - ensurePrevote(voteCh, height, round) |
313 |
| - validatePrevote(t, cs1, round, vss[0], nil) |
| 310 | + // start the machine |
| 311 | + startTestRound(cs1, height, round) |
| 312 | + |
| 313 | + t.Log("Block Sizes;", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes) |
| 314 | + |
| 315 | + validateHash := propBlock.Hash() |
| 316 | + lockedRound := int32(1) |
| 317 | + if testCase.oversized { |
| 318 | + validateHash = nil |
| 319 | + lockedRound = -1 |
| 320 | + // if the block is oversized cs1 should log an error with the block part message as it exceeds |
| 321 | + // the consensus params. The block is not added to cs.ProposalBlock so the node timeouts. |
| 322 | + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) |
| 323 | + // and then should send nil prevote and precommit regardless of whether other validators prevote and |
| 324 | + // precommit on it |
| 325 | + } |
| 326 | + ensurePrevote(voteCh, height, round) |
| 327 | + validatePrevote(t, cs1, round, vss[0], validateHash) |
314 | 328 |
|
315 |
| - bps, err := propBlock.MakePartSet(partSize) |
316 |
| - require.NoError(t, err) |
| 329 | + bps, err := propBlock.MakePartSet(partSize) |
| 330 | + require.NoError(t, err) |
317 | 331 |
|
318 |
| - signAddVotes(cs1, cmtproto.PrevoteType, propBlock.Hash(), bps.Header(), false, vs2) |
319 |
| - ensurePrevote(voteCh, height, round) |
320 |
| - ensurePrecommit(voteCh, height, round) |
321 |
| - validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) |
| 332 | + signAddVotes(cs1, cmtproto.PrevoteType, propBlock.Hash(), bps.Header(), false, vs2) |
| 333 | + ensurePrevote(voteCh, height, round) |
| 334 | + ensurePrecommit(voteCh, height, round) |
| 335 | + validatePrecommit(t, cs1, round, lockedRound, vss[0], validateHash, validateHash) |
322 | 336 |
|
323 |
| - bps2, err := propBlock.MakePartSet(partSize) |
324 |
| - require.NoError(t, err) |
325 |
| - signAddVotes(cs1, cmtproto.PrecommitType, propBlock.Hash(), bps2.Header(), true, vs2) |
| 337 | + bps2, err := propBlock.MakePartSet(partSize) |
| 338 | + require.NoError(t, err) |
| 339 | + signAddVotes(cs1, cmtproto.PrecommitType, propBlock.Hash(), bps2.Header(), true, vs2) |
| 340 | + }) |
| 341 | + } |
326 | 342 | }
|
327 | 343 |
|
328 | 344 | //----------------------------------------------------------------------------------------------------
|
@@ -2516,3 +2532,33 @@ func signAddPrecommitWithExtension(
|
2516 | 2532 | require.NoError(t, err, "failed to sign vote")
|
2517 | 2533 | addVotes(cs, v)
|
2518 | 2534 | }
|
| 2535 | + |
| 2536 | +func findBlockSizeLimit(t *testing.T, height, maxBytes int64, cs *State, partSize uint32, oversized bool) (*types.Block, *types.PartSet) { |
| 2537 | + var offset int64 |
| 2538 | + if !oversized { |
| 2539 | + offset = -2 |
| 2540 | + } |
| 2541 | + softMaxDataBytes := int(types.MaxDataBytes(maxBytes, 0, 0)) |
| 2542 | + for i := softMaxDataBytes; i < softMaxDataBytes*2; i++ { |
| 2543 | + propBlock := cs.state.MakeBlock( |
| 2544 | + height, |
| 2545 | + []types.Tx{[]byte("a=" + strings.Repeat("o", i-2))}, |
| 2546 | + &types.Commit{}, |
| 2547 | + nil, |
| 2548 | + cs.privValidatorPubKey.Address(), |
| 2549 | + ) |
| 2550 | + |
| 2551 | + propBlockParts, err := propBlock.MakePartSet(partSize) |
| 2552 | + require.NoError(t, err) |
| 2553 | + if propBlockParts.ByteSize() > maxBytes+offset { |
| 2554 | + s := "real max" |
| 2555 | + if oversized { |
| 2556 | + s = "off-by-1" |
| 2557 | + } |
| 2558 | + t.Log("Detected "+s+" data size for block;", "size", i, "softMaxDataBytes", softMaxDataBytes) |
| 2559 | + return propBlock, propBlockParts |
| 2560 | + } |
| 2561 | + } |
| 2562 | + require.Fail(t, "We shouldn't hit the end of the loop") |
| 2563 | + return nil, nil |
| 2564 | +} |
0 commit comments