Skip to content

Commit

Permalink
Merge branch 'alephzero' into cp-123d90aa01
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcin-Radecki committed Feb 17, 2025
2 parents 123d90a + 454245d commit 4cf4c89
Show file tree
Hide file tree
Showing 31 changed files with 1,311 additions and 337 deletions.
33 changes: 31 additions & 2 deletions packages/apps-config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,34 @@
// SPDX-License-Identifier: Apache-2.0

import type { ApiTypes, AugmentedCall, DecoratedCallBase } from '@polkadot/api-base/types';
import type { Perbill } from '@polkadot/types/interfaces/runtime';
import type { Observable } from '@polkadot/types/types';
import type { Struct, Vec } from '@polkadot/types';
import type { SessionIndex } from '@polkadot/types/interfaces';
import type { AccountId32, Perbill } from '@polkadot/types/interfaces/runtime';
import type { AnyNumber, Codec, Observable } from '@polkadot/types/types';
import type { Enum, Result, u8 } from '@polkadot/types-codec';

export interface SessionCommitteeV15<T extends Codec> extends Struct {
readonly finalizers: Vec<T>;
readonly producers: Vec<T>;
}

export interface SessionCommitteeV14<T extends Codec> extends Struct {
readonly finalityCommittee: Vec<T>;
readonly blockProducers: Vec<T>;
}

export interface SessionNotWithinRange extends Struct {
readonly lowerLimit: SessionIndex;
readonly upperLimit: SessionIndex;
}

interface SessionValidatorError extends Enum {
readonly isOther: boolean;
readonly asOther: Vec<u8>;
readonly isSessionNotWithinRange: boolean;
readonly asSessionNotWithinRange: SessionNotWithinRange;
readonly type: 'Other' | 'SessionNotWithinRange';
}

declare module '@polkadot/api-base/types/calls' {
interface AugmentedCalls<ApiType extends ApiTypes> {
Expand All @@ -14,6 +40,9 @@ declare module '@polkadot/api-base/types/calls' {
**/
yearlyInflation?: AugmentedCall<ApiType, () => Observable<Perbill>>;
/**
Predict finality and block production committee
**/
predictSessionCommittee?: AugmentedCall<ApiType, (session: SessionIndex | AnyNumber | Uint8Array) => Observable<Result<SessionCommitteeV15<AccountId32> | SessionCommitteeV14<AccountId32>, SessionValidatorError>>>; /**
* Generic call
**/
[key: string]: DecoratedCallBase<ApiType> | undefined;
Expand Down
71 changes: 71 additions & 0 deletions packages/page-staking/src/FutureCommittee/Address/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2017-2025 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0

import React, { useCallback, useMemo } from 'react';

import { AddressSmall, Badge, Icon } from '@polkadot/react-components';
import { checkVisibility } from '@polkadot/react-components/util';
import { useAddressToDomain, useApi, useDeriveAccountInfo } from '@polkadot/react-hooks';

interface Props {
address: string;
currentSessionCommittee: boolean;
filterName: string;
nextSessionInCommittee?: number;
}

function useAddressCalls (address: string) {
const accountInfo = useDeriveAccountInfo(address);

return { accountInfo };
}

function queryAddress (address: string) {
window.location.hash = `/staking/query/${address}`;
}

function Address ({ address, currentSessionCommittee, filterName, nextSessionInCommittee }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const { accountInfo } = useAddressCalls(address);
const { primaryDomain: domain } = useAddressToDomain(address);

const isVisible = useMemo(
() => accountInfo ? checkVisibility(api, address, { ...accountInfo, domain }, filterName) : true,
[api, accountInfo, address, domain, filterName]
);

const onQueryStats = useCallback(
() => queryAddress(address),
[address]
);

if (!isVisible) {
return null;
}

return (
<tr>
<td className='address'>
<AddressSmall value={address} />
</td>
<td className='number'>
<Badge
color={currentSessionCommittee ? 'green' : 'transparent'}
icon={currentSessionCommittee ? 'check' : undefined}
/>
</td>
<td className='number'>
{nextSessionInCommittee ?? 'No next committee in the current era'}
</td>
<td className='number'>
<Icon
className='staking--stats highlight--color'
icon='chart-line'
onClick={onQueryStats}
/>
</td>
</tr>
);
}

export default React.memo(Address);
120 changes: 120 additions & 0 deletions packages/page-staking/src/FutureCommittee/FutureValidators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2017-2025 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0

import React, { useMemo, useRef, useState } from 'react';

import { Table } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';

import Filtering from '../Filtering.js';
import { useEraValidators } from '../Performance/useEraValidators.js';
import useFutureSessionCommittee from '../Performance/useFutureSessionCommittee.js';
import { useTranslation } from '../translate.js';
import useSessionValidators from '../useSessionValidators.js';
import Address from './Address/index.js';

interface Props {
currentSession: number;
maximumSessionNumber: number;
}

interface ListEntry {
accountId: string;
currentSessionCommittee: boolean;
nextSessionInCommittee?: number;
}

function range (size: number, startAt = 0) {
return [...Array(size).keys()].map((i) => i + startAt);
}

function FutureValidators ({ currentSession, maximumSessionNumber }: Props): React.ReactElement<Props> {
const { api } = useApi();
const { t } = useTranslation();

const [nameFilter, setNameFilter] = useState<string>('');

const sessionValidators = useSessionValidators(api);

const eraValidatorsAddresses = useEraValidators(currentSession);
const eraValidators = useMemo(() => {
if (eraValidatorsAddresses && eraValidatorsAddresses.length > 0) {
return eraValidatorsAddresses;
}

return [];
}, [eraValidatorsAddresses]
);

const futureSessions = useMemo(() => {
if (currentSession < maximumSessionNumber) {
return range(maximumSessionNumber - currentSession, currentSession + 1);
}

return [];
}, [currentSession, maximumSessionNumber]);
const futureSessionCommittee = useFutureSessionCommittee(futureSessions ?? []);

const validatorsList: ListEntry[] = useMemo(() => {
if (futureSessionCommittee.length > 0 && sessionValidators && sessionValidators.length > 0) {
return eraValidators.map((accountId) => {
const nextCommittee = futureSessionCommittee.find((futureCommittee) => {
return futureCommittee.producers.find((producer) => producer === accountId) !== undefined;
});

return {
accountId,
currentSessionCommittee: sessionValidators.includes(accountId),
nextSessionInCommittee: nextCommittee?.session
};
}).sort((a, b) => {
return Number(b.currentSessionCommittee) - Number(a.currentSessionCommittee);
});
}

return [];
}, [futureSessionCommittee, eraValidators, sessionValidators]);

const headerRef = useRef<[string, string, number?][]>(
[
[t('validators'), 'start', 1],
[t('current session committee'), 'expand'],
[t('next session committee'), 'expand'],
[t('stats'), 'expand']
]
);

return (
<Table
empty={
validatorsList && t('No active validators found')
}
emptySpinner={
<>
{!validatorsList && <div>{t('Preparing validator list')}</div>}
</>
}
filter={
<div className='staking--optionsBar'>
<Filtering
nameFilter={nameFilter}
setNameFilter={setNameFilter}
/>
</div>
}
header={headerRef.current}
>
{validatorsList.map(({ accountId, currentSessionCommittee, nextSessionInCommittee }): React.ReactNode => (
<Address
address={accountId}
currentSessionCommittee={currentSessionCommittee}
filterName={nameFilter}
key={accountId}
nextSessionInCommittee={nextSessionInCommittee}
/>
))}
</Table>
);
}

export default React.memo(FutureValidators);
23 changes: 23 additions & 0 deletions packages/page-staking/src/FutureCommittee/SummarySession.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2017-2025 @polkadot/app-explorer authors & contributors
// SPDX-License-Identifier: Apache-2.0

import React from 'react';

import { CardSummary } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';

interface Props {
session: number;
}

function SummarySession ({ session }: Props): React.ReactElement<Props> {
return (
<>
<CardSummary label={'session'}>
#{formatNumber(session)}
</CardSummary>
</>
);
}

export default React.memo(SummarySession);
47 changes: 47 additions & 0 deletions packages/page-staking/src/FutureCommittee/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2017-2025 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0

import React from 'react';

import { MarkWarning, Spinner, SummaryBox } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';

import useSessionInfo from '../Performance/useSessionInfo.js';
import FutureValidators from './FutureValidators.js';
import SummarySession from './SummarySession.js';

function FutureCommitteePage (): React.ReactElement {
const { api } = useApi();

const sessionInfo = useSessionInfo();

if (!api.runtimeChain.toString().includes('Aleph Zero')) {
return (
<MarkWarning content={'Unsupported chain.'} />
);
}

if (sessionInfo === undefined) {
return (
<Spinner label={'loading data'} />
);
}

return (
<div className='staking--Performance'>
<SummaryBox>
<section>
<SummarySession
session={sessionInfo.currentSession}
/>
</section>
</SummaryBox>
<FutureValidators
currentSession={sessionInfo.currentSession}
maximumSessionNumber={sessionInfo.maximumSessionNumber}
/>
</div>
);
}

export default React.memo(FutureCommitteePage);
Loading

0 comments on commit 4cf4c89

Please sign in to comment.