feat(2356): use validator performance score from previous epoch (#2365)

* feat(1913): validator table column heading mouseovers

* feat(2089): stake needed for promotion tooltip

* feat(2089): normalised voting power tooltip

* feat(2089): total stake tooltip

* feat(2089): total stake tooltip for standby-pending-validators-table

* feat(2089): total penalties tooltip

* feat(2089): tooltip colour tweakage

* feat(2089): unit tests for the shared validator data functions

* feat(2089): removed unused import from tooltip.tsx

* Update apps/token/src/routes/staking/home/validator-tables/standby-pending-validators-table.tsx

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>

* feat(2089): tweaks from PR comments

* feat(2356): display previous epoch validator performance score instead of current epoch

* feat(2356): added condition to ensure no divide by zero errors

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
This commit is contained in:
Sam Keen 2022-12-15 10:06:08 +00:00 committed by GitHub
parent e653ad328f
commit 05b07c2bfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 60 deletions

View File

@ -8,6 +8,9 @@ query PreviousEpoch($epochId: ID) {
rewardScore { rewardScore {
rawValidatorScore rawValidatorScore
} }
rankingScore {
performanceScore
}
} }
} }
} }

View File

@ -8,7 +8,7 @@ export type PreviousEpochQueryVariables = Types.Exact<{
}>; }>;
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string } | null } } | null> | null } | null } }; export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string } | null, rankingScore: { __typename?: 'RankingScore', performanceScore: string } } } | null> | null } | null } };
export const PreviousEpochDocument = gql` export const PreviousEpochDocument = gql`
@ -22,6 +22,9 @@ export const PreviousEpochDocument = gql`
rewardScore { rewardScore {
rawValidatorScore rawValidatorScore
} }
rankingScore {
performanceScore
}
} }
} }
} }

View File

@ -81,6 +81,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
rewardScore: { rewardScore: {
rawValidatorScore: '0.25', rawValidatorScore: '0.25',
}, },
rankingScore: {
performanceScore: '0.9998677767864936',
},
}, },
}, },
{ {
@ -89,6 +92,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
rewardScore: { rewardScore: {
rawValidatorScore: '0.3', rawValidatorScore: '0.3',
}, },
rankingScore: {
performanceScore: '1',
},
}, },
}, },
{ {
@ -97,6 +103,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
rewardScore: { rewardScore: {
rawValidatorScore: '0.35', rawValidatorScore: '0.35',
}, },
rankingScore: {
performanceScore: '0.999629748500531',
},
}, },
}, },
], ],

View File

@ -7,11 +7,11 @@ import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber'; import { BigNumber } from '../../../../lib/bignumber';
import { import {
getFormattedPerformanceScore, getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower, getNormalisedVotingPower,
getOverstakedAmount, getOverstakedAmount,
getOverstakingPenalty, getOverstakingPenalty,
getPerformancePenalty, getPerformancePenalty,
getRawValidatorScore,
getTotalPenalties, getTotalPenalties,
getUnnormalisedVotingPower, getUnnormalisedVotingPower,
} from '../../shared'; } from '../../shared';
@ -146,13 +146,15 @@ export const ConsensusValidatorsTable = ({
stakedByDelegates, stakedByDelegates,
stakedByOperator, stakedByOperator,
stakedTotal, stakedTotal,
rankingScore: { stakeScore, votingPower, performanceScore }, rankingScore: { stakeScore, votingPower },
pendingStake, pendingStake,
votingPowerRanking, votingPowerRanking,
}) => { }) => {
const validatorScore = getRawValidatorScore(previousEpochData, id); const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount( const overstakedAmount = getOverstakedAmount(
validatorScore, rawValidatorScore,
stakedTotal, stakedTotal,
totalStake totalStake
); );
@ -171,7 +173,7 @@ export const ConsensusValidatorsTable = ({
[ValidatorFields.NORMALISED_VOTING_POWER]: [ValidatorFields.NORMALISED_VOTING_POWER]:
getNormalisedVotingPower(votingPower), getNormalisedVotingPower(votingPower),
[ValidatorFields.UNNORMALISED_VOTING_POWER]: [ValidatorFields.UNNORMALISED_VOTING_POWER]:
getUnnormalisedVotingPower(validatorScore), getUnnormalisedVotingPower(rawValidatorScore),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore), [ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.STAKED_BY_DELEGATES]: formatNumber( [ValidatorFields.STAKED_BY_DELEGATES]: formatNumber(
toBigNum(stakedByDelegates, decimals), toBigNum(stakedByDelegates, decimals),
@ -191,7 +193,7 @@ export const ConsensusValidatorsTable = ({
totalStake totalStake
), ),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties( [ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
validatorScore, rawValidatorScore,
performanceScore, performanceScore,
stakedTotal, stakedTotal,
totalStake totalStake

View File

@ -1,7 +1,10 @@
import { getRawValidatorScore, getTotalPenalties } from '../../shared'; import {
getLastEpochScoreAndPerformance,
getTotalPenalties,
} from '../../shared';
import { stakedTotalPercentage } from './shared'; import { stakedTotalPercentage } from './shared';
const mockPreviousEpochData = { const MOCK_PREVIOUS_EPOCH = {
epoch: { epoch: {
id: '1', id: '1',
validatorsConnection: { validatorsConnection: {
@ -12,6 +15,9 @@ const mockPreviousEpochData = {
rewardScore: { rewardScore: {
rawValidatorScore: '0.25', rawValidatorScore: '0.25',
}, },
rankingScore: {
performanceScore: '0.75',
},
}, },
}, },
], ],
@ -29,7 +35,8 @@ describe('totalPenalties', () => {
it('should return the correct penalty based on arbitrary values, test 1', () => { it('should return the correct penalty based on arbitrary values, test 1', () => {
expect( expect(
getTotalPenalties( getTotalPenalties(
getRawValidatorScore(mockPreviousEpochData, '0x123'), getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123')
.rawValidatorScore,
'0.1', '0.1',
'5000', '5000',
'100000' '100000'
@ -40,7 +47,8 @@ describe('totalPenalties', () => {
it('should return the correct penalty based on lower performance score than first test', () => { it('should return the correct penalty based on lower performance score than first test', () => {
expect( expect(
getTotalPenalties( getTotalPenalties(
getRawValidatorScore(mockPreviousEpochData, '0x123'), getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123')
.rawValidatorScore,
'0.05', '0.05',
'5000', '5000',
'100000' '100000'
@ -51,7 +59,8 @@ describe('totalPenalties', () => {
it('should return the correct penalty based on higher amount of stake than other tests (great penalty due to anti-whaling)', () => { it('should return the correct penalty based on higher amount of stake than other tests (great penalty due to anti-whaling)', () => {
expect( expect(
getTotalPenalties( getTotalPenalties(
getRawValidatorScore(mockPreviousEpochData, '0x123'), getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123')
.rawValidatorScore,
'0.1', '0.1',
'5000', '5000',
'5500' '5500'

View File

@ -6,10 +6,10 @@ import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber'; import { BigNumber } from '../../../../lib/bignumber';
import { import {
getFormattedPerformanceScore, getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getOverstakedAmount, getOverstakedAmount,
getOverstakingPenalty, getOverstakingPenalty,
getPerformancePenalty, getPerformancePenalty,
getRawValidatorScore,
getTotalPenalties, getTotalPenalties,
} from '../../shared'; } from '../../shared';
import { import {
@ -58,19 +58,21 @@ export const StandbyPendingValidatorsTable = ({
stakedByDelegates, stakedByDelegates,
stakedByOperator, stakedByOperator,
stakedTotal, stakedTotal,
rankingScore: { stakeScore, performanceScore }, rankingScore: { stakeScore },
pendingStake, pendingStake,
}) => { }) => {
const validatorScore = getRawValidatorScore(previousEpochData, id); const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount( const overstakedAmount = getOverstakedAmount(
validatorScore, rawValidatorScore,
stakedTotal, stakedTotal,
totalStake totalStake
); );
let individualStakeNeededForPromotion, let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription; individualStakeNeededForPromotionDescription;
if (stakeNeededForPromotion) { if (stakeNeededForPromotion && performanceScore) {
const stakedTotalBigNum = new BigNumber(stakedTotal); const stakedTotalBigNum = new BigNumber(stakedTotal);
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion); const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
const performanceScoreBigNum = new BigNumber(performanceScore); const performanceScoreBigNum = new BigNumber(performanceScore);
@ -131,7 +133,7 @@ export const StandbyPendingValidatorsTable = ({
totalStake totalStake
), ),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties( [ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
getRawValidatorScore(previousEpochData, id), rawValidatorScore,
performanceScore, performanceScore,
stakedTotal, stakedTotal,
totalStake totalStake

View File

@ -17,11 +17,11 @@ import * as Schema from '@vegaprotocol/types';
import { SubHeading } from '../../../components/heading'; import { SubHeading } from '../../../components/heading';
import { import {
getFormattedPerformanceScore, getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower, getNormalisedVotingPower,
getOverstakedAmount, getOverstakedAmount,
getOverstakingPenalty, getOverstakingPenalty,
getPerformancePenalty, getPerformancePenalty,
getRawValidatorScore,
getTotalPenalties, getTotalPenalties,
getUnnormalisedVotingPower, getUnnormalisedVotingPower,
} from '../shared'; } from '../shared';
@ -73,10 +73,11 @@ export const ValidatorTable = ({
const stakedOnNode = toBigNum(node.stakedTotal, decimals); const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const validatorScore = getRawValidatorScore(previousEpochData, node.id); const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, node.id);
const overstakedAmount = getOverstakedAmount( const overstakedAmount = getOverstakedAmount(
validatorScore, rawValidatorScore,
stakedTotal, stakedTotal,
node.stakedTotal node.stakedTotal
); );
@ -87,8 +88,8 @@ export const ValidatorTable = ({
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%'; : stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
const totalPenaltiesAmount = getTotalPenalties( const totalPenaltiesAmount = getTotalPenalties(
validatorScore, rawValidatorScore,
node.rankingScore.performanceScore, performanceScore,
stakedOnNode.toString(), stakedOnNode.toString(),
total.toString() total.toString()
); );
@ -219,16 +220,12 @@ export const ValidatorTable = ({
<KeyValueTableRow> <KeyValueTableRow>
<span>{t('PERFORMANCE SCORE')}</span> <span>{t('PERFORMANCE SCORE')}</span>
<span> <span>
{getFormattedPerformanceScore( {getFormattedPerformanceScore(performanceScore).toString()}
node.rankingScore.performanceScore
).toString()}
</span> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<span>{t('PERFORMANCE PENALITY')}</span> <span>{t('PERFORMANCE PENALITY')}</span>
<span> <span>{getPerformancePenalty(performanceScore)}</span>
{getPerformancePenalty(node.rankingScore.performanceScore)}
</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow noBorder={true}> <KeyValueTableRow noBorder={true}>
<span> <span>
@ -246,7 +243,7 @@ export const ValidatorTable = ({
<KeyValueTable data-testid="validator-table-voting-power"> <KeyValueTable data-testid="validator-table-voting-power">
<KeyValueTableRow> <KeyValueTableRow>
<span>{t('UNNORMALISED VOTING POWER')}</span> <span>{t('UNNORMALISED VOTING POWER')}</span>
<span>{getUnnormalisedVotingPower(validatorScore)}</span> <span>{getUnnormalisedVotingPower(rawValidatorScore)}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow noBorder={true}> <KeyValueTableRow noBorder={true}>
<span> <span>

View File

@ -1,6 +1,6 @@
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import { import {
getRawValidatorScore, getLastEpochScoreAndPerformance,
getNormalisedVotingPower, getNormalisedVotingPower,
getUnnormalisedVotingPower, getUnnormalisedVotingPower,
getOverstakingPenalty, getOverstakingPenalty,
@ -10,7 +10,7 @@ import {
getTotalPenalties, getTotalPenalties,
} from './shared'; } from './shared';
describe('getRawValidatorScore', () => { describe('getLastEpochScoreAndPerformance', () => {
const mockPreviousEpochData = { const mockPreviousEpochData = {
epoch: { epoch: {
id: '123', id: '123',
@ -22,6 +22,9 @@ describe('getRawValidatorScore', () => {
rewardScore: { rewardScore: {
rawValidatorScore: '0.25', rawValidatorScore: '0.25',
}, },
rankingScore: {
performanceScore: '0.75',
},
}, },
}, },
{ {
@ -30,6 +33,9 @@ describe('getRawValidatorScore', () => {
rewardScore: { rewardScore: {
rawValidatorScore: '0.35', rawValidatorScore: '0.35',
}, },
rankingScore: {
performanceScore: '0.85',
},
}, },
}, },
], ],
@ -37,13 +43,19 @@ describe('getRawValidatorScore', () => {
}, },
}; };
it('should return the rawValidatorScore for the given validator id', () => { it("should return last epoch's performance and raw validator score for the given validator id", () => {
expect(getRawValidatorScore(mockPreviousEpochData, '0x123')).toEqual( expect(
'0.25' getLastEpochScoreAndPerformance(mockPreviousEpochData, '0x123')
); ).toEqual({
expect(getRawValidatorScore(mockPreviousEpochData, '0x234')).toEqual( rawValidatorScore: '0.25',
'0.35' performanceScore: '0.75',
); });
expect(
getLastEpochScoreAndPerformance(mockPreviousEpochData, '0x234')
).toEqual({
rawValidatorScore: '0.35',
performanceScore: '0.85',
});
}); });
}); });

View File

@ -5,16 +5,18 @@ import {
import type { PreviousEpochQuery } from './__generated___/PreviousEpoch'; import type { PreviousEpochQuery } from './__generated___/PreviousEpoch';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
export const getRawValidatorScore = ( export const getLastEpochScoreAndPerformance = (
previousEpochData: PreviousEpochQuery | undefined, previousEpochData: PreviousEpochQuery | undefined,
id: string id: string
) => { ) => {
return previousEpochData const validator = removePaginationWrapper(
? removePaginationWrapper( previousEpochData?.epoch?.validatorsConnection?.edges
previousEpochData.epoch?.validatorsConnection?.edges ).find((validator) => validator?.id === id);
).find((validator) => validator?.id === id)?.rewardScore
?.rawValidatorScore return {
: null; rawValidatorScore: validator?.rewardScore?.rawValidatorScore,
performanceScore: validator?.rankingScore?.performanceScore,
};
}; };
export const getNormalisedVotingPower = (votingPower: string) => export const getNormalisedVotingPower = (votingPower: string) =>
@ -27,10 +29,12 @@ export const getUnnormalisedVotingPower = (
? formatNumberPercentage(new BigNumber(validatorScore).times(100), 2) ? formatNumberPercentage(new BigNumber(validatorScore).times(100), 2)
: null; : null;
export const getFormattedPerformanceScore = (performanceScore: string) => export const getFormattedPerformanceScore = (performanceScore?: string) =>
new BigNumber(performanceScore).dp(2); performanceScore
? new BigNumber(performanceScore).dp(2)
: new BigNumber(0).dp(2);
export const getPerformancePenalty = (performanceScore: string) => export const getPerformancePenalty = (performanceScore?: string) =>
formatNumberPercentage( formatNumberPercentage(
new BigNumber(1) new BigNumber(1)
.minus(getFormattedPerformanceScore(performanceScore)) .minus(getFormattedPerformanceScore(performanceScore))
@ -64,11 +68,14 @@ export const getOverstakingPenalty = (
export const getTotalPenalties = ( export const getTotalPenalties = (
rawValidatorScore: string | null | undefined, rawValidatorScore: string | null | undefined,
performanceScore: string, performanceScore: string | undefined,
stakedOnNode: string, stakedOnNode: string,
totalStake: string totalStake: string
) => { ) => {
const calc = rawValidatorScore const calc =
rawValidatorScore &&
performanceScore &&
new BigNumber(totalStake).isGreaterThan(0)
? new BigNumber(1).minus( ? new BigNumber(1).minus(
new BigNumber(performanceScore) new BigNumber(performanceScore)
.times(new BigNumber(rawValidatorScore)) .times(new BigNumber(rawValidatorScore))