diff --git a/apps/governance/src/lib/get-multisig-status-info.spec.ts b/apps/governance/src/lib/get-multisig-status-info.spec.ts index d92d5a7d5..8e3112e4b 100644 --- a/apps/governance/src/lib/get-multisig-status-info.spec.ts +++ b/apps/governance/src/lib/get-multisig-status-info.spec.ts @@ -20,6 +20,7 @@ describe('getMultisigStatus', () => { expect(result).toEqual({ multisigStatus: MultisigStatus.noNodes, showMultisigStatusError: true, + zeroScoreNodes: [], }); }); @@ -35,6 +36,7 @@ describe('getMultisigStatus', () => { expect(result).toEqual({ multisigStatus: MultisigStatus.correct, showMultisigStatusError: false, + zeroScoreNodes: [], }); }); @@ -50,6 +52,22 @@ describe('getMultisigStatus', () => { expect(result).toEqual({ multisigStatus: MultisigStatus.nodeNeedsRemoving, 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({ multisigStatus: MultisigStatus.nodeNeedsAdding, showMultisigStatusError: true, + zeroScoreNodes: [ + { + id: '1', + rewardScore: { + multisigScore: '0', + }, + stakedTotal: '1000', + }, + ], }); }); }); diff --git a/apps/governance/src/lib/get-multisig-status-info.ts b/apps/governance/src/lib/get-multisig-status-info.ts index 53352c6bf..608ade107 100644 --- a/apps/governance/src/lib/get-multisig-status-info.ts +++ b/apps/governance/src/lib/get-multisig-status-info.ts @@ -1,5 +1,8 @@ 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 { 'correct' = 'correct', @@ -17,12 +20,15 @@ export const getMultisigStatusInfo = ( previousEpochData?.epoch.validatorsConnection?.edges ); - const hasZero = allNodesInPreviousEpoch.some( - (node) => Number(node?.rewardScore?.multisigScore) === 0 - ); - const hasOne = allNodesInPreviousEpoch.some( - (node) => Number(node?.rewardScore?.multisigScore) === 1 - ); + const zeroScore = (node: ValidatorNodeFragment) => + Number(node.rewardScore?.multisigScore) === 0; + const oneScore = (node: ValidatorNodeFragment) => + Number(node.rewardScore?.multisigScore) === 1; + + const hasZero = allNodesInPreviousEpoch.some(zeroScore); + const hasOne = allNodesInPreviousEpoch.some(oneScore); + + const zeroScoreNodes = allNodesInPreviousEpoch.filter(zeroScore); if (hasZero && hasOne) { // 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 { showMultisigStatusError: status !== MultisigStatus.correct, multisigStatus: status, + zeroScoreNodes, }; }; diff --git a/apps/governance/src/routes/staking/PreviousEpoch.graphql b/apps/governance/src/routes/staking/PreviousEpoch.graphql index 1b6c42e03..d064e3d8e 100644 --- a/apps/governance/src/routes/staking/PreviousEpoch.graphql +++ b/apps/governance/src/routes/staking/PreviousEpoch.graphql @@ -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) { epoch(id: $epochId) { id validatorsConnection { edges { node { - id - stakedTotal - rewardScore { - rawValidatorScore - performanceScore - multisigScore - validatorScore - normalisedScore - validatorStatus - } - rankingScore { - status - previousStatus - rankingScore - stakeScore - performanceScore - votingPower - } + ...ValidatorNode } } } diff --git a/apps/governance/src/routes/staking/__generated__/PreviousEpoch.ts b/apps/governance/src/routes/staking/__generated__/PreviousEpoch.ts index 002e75b5c..4246b5248 100644 --- a/apps/governance/src/routes/staking/__generated__/PreviousEpoch.ts +++ b/apps/governance/src/routes/staking/__generated__/PreviousEpoch.ts @@ -3,6 +3,8 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; 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<{ epochId?: Types.InputMaybe; }>; @@ -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 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` query PreviousEpoch($epochId: ID) { epoch(id: $epochId) { @@ -18,30 +41,13 @@ export const PreviousEpochDocument = gql` validatorsConnection { edges { node { - id - stakedTotal - rewardScore { - rawValidatorScore - performanceScore - multisigScore - validatorScore - normalisedScore - validatorStatus - } - rankingScore { - status - previousStatus - rankingScore - stakeScore - performanceScore - votingPower - } + ...ValidatorNode } } } } } - `; + ${ValidatorNodeFragmentDoc}`; /** * __usePreviousEpochQuery__ diff --git a/apps/governance/src/routes/staking/node/validator-table.tsx b/apps/governance/src/routes/staking/node/validator-table.tsx index 8e302a019..7c0c79a19 100644 --- a/apps/governance/src/routes/staking/node/validator-table.tsx +++ b/apps/governance/src/routes/staking/node/validator-table.tsx @@ -37,6 +37,7 @@ import { import type { ReactNode } from 'react'; import type { StakingNodeFieldsFragment } from '../__generated__/Staking'; import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch'; +import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info'; const statuses = { [Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz', @@ -104,6 +105,10 @@ export const ValidatorTable = ({ }; }, [node, previousEpochData?.epoch.validatorsConnection?.edges]); + const multisigStatus = previousEpochData + ? getMultisigStatusInfo(previousEpochData) + : undefined; + return ( <>

@@ -281,6 +286,34 @@ export const ValidatorTable = ({ + + {t('multisigPenalty')} + + + {multisigStatus?.zeroScoreNodes.find( + (n) => n.id === node.id + ) ? ( + + + + ) : null} + + + {formatNumberPercentage( + BigNumber( + multisigStatus?.showMultisigStatusError ? 100 : 0 + ), + 2 + )} + + + + {t('TOTAL PENALTIES')} diff --git a/libs/i18n/src/locales/en/governance.json b/libs/i18n/src/locales/en/governance.json index cd85511cc..e540cefaf 100644 --- a/libs/i18n/src/locales/en/governance.json +++ b/libs/i18n/src/locales/en/governance.json @@ -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.", "multisigContractLink": "Ethereum Multisig Contract", "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", "myStake": "My stake", "n/a": "N/A",