Skip to content

Commit 1f48ff9

Browse files
mergify[bot]Danielsergio-menaandynog
authored
feat(consensus): additional sanity checks for the size of proposed blocks (backport cometbft#1408) (cometbft#2139)
This is an automatic backport of pull request cometbft#1408 done by [Mergify](https://mergify.com). Cherry-pick of 28ad4d2 has failed: ``` On branch mergify/bp/v0.38.x/pr-1408 Your branch is up to date with 'origin/v0.38.x'. You are currently cherry-picking commit 28ad4d2. (fix conflicts and run "git cherry-pick --continue") (use "git cherry-pick --skip" to skip this patch) (use "git cherry-pick --abort" to cancel the cherry-pick operation) Changes to be committed: modified: consensus/state.go modified: consensus/state_test.go modified: crypto/merkle/proof.go modified: evidence/pool_test.go modified: state/execution_test.go modified: types/event_bus_test.go modified: types/part_set.go modified: types/part_set_test.go Unmerged paths: (use "git add/rm <file>..." as appropriate to mark resolution) deleted by us: internal/consensus/errors.go both modified: state/store_test.go both modified: store/store_test.go ``` To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally --- <details> <summary>Mergify commands and options</summary> <br /> More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com </details> --------- Co-authored-by: Daniel <daniel.cason@informal.systems> Co-authored-by: Sergio Mena <sergio@informal.systems> Co-authored-by: Andy Nogueira <me@andynogueira.dev>
1 parent 6b9defb commit 1f48ff9

9 files changed

Lines changed: 90 additions & 23 deletions

File tree

consensus/state.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var (
3737
ErrInvalidProposalPOLRound = errors.New("error invalid proposal POL round")
3838
ErrAddingVote = errors.New("error adding vote")
3939
ErrSignatureFoundInPastBlocks = errors.New("found signature from the same key")
40+
ErrProposalTooManyParts = errors.New("proposal block has too many parts")
4041

4142
errPubKeyIsNotSet = errors.New("pubkey is not set. Look for \"Can't get private validator pubkey\" errors")
4243
)
@@ -1922,6 +1923,15 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error {
19221923
return ErrInvalidProposalSignature
19231924
}
19241925

1926+
// Validate the proposed block size, derived from its PartSetHeader
1927+
maxBytes := cs.state.ConsensusParams.Block.MaxBytes
1928+
if maxBytes == -1 {
1929+
maxBytes = int64(types.MaxBlockSizeBytes)
1930+
}
1931+
if int64(proposal.BlockID.PartSetHeader.Total) > (maxBytes-1)/int64(types.BlockPartSizeBytes)+1 {
1932+
return ErrProposalTooManyParts
1933+
}
1934+
19251935
proposal.Signature = p.Signature
19261936
cs.Proposal = proposal
19271937
// We don't update cs.ProposalBlockParts if it is already set.

consensus/state_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func TestStateBadProposal(t *testing.T) {
257257
}
258258

259259
func TestStateOversizedBlock(t *testing.T) {
260-
const maxBytes = 2000
260+
const maxBytes = int64(types.BlockPartSizeBytes)
261261

262262
for _, testCase := range []struct {
263263
name string
@@ -303,14 +303,21 @@ func TestStateOversizedBlock(t *testing.T) {
303303
totalBytes += len(part.Bytes)
304304
}
305305

306+
maxBlockParts := maxBytes / int64(types.BlockPartSizeBytes)
307+
if maxBytes > maxBlockParts*int64(types.BlockPartSizeBytes) {
308+
maxBlockParts++
309+
}
310+
numBlockParts := int64(propBlockParts.Total())
311+
306312
if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil {
307313
t.Fatal(err)
308314
}
309315

310316
// start the machine
311317
startTestRound(cs1, height, round)
312318

313-
t.Log("Block Sizes;", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes)
319+
t.Log("Block Sizes;", "Limit", maxBytes, "Current", totalBytes)
320+
t.Log("Proposal Parts;", "Maximum", maxBlockParts, "Current", numBlockParts)
314321

315322
validateHash := propBlock.Hash()
316323
lockedRound := int32(1)
@@ -326,6 +333,11 @@ func TestStateOversizedBlock(t *testing.T) {
326333
ensurePrevote(voteCh, height, round)
327334
validatePrevote(t, cs1, round, vss[0], validateHash)
328335

336+
// Should not accept a Proposal with too many block parts
337+
if numBlockParts > maxBlockParts {
338+
require.Nil(t, cs1.Proposal)
339+
}
340+
329341
bps, err := propBlock.MakePartSet(partSize)
330342
require.NoError(t, err)
331343

crypto/merkle/proof.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ const (
2424
// everything. This also affects the generalized proof system as
2525
// well.
2626
type Proof struct {
27-
Total int64 `json:"total"` // Total number of items.
28-
Index int64 `json:"index"` // Index of item to prove.
29-
LeafHash []byte `json:"leaf_hash"` // Hash of item value.
30-
Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child.
27+
Total int64 `json:"total"` // Total number of items.
28+
Index int64 `json:"index"` // Index of item to prove.
29+
LeafHash []byte `json:"leaf_hash"` // Hash of item value.
30+
Aunts [][]byte `json:"aunts,omitempty"` // Hashes from leaf's sibling to a root's child.
3131
}
3232

3333
// ProofsFromByteSlices computes inclusion proof for given items.

evidence/pool_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo
416416
block := state.MakeBlock(i, test.MakeNTxs(i, 1), lastCommit.ToCommit(), nil, state.Validators.Proposer.Address)
417417
block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute)
418418
block.Header.Version = cmtversion.Consensus{Block: version.BlockProtocol, App: 1}
419-
const parts = 1
420-
partSet, err := block.MakePartSet(parts)
419+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
421420
if err != nil {
422421
return nil, err
423422
}

state/execution_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import (
3636

3737
var (
3838
chainID = "execution_chain"
39-
testPartSize uint32 = 65536
39+
testPartSize uint32 = types.BlockPartSizeBytes
4040
)
4141

4242
func TestApplyBlock(t *testing.T) {

store/store_test.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package store
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"os"
67
"runtime/debug"
@@ -154,10 +155,12 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
154155
}
155156
}
156157

157-
// save a block
158-
block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
159-
validPartSet, err := block.MakePartSet(2)
158+
// save a block big enough to have two block parts
159+
txs := []types.Tx{make([]byte, types.BlockPartSizeBytes)} // TX taking one block part alone
160+
block := state.MakeBlock(bs.Height()+1, txs, new(types.Commit), nil, state.Validators.GetProposer().Address)
161+
validPartSet, err := block.MakePartSet(types.BlockPartSizeBytes)
160162
require.NoError(t, err)
163+
require.GreaterOrEqual(t, validPartSet.Total(), uint32(2))
161164
part2 := validPartSet.GetPart(1)
162165

163166
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
@@ -399,7 +402,7 @@ func TestSaveBlockWithExtendedCommitPanicOnAbsentExtension(t *testing.T) {
399402
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
400403

401404
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
402-
ps, err := block.MakePartSet(2)
405+
ps, err := block.MakePartSet(types.BlockPartSizeBytes)
403406
require.NoError(t, err)
404407
testCase.malleateCommit(seenCommit)
405408
if testCase.shouldPanic {
@@ -439,7 +442,7 @@ func TestLoadBlockExtendedCommit(t *testing.T) {
439442
h := bs.Height() + 1
440443
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
441444
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
442-
ps, err := block.MakePartSet(2)
445+
ps, err := block.MakePartSet(types.BlockPartSizeBytes)
443446
require.NoError(t, err)
444447
if testCase.saveExtended {
445448
bs.SaveBlockWithExtendedCommit(block, ps, seenCommit)
@@ -468,7 +471,7 @@ func TestLoadBaseMeta(t *testing.T) {
468471

469472
for h := int64(1); h <= 10; h++ {
470473
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
471-
partSet, err := block.MakePartSet(2)
474+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
472475
require.NoError(t, err)
473476
seenCommit := makeTestExtCommit(h, cmttime.Now())
474477
bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit)
@@ -513,7 +516,7 @@ func TestLoadBlockPart(t *testing.T) {
513516

514517
// 3. A good block serialized and saved to the DB should be retrievable
515518
block := state.MakeBlock(height, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
516-
partSet, err := block.MakePartSet(2)
519+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
517520
require.NoError(t, err)
518521
part1 := partSet.GetPart(0)
519522

@@ -524,7 +527,13 @@ func TestLoadBlockPart(t *testing.T) {
524527
gotPart, _, panicErr := doFn(loadPart)
525528
require.Nil(t, panicErr, "an existent and proper block should not panic")
526529
require.Nil(t, res, "a properly saved block should return a proper block")
527-
require.Equal(t, gotPart.(*types.Part), part1,
530+
531+
// Having to do this because of https://github.com/stretchr/testify/issues/1141
532+
gotPartJSON, err := json.Marshal(gotPart.(*types.Part))
533+
require.NoError(t, err)
534+
part1JSON, err := json.Marshal(part1)
535+
require.NoError(t, err)
536+
require.JSONEq(t, string(gotPartJSON), string(part1JSON),
528537
"expecting successful retrieval of previously saved block")
529538
}
530539

@@ -552,7 +561,7 @@ func TestPruneBlocks(t *testing.T) {
552561
// make more than 1000 blocks, to test batch deletions
553562
for h := int64(1); h <= 1500; h++ {
554563
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
555-
partSet, err := block.MakePartSet(2)
564+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
556565
require.NoError(t, err)
557566
seenCommit := makeTestExtCommit(h, cmttime.Now())
558567
bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit)
@@ -681,7 +690,7 @@ func TestLoadBlockMetaByHash(t *testing.T) {
681690
bs := NewBlockStore(dbm.NewMemDB())
682691

683692
b1 := state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
684-
partSet, err := b1.MakePartSet(2)
693+
partSet, err := b1.MakePartSet(types.BlockPartSizeBytes)
685694
require.NoError(t, err)
686695
seenCommit := makeTestExtCommit(1, cmttime.Now())
687696
bs.SaveBlock(b1, partSet, seenCommit.ToCommit())
@@ -698,7 +707,7 @@ func TestBlockFetchAtHeight(t *testing.T) {
698707
require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
699708
block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
700709

701-
partSet, err := block.MakePartSet(2)
710+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
702711
require.NoError(t, err)
703712
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
704713
bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit)

types/event_bus_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) {
9696
}()
9797

9898
var ps *PartSet
99-
ps, err = block.MakePartSet(MaxBlockSizeBytes)
99+
ps, err = block.MakePartSet(BlockPartSizeBytes)
100100
require.NoError(t, err)
101101

102102
err = eventBus.PublishEventNewBlock(EventDataNewBlock{

types/part_set.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
var (
1919
ErrPartSetUnexpectedIndex = errors.New("error part set unexpected index")
2020
ErrPartSetInvalidProof = errors.New("error part set invalid proof")
21+
ErrPartTooBig = errors.New("error part size too big")
22+
ErrPartInvalidSize = errors.New("error inner part with invalid size")
2123
)
2224

2325
type Part struct {
@@ -29,7 +31,11 @@ type Part struct {
2931
// ValidateBasic performs basic validation.
3032
func (part *Part) ValidateBasic() error {
3133
if len(part.Bytes) > int(BlockPartSizeBytes) {
32-
return fmt.Errorf("too big: %d bytes, max: %d", len(part.Bytes), BlockPartSizeBytes)
34+
return ErrPartTooBig
35+
}
36+
// All parts except the last one should have the same constant size.
37+
if int64(part.Index) < part.Proof.Total-1 && len(part.Bytes) != int(BlockPartSizeBytes) {
38+
return ErrPartInvalidSize
3339
}
3440
if err := part.Proof.ValidateBasic(); err != nil {
3541
return fmt.Errorf("wrong Proof: %w", err)
@@ -289,6 +295,11 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) {
289295
return false, nil
290296
}
291297

298+
// The proof should be compatible with the number of parts.
299+
if part.Proof.Total != int64(ps.total) {
300+
return false, ErrPartSetInvalidProof
301+
}
302+
292303
// Check hash proof
293304
if part.Proof.Verify(ps.Hash(), part.Bytes) != nil {
294305
return false, ErrPartSetInvalidProof

types/part_set_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ func TestWrongProof(t *testing.T) {
8686
if added || err == nil {
8787
t.Errorf("expected to fail adding a part with bad bytes.")
8888
}
89+
90+
// Test adding a part with wrong proof index.
91+
part = partSet.GetPart(2)
92+
part.Proof.Index = 1
93+
added, err = partSet2.AddPart(part)
94+
if added || err == nil {
95+
t.Errorf("expected to fail adding a part with bad proof index.")
96+
}
97+
98+
// Test adding a part with wrong proof total.
99+
part = partSet.GetPart(3)
100+
part.Proof.Total = int64(partSet.Total() - 1)
101+
added, err = partSet2.AddPart(part)
102+
if added || err == nil {
103+
t.Errorf("expected to fail adding a part with bad proof total.")
104+
}
89105
}
90106

91107
func TestPartSetHeaderValidateBasic(t *testing.T) {
@@ -117,9 +133,19 @@ func TestPartValidateBasic(t *testing.T) {
117133
}{
118134
{"Good Part", func(pt *Part) {}, false},
119135
{"Too big part", func(pt *Part) { pt.Bytes = make([]byte, BlockPartSizeBytes+1) }, true},
136+
{"Good small last part", func(pt *Part) {
137+
pt.Index = 1
138+
pt.Bytes = make([]byte, BlockPartSizeBytes-1)
139+
pt.Proof.Total = 2
140+
}, false},
141+
{"Too small inner part", func(pt *Part) {
142+
pt.Index = 0
143+
pt.Bytes = make([]byte, BlockPartSizeBytes-1)
144+
pt.Proof.Total = 2
145+
}, true},
120146
{"Too big proof", func(pt *Part) {
121147
pt.Proof = merkle.Proof{
122-
Total: 1,
148+
Total: 2,
123149
Index: 1,
124150
LeafHash: make([]byte, 1024*1024),
125151
}

0 commit comments

Comments
 (0)