fix(governance): add multisig score to the validator page (#5845)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
This commit is contained in:
parent
4f8d6bd876
commit
b81c4bc948
@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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__
|
||||||
|
@ -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>
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user