Displayed chilled nominators in waiting (#3361)

This commit is contained in:
Jaco Greeff 2020-08-07 12:21:47 +02:00 committed by GitHub
parent a0dda54777
commit e498669d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 48 deletions

View File

@ -2,8 +2,6 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Slash } from '../types';
import React from 'react';
import { AddressMini, Expander } from '@polkadot/react-components';
@ -13,12 +11,11 @@ import useInactives from '../useInactives';
interface Props {
nominating?: string[];
stashId: string;
slashes: Slash[];
}
function ListNominees ({ nominating, slashes, stashId }: Props): React.ReactElement<Props> {
function ListNominees ({ nominating, stashId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { nomsActive, nomsChilled, nomsInactive, nomsWaiting } = useInactives(stashId, slashes, nominating);
const { nomsActive, nomsChilled, nomsInactive, nomsWaiting } = useInactives(stashId, nominating);
return (
<>

View File

@ -202,7 +202,6 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
{isStashNominating && (
<ListNominees
nominating={nominating}
slashes={slashes}
stashId={stashId}
/>
)}

View File

@ -3,8 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveSessionIndexes } from '@polkadot/api-derive/types';
import { EraIndex, Exposure, Nominations } from '@polkadot/types/interfaces';
import { Slash } from './types';
import { EraIndex, Exposure, Nominations, SlashingSpans } from '@polkadot/types/interfaces';
import { useEffect, useState } from 'react';
import { useApi, useCall, useIsMountedRef } from '@polkadot/react-hooks';
@ -17,18 +16,10 @@ interface Inactives {
nomsWaiting?: string[];
}
function extractState (stashId: string, slashes: Slash[], nominees: string[], activeEra: EraIndex, submittedIn: EraIndex, exposures: Exposure[]): Inactives {
function extractState (stashId: string, slashes: Option<SlashingSpans>[], nominees: string[], activeEra: EraIndex, submittedIn: EraIndex, exposures: Exposure[]): Inactives {
// chilled
const nomsChilled = nominees.filter((nominee) =>
slashes.some(({ era, slashes }) =>
submittedIn.lt(era) && (
slashes
.filter(({ validator }) => validator.eq(nominee))
.some(({ others }) =>
others.some(([accountId]) => accountId.eq(stashId))
)
)
)
const nomsChilled = nominees.filter((_, index) =>
slashes[index].isSome && slashes[index].unwrap().lastNonzeroSlash.gte(submittedIn)
);
// first a blanket find of nominations not in the active set
@ -64,7 +55,7 @@ function extractState (stashId: string, slashes: Slash[], nominees: string[], ac
};
}
export default function useInactives (stashId: string, slashes: Slash[], nominees?: string[]): Inactives {
export default function useInactives (stashId: string, nominees?: string[]): Inactives {
const { api } = useApi();
const mountedRef = useIsMountedRef();
const [state, setState] = useState<Inactives>({});
@ -76,12 +67,19 @@ export default function useInactives (stashId: string, slashes: Slash[], nominee
if (mountedRef.current && nominees && nominees.length && indexes) {
api
.queryMulti(
[[api.query.staking.nominators, stashId] as any].concat(
api.query.staking.erasStakers
? nominees.map((id) => [api.query.staking.erasStakers, [indexes.activeEra, id]])
: nominees.map((id) => [api.query.staking.stakers, id])
),
([optNominators, ...exposures]: [Option<Nominations>, ...Exposure[]]): void => {
[[api.query.staking.nominators, stashId] as any]
.concat(
api.query.staking.erasStakers
? nominees.map((id) => [api.query.staking.erasStakers, [indexes.activeEra, id]])
: nominees.map((id) => [api.query.staking.stakers, id])
)
.concat(
nominees.map((id) => [api.query.staking.slashingSpans, id])
),
([optNominators, ...exposuresAndSpans]: [Option<Nominations>, ...(Exposure | Option<SlashingSpans>)[]]): void => {
const exposures = exposuresAndSpans.slice(0, nominees.length) as Exposure[];
const slashes = exposuresAndSpans.slice(nominees.length) as Option<SlashingSpans>[];
mountedRef.current && setState(
extractState(stashId, slashes, nominees, indexes.activeEra, optNominators.unwrapOrDefault().submittedIn, exposures)
);
@ -95,7 +93,7 @@ export default function useInactives (stashId: string, slashes: Slash[], nominee
return (): void => {
unsub && unsub();
};
}, [api, indexes, mountedRef, nominees, slashes, stashId]);
}, [api, indexes, mountedRef, nominees, stashId]);
return state;
}

View File

@ -2,32 +2,70 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import { EraIndex, SlashingSpans } from '@polkadot/types/interfaces';
import React, { useEffect, useState } from 'react';
import { AddressMini, Expander, Spinner } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../../translate';
interface Props {
nominators?: [string, number][];
nominators?: [string, EraIndex, number][];
slashingSpans?: SlashingSpans | null;
}
function NominatedBy ({ nominators }: Props): React.ReactElement<Props> {
interface Chilled {
active: string[];
chilled: string[];
}
function NominatedBy ({ nominators, slashingSpans }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [{ active, chilled }, setChilled] = useState<Chilled>({ active: [], chilled: [] });
useEffect((): void => {
if (nominators) {
const chilled = slashingSpans
? nominators
.filter(([, submittedIn]) => submittedIn.lte(slashingSpans.lastNonzeroSlash))
.map(([who]) => who)
: [];
const active = nominators
.filter(([who]) => !chilled.includes(who))
.map(([who]) => who);
setChilled({ active, chilled });
}
}, [nominators, slashingSpans]);
return (
<td className='start all'>
{nominators
? (nominators.length !== 0 && (
<Expander summary={t<string>('Nominations ({{count}})', { replace: { count: formatNumber(nominators.length) } })}>
{nominators.map(([who]): React.ReactNode =>
<AddressMini
key={who}
value={who}
/>
? (
<>
{active.length !== 0 && (
<Expander summary={t<string>('Nominations ({{count}})', { replace: { count: formatNumber(active.length) } })}>
{active.map((who): React.ReactNode =>
<AddressMini
key={who}
value={who}
/>
)}
</Expander>
)}
</Expander>
))
{chilled.length !== 0 && (
<Expander summary={t<string>('Chilled ({{count}})', { replace: { count: formatNumber(chilled.length) } })}>
{chilled.map((who): React.ReactNode =>
<AddressMini
key={who}
value={who}
/>
)}
</Expander>
)}
</>
)
: <Spinner variant='mini' />
}
</td>

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 { Balance } from '@polkadot/types/interfaces';
import { Balance, EraIndex, SlashingSpans } from '@polkadot/types/interfaces';
import { DeriveAccountInfo, DeriveStakingQuery } from '@polkadot/api-derive/types';
import BN from 'bn.js';
@ -11,6 +11,7 @@ import { AddressSmall, Icon } from '@polkadot/react-components';
import { checkVisibility } from '@polkadot/react-components/util';
import { useApi, useCall } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { Option } from '@polkadot/types';
import Favorite from './Favorite';
import NominatedBy from './NominatedBy';
@ -26,7 +27,7 @@ interface Props {
isFavorite: boolean;
isMain?: boolean;
lastBlock?: string;
nominatedBy?: [string, number][];
nominatedBy?: [string, EraIndex, number][];
onlineCount?: false | number;
onlineMessage?: boolean;
points?: string;
@ -74,6 +75,9 @@ function Address ({ address, className = '', filterName, hasQueries, isElected,
const { api } = useApi();
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [address]);
const stakingInfo = useCall<DeriveStakingQuery>(api.derive.staking.query, [address]);
const slashingSpans = useCall<SlashingSpans | null>(!isMain && api.query.staking.slashingSpans, [address], {
transform: (opt: Option<SlashingSpans>) => opt.unwrapOr(null)
});
const [{ commission, nominators, stakeOther, stakeOwn }, setStakingState] = useState<StakingState>({ nominators: [] });
const [isVisible, setIsVisible] = useState(true);
@ -123,7 +127,12 @@ function Address ({ address, className = '', filterName, hasQueries, isElected,
stakeOther={stakeOther}
/>
)
: <NominatedBy nominators={nominatedBy} />
: (
<NominatedBy
nominators={nominatedBy}
slashingSpans={slashingSpans}
/>
)
}
<td className='number'>
{stakeOwn?.gtn(0) && (

View File

@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/types';
import { AccountId, Nominations } from '@polkadot/types/interfaces';
import { AccountId, EraIndex, Nominations } from '@polkadot/types/interfaces';
import { Authors } from '@polkadot/react-query/BlockAuthors';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
@ -69,14 +69,15 @@ function getFiltered (stakingOverview: DeriveStakingOverview, favorites: string[
};
}
function extractNominators (nominations: [StorageKey, Option<Nominations>][]): Record<string, [string, number][]> {
return nominations.reduce((mapped: Record<string, [string, number][]>, [key, optNoms]) => {
function extractNominators (nominations: [StorageKey, Option<Nominations>][]): Record<string, [string, EraIndex, number][]> {
return nominations.reduce((mapped: Record<string, [string, EraIndex, number][]>, [key, optNoms]) => {
if (optNoms.isSome) {
const nominatorId = key.args[0].toString();
const { submittedIn, targets } = optNoms.unwrap();
optNoms.unwrap().targets.forEach((_validatorId, index): void => {
targets.forEach((_validatorId, index): void => {
const validatorId = _validatorId.toString();
const info: [string, number] = [nominatorId, index + 1];
const info: [string, EraIndex, number] = [nominatorId, submittedIn, index + 1];
if (!mapped[validatorId]) {
mapped[validatorId] = [info];
@ -98,7 +99,7 @@ function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOvervi
const nominators = useCall<[StorageKey, Option<Nominations>][]>(isIntentions && api.query.staking.nominators.entries as any, []);
const [{ elected, validators, waiting }, setFiltered] = useState<Filtered>({});
const [nameFilter, setNameFilter] = useState<string>('');
const [nominatedBy, setNominatedBy] = useState<Record<string, [string, number][]> | null>();
const [nominatedBy, setNominatedBy] = useState<Record<string, [string, EraIndex, number][]> | null>();
const [withIdentity, setWithIdentity] = useState(false);
// we have a very large list, so we use a loading delay