Staking details query optimizations (#3618)

* Staking details query optimizations

* Map lookups

* Extras
This commit is contained in:
Jaco Greeff 2020-09-06 09:40:17 +02:00 committed by GitHub
parent 5faf9cba25
commit ca4b0b9cca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 47 additions and 41 deletions

View File

@ -17,6 +17,7 @@ Changes:
- Add individual era-slash cancels on staking slashes page
- Adjust staking slashes with per-era selector
- Staking list details now fully shared with targets (query optimizations)
- Adjust max payout batch size (40 txs for 64 payees) based on staking payout constant
- Add "copy seed" button to align with the extension UX on new accounts
- Combine confirmation/backup of imports/new account into the modals (no double-modal)

View File

@ -2,6 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import BN from 'bn.js';
import React from 'react';
import { Badge, Icon } from '@polkadot/react-components';
@ -11,7 +12,7 @@ interface Props {
isElected: boolean;
isMain?: boolean;
numNominators?: number;
onlineCount?: false | number;
onlineCount?: false | BN;
onlineMessage?: boolean;
}
@ -32,7 +33,7 @@ function Status ({ isElected, isMain, numNominators, onlineCount, onlineMessage
? (
<Badge
color='green'
info={onlineCount || <Icon icon='envelope' />}
info={onlineCount ? onlineCount.toNumber() : <Icon icon='envelope' />}
/>
)
: <Badge color='transparent' />

View File

@ -2,8 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Balance, EraIndex, SlashingSpans } from '@polkadot/types/interfaces';
import { DeriveAccountInfo, DeriveStakingQuery } from '@polkadot/api-derive/types';
import { Balance, EraIndex, SlashingSpans, ValidatorPrefs } from '@polkadot/types/interfaces';
import { DeriveAccountInfo } from '@polkadot/api-derive/types';
import { ValidatorInfo } from '../../types';
import BN from 'bn.js';
import React, { useCallback, useMemo } from 'react';
@ -29,10 +30,11 @@ interface Props {
isMain?: boolean;
lastBlock?: string;
nominatedBy?: [string, EraIndex, number][];
onlineCount?: false | number;
onlineCount?: false | BN;
onlineMessage?: boolean;
points?: string;
toggleFavorite: (accountId: string) => void;
validatorInfo?: ValidatorInfo;
withIdentity: boolean;
}
@ -44,9 +46,7 @@ interface StakingState {
stakeOwn?: BN;
}
const PERBILL_PERCENT = 10_000_000;
function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingState {
function expandInfo ({ exposure, validatorPrefs }: ValidatorInfo): StakingState {
let nominators: [string, Balance][] = [];
let stakeTotal: BN | undefined;
let stakeOther: BN | undefined;
@ -59,12 +59,10 @@ function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingS
stakeOther = stakeTotal.sub(stakeOwn);
}
const commission = validatorPrefs?.commission?.unwrap();
const commission = (validatorPrefs as ValidatorPrefs)?.commission?.unwrap();
return {
commission: commission
? `${(commission.toNumber() / PERBILL_PERCENT).toFixed(2)}%`
: undefined,
commission: commission?.toHuman(),
nominators,
stakeOther,
stakeOwn,
@ -80,18 +78,17 @@ function useAddressCalls (api: ApiPromise, address: string, isMain?: boolean) {
const params = useMemo(() => [address], [address]);
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, params);
const slashingSpans = useCall<SlashingSpans | null>(!isMain && api.query.staking.slashingSpans, params, transformSlashes);
const stakingInfo = useCall<DeriveStakingQuery>(api.derive.staking.query, params);
return { accountInfo, slashingSpans, stakingInfo };
return { accountInfo, slashingSpans };
}
function Address ({ address, className = '', filterName, hasQueries, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, toggleFavorite, withIdentity }: Props): React.ReactElement<Props> | null {
function Address ({ address, className = '', filterName, hasQueries, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, toggleFavorite, validatorInfo, withIdentity }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const { accountInfo, slashingSpans, stakingInfo } = useAddressCalls(api, address, isMain);
const { accountInfo, slashingSpans } = useAddressCalls(api, address, isMain);
const { commission, nominators, stakeOther, stakeOwn } = useMemo(
() => stakingInfo ? expandInfo(stakingInfo) : { nominators: [] },
[stakingInfo]
() => validatorInfo ? expandInfo(validatorInfo) : { nominators: [] },
[validatorInfo]
);
const isVisible = useMemo(
@ -121,7 +118,7 @@ function Address ({ address, className = '', filterName, hasQueries, isElected,
<Status
isElected={isElected}
isMain={isMain}
numNominators={nominatedBy?.length || nominators.length}
numNominators={(nominatedBy || nominators).length}
onlineCount={onlineCount}
onlineMessage={onlineMessage}
/>

View File

@ -5,6 +5,7 @@
import { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/types';
import { AccountId, EraIndex, Nominations } from '@polkadot/types/interfaces';
import { Authors } from '@polkadot/react-query/BlockAuthors';
import { SortedTargets, ValidatorInfo } from '../types';
import React, { useCallback, useContext, useMemo, useRef, useState } from 'react';
import { Table } from '@polkadot/react-components';
@ -23,6 +24,7 @@ interface Props {
next?: string[];
setNominators?: (nominators: string[]) => void;
stakingOverview?: DeriveStakingOverview;
targets: SortedTargets;
toggleFavorite: (address: string) => void;
}
@ -91,7 +93,7 @@ function extractNominators (nominations: [StorageKey, Option<Nominations>][]): R
}, {});
}
function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOverview, toggleFavorite }: Props): React.ReactElement<Props> | null {
function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOverview, targets, toggleFavorite }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const { byAuthor, eraPoints } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext);
@ -108,6 +110,15 @@ function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOvervi
[favorites, next, stakingOverview]
);
const infoMap = useMemo(
() => (targets?.validators || []).reduce((result: Record<string, ValidatorInfo>, info): Record<string, ValidatorInfo> => {
result[info.accountId.toString()] = info;
return result;
}, {}),
[targets]
);
const nominatedBy = useMemo(
() => nominators ? extractNominators(nominators) : null,
[nominators]
@ -145,14 +156,15 @@ function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOvervi
key={address}
lastBlock={byAuthor[address]}
nominatedBy={nominatedBy ? (nominatedBy[address] || []) : undefined}
onlineCount={recentlyOnline?.[address]?.blockCount.toNumber()}
onlineCount={recentlyOnline?.[address]?.blockCount}
onlineMessage={recentlyOnline?.[address]?.hasMessage}
points={eraPoints[address]}
toggleFavorite={toggleFavorite}
validatorInfo={infoMap[address]}
withIdentity={withIdentity}
/>
)),
[byAuthor, eraPoints, hasQueries, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withIdentity]
[byAuthor, eraPoints, hasQueries, infoMap, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withIdentity]
);
return isIntentions

View File

@ -3,6 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveStakingOverview } from '@polkadot/api-derive/types';
import { SortedTargets } from '../types';
import React from 'react';
@ -15,10 +16,11 @@ interface Props {
isIntentions?: boolean;
next?: string[];
stakingOverview?: DeriveStakingOverview;
targets: SortedTargets;
toggleFavorite: (address: string) => void;
}
function Overview ({ className = '', favorites, hasQueries, isIntentions, next, stakingOverview, toggleFavorite }: Props): React.ReactElement<Props> {
function Overview ({ className = '', favorites, hasQueries, isIntentions, next, stakingOverview, targets, toggleFavorite }: Props): React.ReactElement<Props> {
return (
<div className={`staking--Overview ${className}`}>
<CurrentList
@ -27,6 +29,7 @@ function Overview ({ className = '', favorites, hasQueries, isIntentions, next,
isIntentions={isIntentions}
next={next}
stakingOverview={stakingOverview}
targets={targets}
toggleFavorite={toggleFavorite}
/>
</div>

View File

@ -155,6 +155,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
isIntentions
next={next}
stakingOverview={stakingOverview}
targets={targets}
toggleFavorite={toggleFavorite}
/>
</Route>
@ -171,6 +172,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
hasQueries={hasQueries}
next={next}
stakingOverview={stakingOverview}
targets={targets}
toggleFavorite={toggleFavorite}
/>
</main>

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId, Balance, BlockNumber, Hash, SessionIndex } from '@polkadot/types/interfaces';
import { AccountId, Balance, BlockNumber, Exposure, Hash, SessionIndex, ValidatorPrefs, ValidatorPrefsTo196 } from '@polkadot/types/interfaces';
import BN from 'bn.js';
@ -46,6 +46,7 @@ export interface ValidatorInfo extends ValidatorInfoRank {
bondShare: number;
bondTotal: Balance;
commissionPer: number;
exposure: Exposure;
hasIdentity: boolean;
isActive: boolean;
isCommission: boolean;
@ -57,6 +58,7 @@ export interface ValidatorInfo extends ValidatorInfoRank {
rewardPayout: BN;
rewardSplit: BN;
validatorPayment: BN;
validatorPrefs?: ValidatorPrefs | ValidatorPrefsTo196;
}
export type TargetSortBy = keyof ValidatorInfoRank;

View File

@ -65,14 +65,8 @@ function sortValidators (list: ValidatorInfo[]): ValidatorInfo[] {
}
function extractSingle (allAccounts: string[], amount: BN = baseBalance(), { info }: DeriveStakingElected | DeriveStakingWaiting, favorites: string[], perValidatorReward: BN, isElected: boolean): [ValidatorInfo[], string[]] {
const defaultExposure = {
others: registry.createType('Vec<IndividualExposure>'),
own: registry.createType('Compact<Balance>'),
total: registry.createType('Compact<Balance>')
};
const defaultPrefs = {
commission: registry.createType('Compact<Perbill>')
};
const defaultExposure = registry.createType('Exposure');
const defaultPrefs = registry.createType('ValidatorPrefs');
const nominators: Record<string, boolean> = {};
const list = info.map(({ accountId, exposure: _exposure, stakingLedger, validatorPrefs }): ValidatorInfo => {
const exposure = _exposure || defaultExposure;
@ -108,6 +102,7 @@ function extractSingle (allAccounts: string[], amount: BN = baseBalance(), { inf
bondShare: 0,
bondTotal,
commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || BN_ZERO).toNumber() / 10_000_000),
exposure,
hasIdentity: false,
isActive: !skipRewards,
isCommission: !!(prefs as ValidatorPrefs).commission,
@ -126,7 +121,8 @@ function extractSingle (allAccounts: string[], amount: BN = baseBalance(), { inf
rankReward: 0,
rewardPayout: skipRewards ? BN_ZERO : rewardPayout,
rewardSplit,
validatorPayment
validatorPayment,
validatorPrefs: prefs
};
});
@ -146,14 +142,6 @@ function extractInfo (allAccounts: string[], amount: BN = baseBalance(), elected
const totalStaked = activeTotals.reduce((total: BN, value) => total.iadd(value), new BN(0));
const avgStaked = totalStaked.divn(activeTotals.length);
// median
// const midIndex = Math.floor(activeTotals.length / 2);
// const avgStaked = activeTotals.length
// ? activeTotals.length % 2
// ? activeTotals[midIndex]
// : activeTotals[midIndex - 1].add(activeTotals[midIndex]).divn(2)
// : BN_ZERO;
return { avgStaked, lowStaked: activeTotals[0] || BN_ZERO, nominators, totalStaked, validatorIds, validators };
}