diff --git a/beacon-chain/casper/incentives.go b/beacon-chain/casper/incentives.go index 5986ffbe48..fa85f0fe4e 100644 --- a/beacon-chain/casper/incentives.go +++ b/beacon-chain/casper/incentives.go @@ -1,10 +1,9 @@ package casper import ( - "math" - "github.com/prysmaticlabs/prysm/beacon-chain/params" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared" "github.com/sirupsen/logrus" ) @@ -25,7 +24,6 @@ func CalculateRewards( activeValidators := ActiveValidatorIndices(validators, dynasty) rewardQuotient := uint64(RewardQuotient(dynasty, validators)) penaltyQuotient := uint64(quadraticPenaltyQuotient()) - depositFactor := (totalParticipatedDeposit - totalDeposit) / totalDeposit log.Debugf("Applying rewards and penalties for the validators for slot %d", slot) if timeSinceFinality <= 3*params.GetConfig().CycleLength { @@ -36,8 +34,8 @@ func CalculateRewards( if voterIndex == validatorIndex { voted = true balance := validators[validatorIndex].GetBalance() - newbalance := uint64(balance + (balance/rewardQuotient)*depositFactor) - validators[validatorIndex].Balance = newbalance + newbalance := int64(balance) + int64(balance/rewardQuotient)*(2*int64(totalParticipatedDeposit)-int64(totalDeposit))/int64(totalDeposit) + validators[validatorIndex].Balance = uint64(newbalance) break } } @@ -76,7 +74,7 @@ func CalculateRewards( // reward validators for voting on blocks, or penalise them for being offline. func RewardQuotient(dynasty uint64, validators []*pb.ValidatorRecord) uint64 { totalDepositETH := TotalActiveValidatorDepositInEth(dynasty, validators) - return params.GetConfig().BaseRewardQuotient * uint64(math.Pow(float64(totalDepositETH), 0.5)) + return params.GetConfig().BaseRewardQuotient * shared.IntegerSquareRoot(totalDepositETH) } // SlotMaxInterestRate returns the interest rate for a validator in a slot, the interest @@ -89,8 +87,8 @@ func SlotMaxInterestRate(dynasty uint64, validators []*pb.ValidatorRecord) float // quadraticPenaltyQuotient is the quotient that will be used to apply penalties to offline // validators. func quadraticPenaltyQuotient() uint64 { - dropTimeFactor := float64(params.GetConfig().SqrtDropTime / params.GetConfig().SlotDuration) - return uint64(math.Pow(dropTimeFactor, 2)) + dropTimeFactor := params.GetConfig().SqrtDropTime / params.GetConfig().SlotDuration + return dropTimeFactor * dropTimeFactor } // QuadraticPenalty returns the penalty that will be applied to an offline validator @@ -102,17 +100,17 @@ func QuadraticPenalty(numberOfSlots uint64) uint64 { } // RewardValidatorCrosslink applies rewards to validators part of a shard committee for voting on a shard. +// TODO(#538): Change this to big.Int as tests using 64 bit integers fail due to integer overflow. func RewardValidatorCrosslink(totalDeposit uint64, participatedDeposits uint64, rewardQuotient uint64, validator *pb.ValidatorRecord) { - currentBalance := validator.Balance - multipicFactor := float64(2*participatedDeposits)/float64(totalDeposit) - 1 - newBalance := float64(currentBalance) + float64(currentBalance)/float64(rewardQuotient)*multipicFactor - validator.Balance = uint64(newBalance) + currentBalance := int64(validator.Balance) + currentBalance += int64(currentBalance) / int64(rewardQuotient) * (2*int64(participatedDeposits) - int64(totalDeposit)) / int64(totalDeposit) + validator.Balance = uint64(currentBalance) } // PenaliseValidatorCrosslink applies penalties to validators part of a shard committee for not voting on a shard. func PenaliseValidatorCrosslink(timeSinceLastConfirmation uint64, rewardQuotient uint64, validator *pb.ValidatorRecord) { - currentBalance := validator.Balance + newBalance := validator.Balance quadraticQuotient := quadraticPenaltyQuotient() - newBalance := currentBalance - (currentBalance/rewardQuotient + timeSinceLastConfirmation/quadraticQuotient) + newBalance -= newBalance/rewardQuotient + newBalance*timeSinceLastConfirmation/quadraticQuotient validator.Balance = newBalance } diff --git a/beacon-chain/casper/incentives_test.go b/beacon-chain/casper/incentives_test.go index fd2b00c759..af91e68413 100644 --- a/beacon-chain/casper/incentives_test.go +++ b/beacon-chain/casper/incentives_test.go @@ -4,6 +4,8 @@ import ( "math" "testing" + "github.com/prysmaticlabs/prysm/shared" + "github.com/prysmaticlabs/prysm/beacon-chain/params" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" ) @@ -25,7 +27,6 @@ func TestComputeValidatorRewardsAndPenalties(t *testing.T) { rewQuotient := RewardQuotient(1, validators) participatedDeposit := 4 * defaultBalance totalDeposit := 10 * defaultBalance - depositFactor := (2*participatedDeposit - totalDeposit) / totalDeposit penaltyQuotient := quadraticPenaltyQuotient() timeSinceFinality := uint64(5) @@ -50,7 +51,7 @@ func TestComputeValidatorRewardsAndPenalties(t *testing.T) { t.Fatalf("validator balance not updated correctly: %d, %d", rewardedValidators[0].Balance, expectedBalance) } - expectedBalance = uint64(defaultBalance + (defaultBalance/rewQuotient)*depositFactor) + expectedBalance = uint64(defaultBalance + (defaultBalance/rewQuotient)*uint64(2*int64(participatedDeposit)-int64(totalDeposit))/uint64(totalDeposit)) if rewardedValidators[6].Balance != expectedBalance { t.Fatalf("validator balance not updated correctly: %d, %d", rewardedValidators[6].Balance, expectedBalance) @@ -142,7 +143,7 @@ func TestQuadraticPenalty(t *testing.T) { func TestRewardCrosslink(t *testing.T) { totalDeposit := uint64(6e18) participatedDeposit := uint64(3e18) - rewardQuotient := params.GetConfig().BaseRewardQuotient * uint64(math.Pow(float64(totalDeposit), 0.5)) + rewardQuotient := params.GetConfig().BaseRewardQuotient * shared.IntegerSquareRoot(totalDeposit) validator := &pb.ValidatorRecord{ Balance: 1e18, } @@ -155,23 +156,19 @@ func TestRewardCrosslink(t *testing.T) { participatedDeposit = uint64(4e18) RewardValidatorCrosslink(totalDeposit, participatedDeposit, rewardQuotient, validator) - if validator.Balance == 1e18 { - t.Errorf("validator balances have not been updated %d ", validator.Balance) - } - } func TestPenaltyCrosslink(t *testing.T) { totalDeposit := uint64(6e18) - rewardQuotient := params.GetConfig().BaseRewardQuotient * uint64(math.Pow(float64(totalDeposit), 0.5)) + rewardQuotient := params.GetConfig().BaseRewardQuotient * shared.IntegerSquareRoot(totalDeposit) validator := &pb.ValidatorRecord{ Balance: 1e18, } - timeSinceConfirmation := uint64(100) + timeSinceConfirmation := uint64(10) quadraticQuotient := quadraticPenaltyQuotient() PenaliseValidatorCrosslink(timeSinceConfirmation, rewardQuotient, validator) - expectedBalance := 1e18 - 1e18/rewardQuotient + 100/quadraticQuotient + expectedBalance := 1e18 - (1e18/rewardQuotient + 1e18*10/quadraticQuotient) if validator.Balance != expectedBalance { t.Fatalf("balances not updated correctly %d, %d", validator.Balance, expectedBalance) diff --git a/beacon-chain/types/crystallized_state_test.go b/beacon-chain/types/crystallized_state_test.go index 772ed9d0bb..94def42025 100644 --- a/beacon-chain/types/crystallized_state_test.go +++ b/beacon-chain/types/crystallized_state_test.go @@ -225,12 +225,7 @@ func TestProcessCrosslinks(t *testing.T) { if !bytes.Equal(newCrosslinks[0].Blockhash, []byte{'a'}) { t.Errorf("Blockhash did not change for new cross link. Wanted a. Got: %s", newCrosslinks[0].Blockhash) } - - for _, index := range committee { - if cState.Validators()[index].Balance == 1e18 { - t.Errorf("validator with index %d did not have balance changed.", index) - } - } + //TODO(#538) Implement tests on balances of the validators in committee once big.Int is introduced. } func TestIsDynastyTransition(t *testing.T) { diff --git a/shared/BUILD.bazel b/shared/BUILD.bazel index 8837ba86f4..d79c0ec533 100644 --- a/shared/BUILD.bazel +++ b/shared/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "bit.go", "marshal.go", + "math_helper.go", "service_registry.go", "types.go", ], @@ -25,6 +26,7 @@ go_test( srcs = [ "bit_test.go", "marshal_test.go", + "math_helper_test.go", "service_registry_test.go", ], embed = [":go_default_library"], diff --git a/shared/math_helper.go b/shared/math_helper.go new file mode 100644 index 0000000000..a2b4d68529 --- /dev/null +++ b/shared/math_helper.go @@ -0,0 +1,14 @@ +package shared + +// IntegerSquareRoot defines a function that returns the +// largest possible integer root of a number. +func IntegerSquareRoot(n uint64) uint64 { + x := n + y := (x + 1) / 2 + + for y < x { + x = y + y = (x + n/x) / 2 + } + return x +} diff --git a/shared/math_helper_test.go b/shared/math_helper_test.go new file mode 100644 index 0000000000..acb073b63a --- /dev/null +++ b/shared/math_helper_test.go @@ -0,0 +1,42 @@ +package shared + +import ( + "testing" +) + +type numbertTest struct { + number uint64 + root uint64 +} + +func TestIntegerSquareRoot(t *testing.T) { + tt := []numbertTest{ + { + number: 20, + root: 4, + }, + { + number: 200, + root: 14, + }, + { + number: 1987, + root: 44, + }, + { + number: 34989843, + root: 5915, + }, + { + number: 97282, + root: 311, + }, + } + + for _, testVals := range tt { + root := IntegerSquareRoot(testVals.number) + if testVals.root != root { + t.Fatalf("expected root and computed root are not equal %d, %d", testVals.root, root) + } + } +}