Skip to content

Commit

Permalink
Merge pull request #35 from Cardinal-Cryptography/rename-kicks-to-sus…
Browse files Browse the repository at this point in the history
…pensions

A0-1530 Rename kickouts to Suspensions
  • Loading branch information
Marcin-Radecki authored Nov 7, 2022
2 parents 3633cc0 + 46f0cd5 commit 86950e5
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 156 deletions.
128 changes: 0 additions & 128 deletions packages/page-staking/src/Kickouts/Kickouts.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { useApi, useDeriveAccountInfo } from '@polkadot/react-hooks';
interface Props {
address: string;
era: number;
kickoutReason: string;
suspensionReason: string;
filterName: string,
suspensionLiftsInEra: number,
}

function useAddressCalls (api: ApiPromise, address: string) {
Expand All @@ -25,7 +26,7 @@ function queryAddress (address: string) {
window.location.hash = `/staking/query/${address}`;
}

function Address ({ address, era, filterName, kickoutReason }: Props): React.ReactElement<Props> | null {
function Address ({ address, era, filterName, suspensionLiftsInEra, suspensionReason }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const { accountInfo } = useAddressCalls(api, address);

Expand All @@ -52,7 +53,10 @@ function Address ({ address, era, filterName, kickoutReason }: Props): React.Rea
{era}
</td>
<td className='number'>
{kickoutReason}
{suspensionLiftsInEra}
</td>
<td className='number'>
{suspensionReason}
</td>
<td className='number'>
<Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@ import { Table } from '@polkadot/react-components';
import Filtering from '../Filtering';
import { useTranslation } from '../translate';
import Address from './Address';
import { KickOutEvent } from './index';
import { SuspensionEvent } from './index';

interface Props {
kicks: KickOutEvent[] | undefined,
suspensions: SuspensionEvent[] | undefined,
}

function CurrentList ({ kicks }: Props): React.ReactElement<Props> {
function CurrentList ({ suspensions }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [nameFilter, setNameFilter] = useState<string>('');

const headerRef = useRef(
[
[t('kick-outs'), 'start', 1],
[t('era'), 'expand'],
[t('kick-out reason'), 'expand'],
[t('suspensions'), 'start', 1],
[t('start era'), 'expand'],
[t('end era'), 'expand'],
[t('reason'), 'expand'],
[t('stats'), 'expand']

]
);

return (
<Table
empty={
kicks !== undefined && kicks.length === 0 && t<string>('No kick-out events found in the past 84 eras')
suspensions !== undefined && suspensions.length === 0 && t<string>('No suspensions events found in the past 84 eras')
}
emptySpinner={
<>
{kicks === undefined && <div>{t<string>('Retrieving kick-out events')}</div>}
{suspensions === undefined && <div>{t<string>('Retrieving suspensions events')}</div>}
</>
}
filter={
Expand All @@ -48,13 +48,14 @@ function CurrentList ({ kicks }: Props): React.ReactElement<Props> {
}
header={headerRef.current}
>
{kicks?.map(({ address, era, kickoutReason }): React.ReactNode => (
{suspensions?.map(({ address, era, suspensionLiftsInEra, suspensionReason }): React.ReactNode => (
<Address
address={address}
era={era}
filterName={nameFilter}
key={address}
kickoutReason={kickoutReason}
suspensionLiftsInEra={suspensionLiftsInEra}
suspensionReason={suspensionReason}
/>
))}
</Table>
Expand Down
136 changes: 136 additions & 0 deletions packages/page-staking/src/Suspensions/Suspensions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { useEffect, useMemo, useState } from 'react';

import useErasStartSessionIndexLookup from '@polkadot/app-staking/Performance/useErasStartSessionIndexLookup';
import { SuspensionEvent } from '@polkadot/app-staking/Suspensions/index';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
import { u64, Vec } from '@polkadot/types';
import { EventRecord, Hash } from '@polkadot/types/interfaces';
import { Codec } from '@polkadot/types/types';
import { u32 } from '@polkadot/types-codec';

type SuspensionReasons = [string, string, number][];

function parseEvents (events: EventRecord[]): SuspensionReasons {
return events.filter(({ event }) => event.section === 'elections' && event.method === 'BanValidators')
.map(({ event }) => {
const raw = event.data[0] as unknown as Codec[][];

const reasons: SuspensionReasons = raw.map((value) => {
const account = value[0].toString();
const reasonAndEra = value[1].toHuman() as unknown as Record<string, Codec>;

const reasonTypeAndValue = reasonAndEra.reason as unknown as Record<string, string>;
const reasonType = Object.keys(reasonTypeAndValue)[0];
const reasonValue = Object.values(reasonTypeAndValue)[0];
const era = Number(reasonAndEra.start.toString());

if (reasonType === 'OtherReason') {
return [account, reasonValue, era];
} else if (reasonType === 'InsufficientUptime') {
return [account, 'Insufficient uptime in at least ' + reasonValue + ' sessions', era];
} else {
return [account, reasonType + ': ' + reasonValue, era];
}
});

return reasons;
}).flat();
}

interface BanConfig {
minimalExpectedPerformance: u64,
underperformedSessionCountThreshold: u32,
cleanSessionCounterDelay: u32,
banPeriod: u32,
}

function useSuspensions (): SuspensionEvent[] | undefined {
const { api } = useApi();
// below logic is not able to detect kicks in blocks in which elections has failed,
// as staking.erasStartSessionIndex is not populated (new era does not start)
const erasStartSessionIndexLookup = useErasStartSessionIndexLookup();
const [electionBlockHashes, setElectionBlockHashes] = useState<Hash[] | undefined>(undefined);
const [eventsInBlocks, setEventsInBlocks] = useState<SuspensionReasons | undefined>(undefined);
const [suspensionEvents, setSuspensionEvents] = useState<SuspensionEvent[] | undefined>(undefined);
const banConfig = useCall<BanConfig>(api.query.elections.banConfig);
const currentBanPeriod = useMemo(() => {
return banConfig?.banPeriod;
},
[banConfig]
);

const erasElectionsSessionIndexLookup = useMemo((): [number, number][] => {
return erasStartSessionIndexLookup
.filter(([, firstSession]) => firstSession > 0)
.map(([era, firstSession]) => [era, firstSession - 1]);
},
[erasStartSessionIndexLookup]
);

useEffect(() => {
if (!(api && api.consts.elections) || erasStartSessionIndexLookup.length === 0) {
return;
}

const sessionPeriod = Number(api.consts.elections.sessionPeriod.toString());
const promises = erasElectionsSessionIndexLookup.map(([, electionSessionIndex]) => {
return api.rpc.chain.getBlockHash(electionSessionIndex * sessionPeriod);
});

Promise.all(promises)
.then((blockHashes) => setElectionBlockHashes(blockHashes))
.catch(console.error);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[api, JSON.stringify(erasElectionsSessionIndexLookup)]
);

useEffect(() => {
if (electionBlockHashes === undefined) {
return;
}

const promisesApiAtFirstBlock = electionBlockHashes.map((hash) => api.at(hash.toString()));

Promise.all(promisesApiAtFirstBlock).then((apis) => {
const promisesSystemEvents = apis.map((promise) => promise.query.system.events());

Promise.all(promisesSystemEvents)
.then((events: Vec<EventRecord>[]) => {
const parsedEvents = parseEvents(events.map((vecOfEvents) => vecOfEvents.toArray()).flat());

setEventsInBlocks(parsedEvents);
}).catch(console.error);
}).catch(console.error);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[api, JSON.stringify(electionBlockHashes)]
);

useEffect(() => {
if (!currentBanPeriod) {
return;
}

const events = eventsInBlocks?.map(([address, suspensionReason, era]) => {
return {
address,
era,
suspensionLiftsInEra: era + currentBanPeriod.toNumber(),
suspensionReason
};
}).reverse();

setSuspensionEvents(events);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[api, JSON.stringify(eventsInBlocks), currentBanPeriod]
);

return suspensionEvents;
}

export default createNamedHook('useSuspensions', useSuspensions);
Loading

0 comments on commit 86950e5

Please sign in to comment.