Skip to content

Commit

Permalink
feat(refactor): Staking metrics to bootstrap and API (#1905)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Bulat authored Jan 27, 2024
1 parent 41e2ab1 commit f6c0b93
Show file tree
Hide file tree
Showing 22 changed files with 247 additions and 157 deletions.
13 changes: 13 additions & 0 deletions src/contexts/Api/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
APIContextInterface,
APINetworkMetrics,
APIPoolsConfig,
APIStakingMetrics,
} from 'contexts/Api/types';

export const defaultChainState: APIChainState = {
Expand Down Expand Up @@ -61,6 +62,17 @@ export const defaultPoolsConfig: APIPoolsConfig = {
globalMaxCommission: 0,
};

export const defaultStakingMetrics: APIStakingMetrics = {
totalNominators: new BigNumber(0),
totalValidators: new BigNumber(0),
lastReward: new BigNumber(0),
lastTotalStake: new BigNumber(0),
validatorCount: new BigNumber(0),
maxValidatorsCount: new BigNumber(0),
minNominatorBond: new BigNumber(0),
totalStaked: new BigNumber(0),
};

export const defaultApiContext: APIContextInterface = {
api: null,
chainState: defaultChainState,
Expand All @@ -74,5 +86,6 @@ export const defaultApiContext: APIContextInterface = {
networkMetrics: defaultNetworkMetrics,
activeEra: defaultActiveEra,
poolsConfig: defaultPoolsConfig,
stakingMetrics: defaultStakingMetrics,
isPagedRewardsActive: (e) => false,
};
45 changes: 44 additions & 1 deletion src/contexts/Api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
APINetworkMetrics,
APIPoolsConfig,
APIProviderProps,
APIStakingMetrics,
} from './types';
import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks';
import {
Expand All @@ -26,6 +27,7 @@ import {
defaultChainState,
defaultPoolsConfig,
defaultNetworkMetrics,
defaultStakingMetrics,
} from './defaults';
import { APIController } from 'static/APIController';
import { isCustomEvent } from 'static/utils';
Expand Down Expand Up @@ -108,6 +110,12 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
useState<APIPoolsConfig>(defaultPoolsConfig);
const poolsConfigRef = useRef(poolsConfig);

// Store staking metrics in state.
const [stakingMetrics, setStakingMetrics] = useState<APIStakingMetrics>(
defaultStakingMetrics
);
const stakingMetricsRef = useRef(stakingMetrics);

// Fetch chain state. Called once `provider` has been initialised.
const onApiReady = async () => {
const { api } = APIController;
Expand Down Expand Up @@ -139,6 +147,7 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
networkMetrics: newNetworkMetrics,
activeEra: newActiveEra,
poolsConfig: newPoolsConfig,
stakingMetrics: newStakingMetrics,
} = await APIController.bootstrapNetworkConfig();

// Populate all config state.
Expand All @@ -151,14 +160,15 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
activeEraRef
);
setStateWithRef(newPoolsConfig, setPoolsConfig, poolsConfigRef);
setStateWithRef(newStakingMetrics, setStakingMetrics, stakingMetricsRef);

// API is now ready to be used.
setApiStatus('ready');

// Initialise subscriptions.
APIController.subscribeNetworkMetrics();
APIController.subscribeActiveEra();
APIController.subscribePoolsConfig();
APIController.subscribeActiveEra();
};

const onApiDisconnected = (err?: string) => {
Expand Down Expand Up @@ -274,6 +284,27 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
}
};

// Handle new staking metrics updates.
const handleStakingMetricsUpdate = (e: Event): void => {
if (isCustomEvent(e)) {
const { stakingMetrics: newStakingMetrics } = e.detail;
// Only update if values have changed.
if (
JSON.stringify(newStakingMetrics) !==
JSON.stringify(stakingMetricsRef.current)
) {
setStateWithRef(
{
...stakingMetricsRef.current,
...newStakingMetrics,
},
setStakingMetrics,
stakingMetricsRef
);
}
}
};

// Given an era, determine whether paged rewards are active.
const isPagedRewardsActive = (era: BigNumber): boolean => {
const networkStartEra = PagedRewardsStartEra[network];
Expand Down Expand Up @@ -321,6 +352,11 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
);
setStateWithRef(defaultActiveEra, setActiveEra, activeEraRef);
setStateWithRef(defaultPoolsConfig, setPoolsConfig, poolsConfigRef);
setStateWithRef(
defaultStakingMetrics,
setStakingMetrics,
stakingMetricsRef
);
}
// Reconnect API instance.
APIController.initialize(network, isLightClient ? 'sc' : 'ws', rpcEndpoint);
Expand Down Expand Up @@ -349,6 +385,12 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {

useEventListener('new-pools-config', handlePoolsConfigUpdate, documentRef);

useEventListener(
'new-staking-metrics',
handleStakingMetricsUpdate,
documentRef
);

return (
<APIContext.Provider
value={{
Expand All @@ -364,6 +406,7 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
networkMetrics,
activeEra,
poolsConfig,
stakingMetrics,
isPagedRewardsActive,
}}
>
Expand Down
12 changes: 12 additions & 0 deletions src/contexts/Api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ export interface APIPoolsConfig {
globalMaxCommission: number;
}

export interface APIStakingMetrics {
totalNominators: BigNumber;
totalValidators: BigNumber;
lastReward: BigNumber;
lastTotalStake: BigNumber;
validatorCount: BigNumber;
maxValidatorsCount: BigNumber;
minNominatorBond: BigNumber;
totalStaked: BigNumber;
}

export interface APIContextInterface {
api: ApiPromise | null;
chainState: APIChainState;
Expand All @@ -72,5 +83,6 @@ export interface APIContextInterface {
networkMetrics: APINetworkMetrics;
activeEra: APIActiveEra;
poolsConfig: APIPoolsConfig;
stakingMetrics: APIStakingMetrics;
isPagedRewardsActive: (era: BigNumber) => boolean;
}
13 changes: 0 additions & 13 deletions src/contexts/Staking/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,9 @@ import type {
EraStakers,
NominationStatuses,
StakingContextInterface,
StakingMetrics,
StakingTargets,
} from 'contexts/Staking/types';

export const defaultStakingMetrics: StakingMetrics = {
totalNominators: new BigNumber(0),
totalValidators: new BigNumber(0),
lastReward: new BigNumber(0),
lastTotalStake: new BigNumber(0),
validatorCount: new BigNumber(0),
maxValidatorsCount: new BigNumber(0),
minNominatorBond: new BigNumber(0),
totalStaked: new BigNumber(0),
};

export const defaultEraStakers: EraStakers = {
activeAccountOwnStake: [],
activeValidators: 0,
Expand Down Expand Up @@ -51,7 +39,6 @@ export const defaultStakingContext: StakingContextInterface = {
isNominating: () => false,
inSetup: () => true,
getLowestRewardFromStaker: (address) => defaultLowestReward,
staking: defaultStakingMetrics,
eraStakers: defaultEraStakers,
targets: defaultTargets,
erasStakersSyncing: true,
Expand Down
67 changes: 0 additions & 67 deletions src/contexts/Staking/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import type { VoidFn } from '@polkadot/api/types';
import {
greaterThanZero,
isNotZero,
localStorageOrDefault,
rmCommas,
setStateWithRef,
Expand All @@ -19,7 +17,6 @@ import type {
Exposure,
ExposureOther,
StakingContextInterface,
StakingMetrics,
StakingTargets,
} from 'contexts/Staking/types';
import type { AnyApi, MaybeAddress } from 'types';
Expand All @@ -34,7 +31,6 @@ import { useBonded } from '../Bonded';
import {
defaultEraStakers,
defaultStakingContext,
defaultStakingMetrics,
defaultTargets,
} from './defaults';
import {
Expand Down Expand Up @@ -63,14 +59,6 @@ export const StakingProvider = ({ children }: { children: ReactNode }) => {
useBonded();
const { maxExposurePageSize } = consts;

// Store staking metrics in state.
const [stakingMetrics, setStakingMetrics] = useState<StakingMetrics>(
defaultStakingMetrics
);

// Store unsub object fro staking metrics.
const unsub = useRef<VoidFn | null>(null);

// Store eras stakers in state.
const [eraStakers, setEraStakers] = useState<EraStakers>(defaultEraStakers);
const eraStakersRef = useRef(eraStakers);
Expand All @@ -88,14 +76,6 @@ export const StakingProvider = ({ children }: { children: ReactNode }) => {
) as StakingTargets
);

// Handle metrics unsubscribe.
const unsubscribeMetrics = () => {
if (unsub.current !== null) {
unsub.current();
unsub.current = null;
}
};

worker.onmessage = (message: MessageEvent) => {
if (message) {
const { data }: { data: ProcessExposuresResponse } = message;
Expand Down Expand Up @@ -138,40 +118,6 @@ export const StakingProvider = ({ children }: { children: ReactNode }) => {
}
};

// Multi subscription to staking metrics.
const subscribeToStakingkMetrics = async () => {
if (api !== null && isReady && isNotZero(activeEra.index)) {
const previousEra = activeEra.index.minus(1);

const u = await api.queryMulti<AnyApi>(
[
api.query.staking.counterForNominators,
api.query.staking.counterForValidators,
api.query.staking.maxValidatorsCount,
api.query.staking.validatorCount,
[api.query.staking.erasValidatorReward, previousEra.toString()],
[api.query.staking.erasTotalStake, previousEra.toString()],
api.query.staking.minNominatorBond,
[api.query.staking.erasTotalStake, activeEra.index.toString()],
],
(q) => {
setStakingMetrics({
totalNominators: new BigNumber(q[0].toString()),
totalValidators: new BigNumber(q[1].toString()),
maxValidatorsCount: new BigNumber(q[2].toString()),
validatorCount: new BigNumber(q[3].toString()),
lastReward: new BigNumber(q[4].toString()),
lastTotalStake: new BigNumber(q[5].toString()),
minNominatorBond: new BigNumber(q[6].toString()),
totalStaked: new BigNumber(q[7].toString()),
});
}
);

unsub.current = u;
}
};

// Fetches erasStakers exposures for an era, and saves to `localStorage`.
const fetchEraStakers = async (era: string) => {
if (!isReady || activeEra.index.isZero() || !api) {
Expand Down Expand Up @@ -394,21 +340,9 @@ export const StakingProvider = ({ children }: { children: ReactNode }) => {
useEffectIgnoreInitial(() => {
if (apiStatus === 'connecting') {
setStateWithRef(defaultEraStakers, setEraStakers, eraStakersRef);
setStakingMetrics(stakingMetrics);
}
}, [apiStatus]);

// Handle staking metrics subscription
useEffectIgnoreInitial(() => {
if (isReady) {
unsubscribeMetrics();
subscribeToStakingkMetrics();
}
return () => {
unsubscribeMetrics();
};
}, [isReady, activeEra, activeAccount]);

// handle syncing with eraStakers
useEffectIgnoreInitial(() => {
if (isReady) {
Expand Down Expand Up @@ -442,7 +376,6 @@ export const StakingProvider = ({ children }: { children: ReactNode }) => {
isNominating,
inSetup,
getLowestRewardFromStaker,
staking: stakingMetrics,
eraStakers: eraStakersRef.current,
erasStakersSyncing: erasStakersSyncingRef.current,
targets,
Expand Down
12 changes: 0 additions & 12 deletions src/contexts/Staking/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@ import type BigNumber from 'bignumber.js';
import type { NominationStatus } from 'library/ValidatorList/ValidatorItem/types';
import type { MaybeAddress } from 'types';

export interface StakingMetrics {
totalNominators: BigNumber;
totalValidators: BigNumber;
lastReward: BigNumber;
lastTotalStake: BigNumber;
validatorCount: BigNumber;
maxValidatorsCount: BigNumber;
minNominatorBond: BigNumber;
totalStaked: BigNumber;
}

export interface ActiveAccountOwnStake {
address: string;
value: string;
Expand Down Expand Up @@ -79,7 +68,6 @@ export interface StakingContextInterface {
isNominating: () => boolean;
inSetup: () => boolean;
getLowestRewardFromStaker: (a: MaybeAddress) => LowestReward;
staking: StakingMetrics;
eraStakers: EraStakers;
targets: StakingTargets;
erasStakersSyncing: boolean;
Expand Down
8 changes: 4 additions & 4 deletions src/contexts/UI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export const UIContext = createContext<UIContextInterface>(
export const useUi = () => useContext(UIContext);

export const UIProvider = ({ children }: { children: ReactNode }) => {
const { staking, eraStakers } = useStaking();
const { eraStakers } = useStaking();
const { balancesInitialSynced } = useBalances();
const { isReady, networkMetrics, activeEra } = useApi();
const { synced: activePoolsSynced } = useActivePools();
const { isReady, networkMetrics, activeEra, stakingMetrics } = useApi();

// Set whether the network has been synced.
const [isNetworkSyncing, setIsNetworkSyncing] = useState<boolean>(false);
Expand Down Expand Up @@ -103,7 +103,7 @@ export const UIProvider = ({ children }: { children: ReactNode }) => {
let poolSyncing = false;

// staking metrics have synced
if (staking.lastReward === new BigNumber(0)) {
if (stakingMetrics.lastReward === new BigNumber(0)) {
syncing = true;
networkSyncing = true;
}
Expand Down Expand Up @@ -137,7 +137,7 @@ export const UIProvider = ({ children }: { children: ReactNode }) => {
setIsSyncing(syncing);
}, [
isReady,
staking,
stakingMetrics,
networkMetrics,
eraStakers,
activePoolsSynced,
Expand Down
Loading

0 comments on commit f6c0b93

Please sign in to comment.