fix(governance): add multisig score to the validator page (#5845)

Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
This commit is contained in:
Art 2024-02-27 11:46:48 +01:00 committed by GitHub
parent 4f8d6bd876
commit b81c4bc948
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 124 additions and 45 deletions

View File

@ -20,6 +20,7 @@ describe('getMultisigStatus', () => {
expect(result).toEqual({ expect(result).toEqual({
multisigStatus: MultisigStatus.noNodes, multisigStatus: MultisigStatus.noNodes,
showMultisigStatusError: true, showMultisigStatusError: true,
zeroScoreNodes: [],
}); });
}); });
@ -35,6 +36,7 @@ describe('getMultisigStatus', () => {
expect(result).toEqual({ expect(result).toEqual({
multisigStatus: MultisigStatus.correct, multisigStatus: MultisigStatus.correct,
showMultisigStatusError: false, showMultisigStatusError: false,
zeroScoreNodes: [],
}); });
}); });
@ -50,6 +52,22 @@ describe('getMultisigStatus', () => {
expect(result).toEqual({ expect(result).toEqual({
multisigStatus: MultisigStatus.nodeNeedsRemoving, multisigStatus: MultisigStatus.nodeNeedsRemoving,
showMultisigStatusError: true, showMultisigStatusError: true,
zeroScoreNodes: [
{
id: '1',
rewardScore: {
multisigScore: '0',
},
stakedTotal: '1000',
},
{
id: '2',
rewardScore: {
multisigScore: '0',
},
stakedTotal: '1000',
},
],
}); });
}); });
@ -65,6 +83,15 @@ describe('getMultisigStatus', () => {
expect(result).toEqual({ expect(result).toEqual({
multisigStatus: MultisigStatus.nodeNeedsAdding, multisigStatus: MultisigStatus.nodeNeedsAdding,
showMultisigStatusError: true, showMultisigStatusError: true,
zeroScoreNodes: [
{
id: '1',
rewardScore: {
multisigScore: '0',
},
stakedTotal: '1000',
},
],
}); });
}); });
}); });

View File

@ -1,5 +1,8 @@
import { removePaginationWrapper } from '@vegaprotocol/utils'; import { removePaginationWrapper } from '@vegaprotocol/utils';
import type { PreviousEpochQuery } from '../routes/staking/__generated__/PreviousEpoch'; import type {
PreviousEpochQuery,
ValidatorNodeFragment,
} from '../routes/staking/__generated__/PreviousEpoch';
export enum MultisigStatus { export enum MultisigStatus {
'correct' = 'correct', 'correct' = 'correct',
@ -17,12 +20,15 @@ export const getMultisigStatusInfo = (
previousEpochData?.epoch.validatorsConnection?.edges previousEpochData?.epoch.validatorsConnection?.edges
); );
const hasZero = allNodesInPreviousEpoch.some( const zeroScore = (node: ValidatorNodeFragment) =>
(node) => Number(node?.rewardScore?.multisigScore) === 0 Number(node.rewardScore?.multisigScore) === 0;
); const oneScore = (node: ValidatorNodeFragment) =>
const hasOne = allNodesInPreviousEpoch.some( Number(node.rewardScore?.multisigScore) === 1;
(node) => Number(node?.rewardScore?.multisigScore) === 1
); const hasZero = allNodesInPreviousEpoch.some(zeroScore);
const hasOne = allNodesInPreviousEpoch.some(oneScore);
const zeroScoreNodes = allNodesInPreviousEpoch.filter(zeroScore);
if (hasZero && hasOne) { if (hasZero && hasOne) {
// If any individual node has 0 it means that node is missing from the multisig and needs to be added // If any individual node has 0 it means that node is missing from the multisig and needs to be added
@ -38,5 +44,6 @@ export const getMultisigStatusInfo = (
return { return {
showMultisigStatusError: status !== MultisigStatus.correct, showMultisigStatusError: status !== MultisigStatus.correct,
multisigStatus: status, multisigStatus: status,
zeroScoreNodes,
}; };
}; };

View File

@ -1,27 +1,31 @@
fragment ValidatorNode on Node {
id
stakedTotal
rewardScore {
rawValidatorScore
performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
}
rankingScore {
status
previousStatus
rankingScore
stakeScore
performanceScore
votingPower
}
}
query PreviousEpoch($epochId: ID) { query PreviousEpoch($epochId: ID) {
epoch(id: $epochId) { epoch(id: $epochId) {
id id
validatorsConnection { validatorsConnection {
edges { edges {
node { node {
id ...ValidatorNode
stakedTotal
rewardScore {
rawValidatorScore
performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
}
rankingScore {
status
previousStatus
rankingScore
stakeScore
performanceScore
votingPower
}
} }
} }
} }

View File

@ -3,6 +3,8 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ValidatorNodeFragment = { __typename?: 'Node', id: string, stakedTotal: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string, multisigScore: string, validatorScore: string, normalisedScore: string, validatorStatus: Types.ValidatorStatus } | null, rankingScore: { __typename?: 'RankingScore', status: Types.ValidatorStatus, previousStatus: Types.ValidatorStatus, rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string } };
export type PreviousEpochQueryVariables = Types.Exact<{ export type PreviousEpochQueryVariables = Types.Exact<{
epochId?: Types.InputMaybe<Types.Scalars['ID']>; epochId?: Types.InputMaybe<Types.Scalars['ID']>;
}>; }>;
@ -10,7 +12,28 @@ 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, stakedTotal: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string, multisigScore: string, validatorScore: string, normalisedScore: string, validatorStatus: Types.ValidatorStatus } | null, rankingScore: { __typename?: 'RankingScore', status: Types.ValidatorStatus, previousStatus: Types.ValidatorStatus, rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string } } } | 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, stakedTotal: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string, multisigScore: string, validatorScore: string, normalisedScore: string, validatorStatus: Types.ValidatorStatus } | null, rankingScore: { __typename?: 'RankingScore', status: Types.ValidatorStatus, previousStatus: Types.ValidatorStatus, rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string } } } | null> | null } | null } };
export const ValidatorNodeFragmentDoc = gql`
fragment ValidatorNode on Node {
id
stakedTotal
rewardScore {
rawValidatorScore
performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
}
rankingScore {
status
previousStatus
rankingScore
stakeScore
performanceScore
votingPower
}
}
`;
export const PreviousEpochDocument = gql` export const PreviousEpochDocument = gql`
query PreviousEpoch($epochId: ID) { query PreviousEpoch($epochId: ID) {
epoch(id: $epochId) { epoch(id: $epochId) {
@ -18,30 +41,13 @@ export const PreviousEpochDocument = gql`
validatorsConnection { validatorsConnection {
edges { edges {
node { node {
id ...ValidatorNode
stakedTotal
rewardScore {
rawValidatorScore
performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
}
rankingScore {
status
previousStatus
rankingScore
stakeScore
performanceScore
votingPower
}
} }
} }
} }
} }
} }
`; ${ValidatorNodeFragmentDoc}`;
/** /**
* __usePreviousEpochQuery__ * __usePreviousEpochQuery__

View File

@ -37,6 +37,7 @@ import {
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { StakingNodeFieldsFragment } from '../__generated__/Staking'; import type { StakingNodeFieldsFragment } from '../__generated__/Staking';
import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch'; import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
const statuses = { const statuses = {
[Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz', [Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz',
@ -104,6 +105,10 @@ export const ValidatorTable = ({
}; };
}, [node, previousEpochData?.epoch.validatorsConnection?.edges]); }, [node, previousEpochData?.epoch.validatorsConnection?.edges]);
const multisigStatus = previousEpochData
? getMultisigStatusInfo(previousEpochData)
: undefined;
return ( return (
<> <>
<p className="mb-12"> <p className="mb-12">
@ -281,6 +286,34 @@ export const ValidatorTable = ({
</span> </span>
</Tooltip> </Tooltip>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow>
<span className="uppercase">{t('multisigPenalty')}</span>
<span
data-testid="multisig-penalty"
className="flex gap-2 items-baseline"
>
{multisigStatus?.zeroScoreNodes.find(
(n) => n.id === node.id
) ? (
<Tooltip
description={t('multisigPenaltyThisNodeIndicator')}
>
<span className="inline-block w-2 h-2 rounded-full bg-vega-red-500"></span>
</Tooltip>
) : null}
<Tooltip description={t('multisigPenaltyDescription')}>
<span>
{formatNumberPercentage(
BigNumber(
multisigStatus?.showMultisigStatusError ? 100 : 0
),
2
)}
</span>
</Tooltip>
</span>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}> <KeyValueTableRow noBorder={true}>
<span> <span>
<strong>{t('TOTAL PENALTIES')}</strong> <strong>{t('TOTAL PENALTIES')}</strong>

View File

@ -391,6 +391,8 @@
"multisigContractIncorrect": "was incorrectly configured as at the end of the last epoch so rewards were penalised. Validator and delegator rewards will continue to be penalised until this is resolved.", "multisigContractIncorrect": "was incorrectly configured as at the end of the last epoch so rewards were penalised. Validator and delegator rewards will continue to be penalised until this is resolved.",
"multisigContractLink": "Ethereum Multisig Contract", "multisigContractLink": "Ethereum Multisig Contract",
"multisigPenalty": "Multisig penalty", "multisigPenalty": "Multisig penalty",
"multisigPenaltyThisNodeIndicator": "The multisig score for this node is equal to zero.",
"multisigPenaltyDescription": "The multisig score is used in the calculation of rewards. For each validator that gets a multisig score of zero, no staking rewards are paid to that consensus validators and their nominators until the epoch following the one in which the configuration issue is resolved.",
"myPendingStake": "My pending stake", "myPendingStake": "My pending stake",
"myStake": "My stake", "myStake": "My stake",
"n/a": "N/A", "n/a": "N/A",