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

@ -339,4 +339,4 @@ export function useProposalLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<P
}
export type ProposalQueryHookResult = ReturnType<typeof useProposalQuery>;
export type ProposalLazyQueryHookResult = ReturnType<typeof useProposalLazyQuery>;
export type ProposalQueryResult = Apollo.QueryResult<ProposalQuery, ProposalQueryVariables>;
export type ProposalQueryResult = Apollo.QueryResult<ProposalQuery, ProposalQueryVariables>;

View File

@ -8,6 +8,9 @@ query PreviousEpoch($epochId: ID) {
rewardScore {
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`
@ -22,6 +22,9 @@ export const PreviousEpochDocument = gql`
rewardScore {
rawValidatorScore
}
rankingScore {
performanceScore
}
}
}
}

View File

@ -81,6 +81,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
rewardScore: {
rawValidatorScore: '0.25',
},
rankingScore: {
performanceScore: '0.9998677767864936',
},
},
},
{
@ -89,6 +92,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
rewardScore: {
rawValidatorScore: '0.3',
},
rankingScore: {
performanceScore: '1',
},
},
},
{
@ -97,6 +103,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
rewardScore: {
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 {
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getRawValidatorScore,
getTotalPenalties,
getUnnormalisedVotingPower,
} from '../../shared';
@ -146,13 +146,15 @@ export const ConsensusValidatorsTable = ({
stakedByDelegates,
stakedByOperator,
stakedTotal,
rankingScore: { stakeScore, votingPower, performanceScore },
rankingScore: { stakeScore, votingPower },
pendingStake,
votingPowerRanking,
}) => {
const validatorScore = getRawValidatorScore(previousEpochData, id);
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
validatorScore,
rawValidatorScore,
stakedTotal,
totalStake
);
@ -171,7 +173,7 @@ export const ConsensusValidatorsTable = ({
[ValidatorFields.NORMALISED_VOTING_POWER]:
getNormalisedVotingPower(votingPower),
[ValidatorFields.UNNORMALISED_VOTING_POWER]:
getUnnormalisedVotingPower(validatorScore),
getUnnormalisedVotingPower(rawValidatorScore),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.STAKED_BY_DELEGATES]: formatNumber(
toBigNum(stakedByDelegates, decimals),
@ -191,7 +193,7 @@ export const ConsensusValidatorsTable = ({
totalStake
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
validatorScore,
rawValidatorScore,
performanceScore,
stakedTotal,
totalStake

View File

@ -1,7 +1,10 @@
import { getRawValidatorScore, getTotalPenalties } from '../../shared';
import {
getLastEpochScoreAndPerformance,
getTotalPenalties,
} from '../../shared';
import { stakedTotalPercentage } from './shared';
const mockPreviousEpochData = {
const MOCK_PREVIOUS_EPOCH = {
epoch: {
id: '1',
validatorsConnection: {
@ -12,6 +15,9 @@ const mockPreviousEpochData = {
rewardScore: {
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', () => {
expect(
getTotalPenalties(
getRawValidatorScore(mockPreviousEpochData, '0x123'),
getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123')
.rawValidatorScore,
'0.1',
'5000',
'100000'
@ -40,7 +47,8 @@ describe('totalPenalties', () => {
it('should return the correct penalty based on lower performance score than first test', () => {
expect(
getTotalPenalties(
getRawValidatorScore(mockPreviousEpochData, '0x123'),
getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123')
.rawValidatorScore,
'0.05',
'5000',
'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)', () => {
expect(
getTotalPenalties(
getRawValidatorScore(mockPreviousEpochData, '0x123'),
getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123')
.rawValidatorScore,
'0.1',
'5000',
'5500'

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { BigNumber } from '../../lib/bignumber';
import {
getRawValidatorScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getUnnormalisedVotingPower,
getOverstakingPenalty,
@ -10,7 +10,7 @@ import {
getTotalPenalties,
} from './shared';
describe('getRawValidatorScore', () => {
describe('getLastEpochScoreAndPerformance', () => {
const mockPreviousEpochData = {
epoch: {
id: '123',
@ -22,6 +22,9 @@ describe('getRawValidatorScore', () => {
rewardScore: {
rawValidatorScore: '0.25',
},
rankingScore: {
performanceScore: '0.75',
},
},
},
{
@ -30,6 +33,9 @@ describe('getRawValidatorScore', () => {
rewardScore: {
rawValidatorScore: '0.35',
},
rankingScore: {
performanceScore: '0.85',
},
},
},
],
@ -37,13 +43,19 @@ describe('getRawValidatorScore', () => {
},
};
it('should return the rawValidatorScore for the given validator id', () => {
expect(getRawValidatorScore(mockPreviousEpochData, '0x123')).toEqual(
'0.25'
);
expect(getRawValidatorScore(mockPreviousEpochData, '0x234')).toEqual(
'0.35'
);
it("should return last epoch's performance and raw validator score for the given validator id", () => {
expect(
getLastEpochScoreAndPerformance(mockPreviousEpochData, '0x123')
).toEqual({
rawValidatorScore: '0.25',
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 { BigNumber } from '../../lib/bignumber';
export const getRawValidatorScore = (
export const getLastEpochScoreAndPerformance = (
previousEpochData: PreviousEpochQuery | undefined,
id: string
) => {
return previousEpochData
? removePaginationWrapper(
previousEpochData.epoch?.validatorsConnection?.edges
).find((validator) => validator?.id === id)?.rewardScore
?.rawValidatorScore
: null;
const validator = removePaginationWrapper(
previousEpochData?.epoch?.validatorsConnection?.edges
).find((validator) => validator?.id === id);
return {
rawValidatorScore: validator?.rewardScore?.rawValidatorScore,
performanceScore: validator?.rankingScore?.performanceScore,
};
};
export const getNormalisedVotingPower = (votingPower: string) =>
@ -27,10 +29,12 @@ export const getUnnormalisedVotingPower = (
? formatNumberPercentage(new BigNumber(validatorScore).times(100), 2)
: null;
export const getFormattedPerformanceScore = (performanceScore: string) =>
new BigNumber(performanceScore).dp(2);
export const getFormattedPerformanceScore = (performanceScore?: string) =>
performanceScore
? new BigNumber(performanceScore).dp(2)
: new BigNumber(0).dp(2);
export const getPerformancePenalty = (performanceScore: string) =>
export const getPerformancePenalty = (performanceScore?: string) =>
formatNumberPercentage(
new BigNumber(1)
.minus(getFormattedPerformanceScore(performanceScore))
@ -64,19 +68,22 @@ export const getOverstakingPenalty = (
export const getTotalPenalties = (
rawValidatorScore: string | null | undefined,
performanceScore: string,
performanceScore: string | undefined,
stakedOnNode: string,
totalStake: string
) => {
const calc = rawValidatorScore
? new BigNumber(1).minus(
new BigNumber(performanceScore)
.times(new BigNumber(rawValidatorScore))
.dividedBy(
new BigNumber(stakedOnNode).dividedBy(new BigNumber(totalStake))
)
)
: new BigNumber(0);
const calc =
rawValidatorScore &&
performanceScore &&
new BigNumber(totalStake).isGreaterThan(0)
? new BigNumber(1).minus(
new BigNumber(performanceScore)
.times(new BigNumber(rawValidatorScore))
.dividedBy(
new BigNumber(stakedOnNode).dividedBy(new BigNumber(totalStake))
)
)
: new BigNumber(0);
return formatNumberPercentage(
calc.isPositive() ? calc.times(100) : new BigNumber(0),