Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add disputes overview #9488

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions packages/page-parachains/src/Disputes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2017-2023 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { DisputeRecord } from './types.js';

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

import { AddressMini, Table } from '@polkadot/react-components';

import { useTranslation } from '../translate.js';
import useSessionDisputes from './useSessionDisputes.js';

interface Props {
className?: string;
}

function transposeDisputes (disputes: DisputeRecord): React.ReactNode[] {
let lastSession = '';
let inclSession = true;

return Object
.entries(disputes)
.reduce((flattened: [string, string, string[]][], [s, r]) =>
Object
.entries(r)
.reduce((flattened, [k, vals]) => {
flattened.push([s, k, vals]);

return flattened;
}, flattened), []
)
.map(([s, k, vals], index) => {
if (lastSession !== s) {
lastSession = s;
inclSession = true;
} else {
inclSession = false;
}

return (
<tr key={`${s}-${index}`}>
{
inclSession
? <Table.Column.Id value={s} />
: <td />
}
<td className='hash'>{k}</td>
<td className='all'>
{vals.map((v) => (
<AddressMini
key={v}
value={v}
/>
))}
</td>
</tr>
);
});
}

function Disputes ({ className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const disputeInfo = useSessionDisputes();

const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
[t<string>('disputes'), 'start', 3]
]);

const rows = useMemo(
() => disputeInfo?.disputes && transposeDisputes(disputeInfo.disputes),
[disputeInfo]
);

return (
<Table
className={className}
empty={rows && t<string>('No ongoing disputes found')}
header={headerRef.current}
>
{rows}
</Table>
);
}

export default React.memo(Disputes);
19 changes: 19 additions & 0 deletions packages/page-parachains/src/Disputes/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2017-2023 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { BN } from '@polkadot/util';
import type { HexString } from '@polkadot/util/types';

export interface SessionInfo {
paraValidators: string[];
sessionCurrentIndex: BN;
sessionIndexes: BN[];
sessionValidators: string[];
}

export type DisputeRecord = Record<string, Record<HexString, string[]>>;

export interface DisputeInfo {
disputes?: DisputeRecord;
sessionInfo: SessionInfo;
}
77 changes: 77 additions & 0 deletions packages/page-parachains/src/Disputes/useSessionDisputes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2017-2023 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { ApiPromise } from '@polkadot/api';
import type { Option, StorageKey, u32 } from '@polkadot/types';
import type { Hash } from '@polkadot/types/interfaces';
import type { PolkadotPrimitivesV4DisputeState } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { HexString } from '@polkadot/util/types';
import type { DisputeInfo, SessionInfo } from './types.js';

import { useEffect, useState } from 'react';

import { createNamedHook, useApi } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';

import useSessionInfo from './useSessionInfo.js';

function queryDisputes (api: ApiPromise, sessionInfo: SessionInfo): Promise<DisputeInfo> {
return Promise
.all(
// FIXME We would need to pull the list of validators alongside
// sessionInfo.sessionIndexes.map((index) =>
[sessionInfo.sessionCurrentIndex].map((index) =>
api.query.parasDisputes.disputes.entries(index) as unknown as Promise<[StorageKey<[u32, Hash]>, Option<PolkadotPrimitivesV4DisputeState>][]>
)
)
.then((entries) =>
// TODO Here we wish to extract the actual sessionValidators as well
entries.map((list) =>
list.map(([{ args: [session, id] }, optInfo]): [BN, Hash, boolean[]] => [session, id, optInfo.isSome ? optInfo.unwrap().validatorsAgainst.toBoolArray() : []])
)
)
.then((entries) => ({
disputes: entries.reduce<Record<string, Record<HexString, string[]>>>((all, list) =>
list.reduce((all, [sessionIndex, hash, flags]) => {
const s = formatNumber(sessionIndex);

if (!all[s]) {
all[s] = {};
}

all[s][hash.toHex()] = flags.reduce<string[]>((vals, flag, index) => {
if (flag) {
vals.push(sessionInfo.paraValidators[index]);
}

return vals;
}, []);

return all;
}, all), {}),
sessionInfo
}));
}

function useSessionDisputesImpl (): DisputeInfo | undefined {
const { api } = useApi();
const [state, setState] = useState<DisputeInfo | undefined>();
const sessionInfo = useSessionInfo();

useEffect((): void => {
if (sessionInfo) {
if (sessionInfo.sessionIndexes) {
queryDisputes(api, sessionInfo)
.then(setState)
.catch(console.error);
} else {
setState({ sessionInfo });
}
}
}, [api, sessionInfo]);

return state;
}

export default createNamedHook('useSessionDisputes', useSessionDisputesImpl);
47 changes: 47 additions & 0 deletions packages/page-parachains/src/Disputes/useSessionInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2017-2023 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Option, u32 } from '@polkadot/types';
import type { AccountId } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';
import type { SessionInfo } from './types.js';

import { createNamedHook, useApi, useCallMulti } from '@polkadot/react-hooks';
import { BN_ONE } from '@polkadot/util';

const OPT_MULTI = {
transform: ([sessionCurrentIndex, validators, optLastPruned, activeValidatorIndices]: [u32, AccountId[], Option<u32>, u32[]]): SessionInfo => {
const sessionValidators = validators.map((v) => v.toString());
const sessionIndexes: BN[] = [sessionCurrentIndex];

if (optLastPruned.isSome) {
const lastPruned = optLastPruned.unwrap();
const nextIndex = sessionCurrentIndex.sub(BN_ONE);

while (nextIndex.gt(lastPruned)) {
sessionIndexes.push(nextIndex);
nextIndex.isub(BN_ONE);
}
}

return {
paraValidators: activeValidatorIndices.map((i) => sessionValidators[i.toNumber()]),
sessionCurrentIndex,
sessionIndexes,
sessionValidators
};
}
};

function useSessionInfoImpl (): SessionInfo | undefined {
const { api } = useApi();

return useCallMulti<SessionInfo>([
api.query.session?.currentIndex,
api.query.session?.validators,
api.query.parasDisputes?.lastPrunedSession,
api.query.parasShared?.activeValidatorIndices
], OPT_MULTI);
}

export default createNamedHook('useSessionInfo', useSessionInfoImpl);
7 changes: 7 additions & 0 deletions packages/page-parachains/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useApi, useCall } from '@polkadot/react-hooks';

import Auctions from './Auctions/index.js';
import Crowdloan from './Crowdloan/index.js';
import Disputes from './Disputes/index.js';
import Overview from './Overview/index.js';
import Parathreads from './Parathreads/index.js';
import Proposals from './Proposals/index.js';
Expand Down Expand Up @@ -100,6 +101,12 @@ function ParachainsApp ({ basePath, className }: Props): React.ReactElement<Prop
}
path='crowdloan'
/>
<Route
element={
<Disputes />
}
path='disputes'
/>
<Route
element={
<Proposals proposals={proposals} />
Expand Down
12 changes: 9 additions & 3 deletions packages/react-components/src/Table/Column/Id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { BN } from '@polkadot/util';

import React from 'react';

import { formatNumber } from '@polkadot/util';
import { formatNumber, isString } from '@polkadot/util';

import { styled } from '../../styled.js';

Expand All @@ -14,7 +14,7 @@ export interface Props {
className?: string;
colSpan?: number;
rowSpan?: number;
value: BN | number;
value: BN | number | string;
}

function Id ({ children, className = '', colSpan, rowSpan, value }: Props): React.ReactElement<Props> {
Expand All @@ -24,7 +24,13 @@ function Id ({ children, className = '', colSpan, rowSpan, value }: Props): Reac
colSpan={colSpan}
rowSpan={rowSpan}
>
<h2 className='--digits'>{formatNumber(value)}</h2>
<h2 className='--digits'>
{
isString(value)
? value
: formatNumber(value)
}
</h2>
{children}
</StyledTd>
);
Expand Down