Skip to content

Commit 23fe3ef

Browse files
refactor: validator set error handling (#2713)
1 parent b2dff9c commit 23fe3ef

File tree

2 files changed

+51
-11
lines changed

2 files changed

+51
-11
lines changed

types/validator_set.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -310,28 +310,40 @@ func (vals *ValidatorSet) Size() int {
310310
}
311311

312312
// Forces recalculation of the set's total voting power.
313-
// Panics if total voting power is bigger than MaxTotalVotingPower.
314-
func (vals *ValidatorSet) updateTotalVotingPower() {
313+
// Returns an error if total voting power exceeds MaxTotalVotingPower.
314+
func (vals *ValidatorSet) updateTotalVotingPower() error {
315315
sum := int64(0)
316316
for _, val := range vals.Validators {
317317
// mind overflow
318318
sum = safeAddClip(sum, val.VotingPower)
319319
if sum > MaxTotalVotingPower {
320-
panic(fmt.Sprintf(
321-
"Total voting power should be guarded to not exceed %v; got: %v",
322-
MaxTotalVotingPower,
323-
sum))
320+
return fmt.Errorf("total voting power %d exceeds maximum %d", sum, MaxTotalVotingPower)
324321
}
325322
}
326323

327324
vals.totalVotingPower = sum
325+
return nil
326+
}
327+
328+
// TotalVotingPowerSafe returns the sum of the voting powers of all validators,
329+
// or an error if the total exceeds MaxTotalVotingPower.
330+
func (vals *ValidatorSet) TotalVotingPowerSafe() (int64, error) {
331+
if vals.totalVotingPower == 0 {
332+
if err := vals.updateTotalVotingPower(); err != nil {
333+
return 0, err
334+
}
335+
}
336+
return vals.totalVotingPower, nil
328337
}
329338

330339
// TotalVotingPower returns the sum of the voting powers of all validators.
331340
// It recomputes the total voting power if required.
332341
func (vals *ValidatorSet) TotalVotingPower() int64 {
333342
if vals.totalVotingPower == 0 {
334-
vals.updateTotalVotingPower()
343+
err := vals.updateTotalVotingPower()
344+
if err != nil {
345+
panic(err)
346+
}
335347
}
336348
return vals.totalVotingPower
337349
}
@@ -665,7 +677,9 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes
665677
// Should go after additions.
666678
vals.checkAllKeysHaveSameType()
667679

668-
vals.updateTotalVotingPower() // will panic if total voting power > MaxTotalVotingPower
680+
if err = vals.updateTotalVotingPower(); err != nil {
681+
panic(err)
682+
}
669683

670684
// Scale and center.
671685
vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower())
@@ -935,7 +949,10 @@ func ValidatorSetFromProto(vp *cmtproto.ValidatorSet) (*ValidatorSet, error) {
935949
// power hence we need to recompute it.
936950
// FIXME: We should look to remove TotalVotingPower from proto or add it in the validators hash
937951
// so we don't have to do this
938-
vals.TotalVotingPower()
952+
// NOTE: Use TotalVotingPowerSafe to return error on invalid input.
953+
if _, err := vals.TotalVotingPowerSafe(); err != nil {
954+
return nil, err
955+
}
939956

940957
return vals, vals.ValidateBasic()
941958
}
@@ -960,7 +977,9 @@ func ValidatorSetFromExistingValidators(valz []*Validator) (*ValidatorSet, error
960977
}
961978
vals.checkAllKeysHaveSameType()
962979
vals.Proposer = vals.findPreviousProposer()
963-
vals.updateTotalVotingPower()
980+
if err := vals.updateTotalVotingPower(); err != nil {
981+
return nil, err
982+
}
964983
sort.Sort(ValidatorsByVotingPower(vals.Validators))
965984
return vals, nil
966985
}

types/validator_set_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/cometbft/cometbft/crypto"
1616
"github.com/cometbft/cometbft/crypto/ed25519"
17+
cryptoenc "github.com/cometbft/cometbft/crypto/encoding"
1718
"github.com/cometbft/cometbft/crypto/sr25519"
1819
cmtmath "github.com/cometbft/cometbft/libs/math"
1920
cmtrand "github.com/cometbft/cometbft/libs/rand"
@@ -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 {
@@ -832,7 +852,8 @@ func verifyValidatorSet(t *testing.T, valSet *ValidatorSet) {
832852

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

0 commit comments

Comments
 (0)