Skip to content

Commit dd0e3a3

Browse files
technicallytymaxim-inj
authored andcommitted
no-panic
1 parent 853c727 commit dd0e3a3

2 files changed

Lines changed: 50 additions & 11 deletions

File tree

types/validator_set.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -323,28 +323,39 @@ func (vals *ValidatorSet) Size() int {
323323
}
324324

325325
// updateTotalVotingPower forces recalculation of the set's total voting power.
326-
// Panics if total voting power is bigger than MaxTotalVotingPower.
327-
func (vals *ValidatorSet) updateTotalVotingPower() {
326+
// Returns an error if total voting power exceeds MaxTotalVotingPower.
327+
func (vals *ValidatorSet) updateTotalVotingPower() error {
328328
sum := int64(0)
329329
for _, val := range vals.Validators {
330330
// mind overflow
331331
sum = safeAddClip(sum, val.VotingPower)
332332
if sum > MaxTotalVotingPower {
333-
panic(fmt.Sprintf(
334-
"Total voting power should be guarded to not exceed %v; got: %v",
335-
MaxTotalVotingPower,
336-
sum))
333+
return fmt.Errorf("total voting power %d exceeds maximum %d", sum, MaxTotalVotingPower)
337334
}
338335
}
339336

340337
vals.totalVotingPower = sum
338+
return nil
339+
}
340+
341+
// TotalVotingPowerSafe returns the sum of the voting powers of all validators,
342+
// or an error if the total exceeds MaxTotalVotingPower.
343+
func (vals *ValidatorSet) TotalVotingPowerSafe() (int64, error) {
344+
if vals.totalVotingPower == 0 {
345+
if err := vals.updateTotalVotingPower(); err != nil {
346+
return 0, err
347+
}
348+
}
349+
return vals.totalVotingPower, nil
341350
}
342351

343352
// TotalVotingPower returns the sum of the voting powers of all validators.
344353
// It recomputes the total voting power if required.
345354
func (vals *ValidatorSet) TotalVotingPower() int64 {
346355
if vals.totalVotingPower == 0 {
347-
vals.updateTotalVotingPower()
356+
if err := vals.updateTotalVotingPower(); err != nil {
357+
panic(err)
358+
}
348359
}
349360
return vals.totalVotingPower
350361
}
@@ -682,7 +693,9 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes
682693
// Should go after additions.
683694
vals.checkAllKeysHaveSameType()
684695

685-
vals.updateTotalVotingPower() // will panic if total voting power > MaxTotalVotingPower
696+
if err = vals.updateTotalVotingPower(); err != nil {
697+
panic(err)
698+
}
686699

687700
// Scale and center.
688701
vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower())
@@ -952,7 +965,10 @@ func ValidatorSetFromProto(vp *cmtproto.ValidatorSet) (*ValidatorSet, error) {
952965
// power hence we need to recompute it.
953966
// FIXME: We should look to remove TotalVotingPower from proto or add it in the validators hash
954967
// so we don't have to do this
955-
vals.TotalVotingPower()
968+
// NOTE: Use TotalVotingPowerSafe to return error instead of panicking on invalid input.
969+
if _, err := vals.TotalVotingPowerSafe(); err != nil {
970+
return nil, err
971+
}
956972

957973
return vals, vals.ValidateBasic()
958974
}
@@ -977,7 +993,9 @@ func ValidatorSetFromExistingValidators(valz []*Validator) (*ValidatorSet, error
977993
}
978994
vals.checkAllKeysHaveSameType()
979995
vals.Proposer = vals.findPreviousProposer()
980-
vals.updateTotalVotingPower()
996+
if err := vals.updateTotalVotingPower(); err != nil {
997+
return nil, err
998+
}
981999
sort.Sort(ValidatorsByVotingPower(vals.Validators))
9821000
return vals, nil
9831001
}

types/validator_set_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1"
1616
"github.com/cometbft/cometbft/crypto"
1717
"github.com/cometbft/cometbft/crypto/ed25519"
18+
cryptoenc "github.com/cometbft/cometbft/crypto/encoding"
1819
"github.com/cometbft/cometbft/crypto/secp256k1"
1920
cmtrand "github.com/cometbft/cometbft/internal/rand"
2021
cmtmath "github.com/cometbft/cometbft/libs/math"
@@ -471,6 +472,25 @@ func TestValidatorSetTotalVotingPowerPanicsOnOverflow(t *testing.T) {
471472
assert.Panics(t, shouldPanic)
472473
}
473474

475+
func TestValidatorSetFromProtoReturnsErrorOnOverflow(t *testing.T) {
476+
// ValidatorSetFromProto should return an error instead of panicking when total voting power exceeds MaxTotalVotingPower.
477+
pubKey := ed25519.GenPrivKey().PubKey()
478+
pkProto, err := cryptoenc.PubKeyToProto(pubKey)
479+
require.NoError(t, err)
480+
481+
protoVals := &cmtproto.ValidatorSet{
482+
Validators: []*cmtproto.Validator{
483+
{Address: pubKey.Address(), PubKey: pkProto, VotingPower: math.MaxInt64, ProposerPriority: 0},
484+
{Address: pubKey.Address(), PubKey: pkProto, VotingPower: math.MaxInt64, ProposerPriority: 0},
485+
},
486+
Proposer: &cmtproto.Validator{Address: pubKey.Address(), PubKey: pkProto, VotingPower: math.MaxInt64, ProposerPriority: 0},
487+
}
488+
489+
_, err = ValidatorSetFromProto(protoVals)
490+
require.Error(t, err)
491+
assert.Contains(t, err.Error(), "exceeds maximum")
492+
}
493+
474494
func TestAvgProposerPriority(t *testing.T) {
475495
// Create Validator set without calling IncrementProposerPriority:
476496
tcs := []struct {
@@ -833,7 +853,8 @@ func verifyValidatorSet(t *testing.T, valSet *ValidatorSet) {
833853

834854
// verify that the set's total voting power has been updated
835855
tvp := valSet.totalVotingPower
836-
valSet.updateTotalVotingPower()
856+
err := valSet.updateTotalVotingPower()
857+
require.NoError(t, err)
837858
expectedTvp := valSet.TotalVotingPower()
838859
assert.Equal(t, expectedTvp, tvp,
839860
"expected TVP %d. Got %d, valSet=%s", expectedTvp, tvp, valSet)

0 commit comments

Comments
 (0)