Swap parachain to multi calls (w/ useCallMulti) (#4414)

This commit is contained in:
Jaco Greeff 2021-01-14 18:15:29 +01:00 committed by GitHub
parent f930cce30c
commit fd9110f432
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 127 additions and 38 deletions

View File

@ -2,14 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
import type { LinkOption } from '@polkadot/apps-config/settings/types';
import type { Option } from '@polkadot/types';
import type { Balance, BlockNumber, HeadData, Header, ParaId, RelayChainBlockNumber } from '@polkadot/types/interfaces';
import type { Codec, ITuple } from '@polkadot/types/types';
import type { Option, Vec } from '@polkadot/types';
import type { Balance, BlockNumber, HeadData, Header, ParaId } from '@polkadot/types/interfaces';
import type { Codec } from '@polkadot/types/types';
import BN from 'bn.js';
import React, { useMemo } from 'react';
import { useApi, useCall, useParaApi } from '@polkadot/react-hooks';
import { useApi, useCall, useCallMulti, useParaApi } from '@polkadot/react-hooks';
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
@ -22,31 +22,40 @@ interface Props {
lastInclusion?: [string, string];
}
const transformDmpUmp = {
transform: (list: Codec[][]) => list.length
};
type QueryResult = [Option<HeadData>, Option<BlockNumber>, Vec<Codec>, Vec<Codec>, Vec<Codec>, Vec<Codec>];
const transformHead = {
transform: (headData: Option<HeadData>): string | null =>
headData.isSome
? sliceHex(headData.unwrap(), 18)
: null
};
// const transformMark = {
// transform: (watermark: Option<BlockNumber>): BlockNumber | null =>
// watermark.isSome
// ? watermark.unwrap()
// : null
// };
interface QueryState {
headHex: string | null;
updateAt: BlockNumber | null;
qDmp: number;
qUmp: number;
qHrmpE: number;
qHrmpI: number;
}
const transformLast = {
transform: (header: Header) => header.number.unwrap()
};
const transformUpgrade = {
transform: (opt: Option<ITuple<[RelayChainBlockNumber]>>): RelayChainBlockNumber | null =>
opt.unwrapOr([null])[0]
const transformMulti = {
defaultValue: {
headHex: null,
qDmp: 0,
qHrmpE: 0,
qHrmpI: 0,
qUmp: 0,
updateAt: null
},
transform: ([headData, optUp, dmp, ump, hrmpE, hrmpI]: QueryResult): QueryState => ({
headHex: headData.isSome
? sliceHex(headData.unwrap(), 18)
: null,
qDmp: dmp.length,
qHrmpE: hrmpE.length,
qHrmpI: hrmpI.length,
qUmp: ump.length,
updateAt: optUp.unwrapOr(null)
})
};
function getChainLink (endpoints: LinkOption[]): React.ReactNode {
@ -64,13 +73,15 @@ function Parachain ({ bestNumber, className = '', id, lastInclusion }: Props): R
const { api: paraApi, endpoints } = useParaApi(id);
const paraBest = useCall<BlockNumber>(paraApi?.derive.chain.bestNumber);
const paraIssu = useCall<Balance>(paraApi?.query.balances?.totalIssuance);
const headHex = useCall<string | null>(api.query.paras.heads, [id], transformHead);
const updateAt = useCall<BlockNumber | null>(api.query.paras.futureCodeUpgrades, [id], transformUpgrade);
const qDmp = useCall<number>(api.query.dmp.downwardMessageQueues, [id], transformDmpUmp);
const qUmp = useCall<number>(api.query.dmp.relayDispatchQueues, [id], transformDmpUmp);
const qHrmpE = useCall<number>(api.query.hrmp.hrmpEgressChannelsIndex, [id], transformDmpUmp);
const qHrmpI = useCall<number>(api.query.hrmp.hrmpIngressChannelsIndex, [id], transformDmpUmp);
const lastRelayNumber = useCall<BN>(lastInclusion && api.rpc.chain.getHeader, [lastInclusion && lastInclusion[1]], transformLast);
const paraInfo = useCallMulti<QueryState>([
[api.query.paras.heads, id],
[api.query.paras.futureCodeUpgrades, id],
[api.query.dmp.downwardMessageQueues, id],
[api.query.ump.relayDispatchQueues, id],
[api.query.hrmp.hrmpEgressChannelsIndex, id],
[api.query.hrmp.hrmpIngressChannelsIndex, id]
], transformMulti) as QueryState; // we have a default value, cast is safe
const blockDelay = useMemo(
() => lastRelayNumber && bestNumber && bestNumber.sub(lastRelayNumber).subn(1),
@ -86,7 +97,7 @@ function Parachain ({ bestNumber, className = '', id, lastInclusion }: Props): R
<tr className={className}>
<td className='number'><h1>{formatNumber(id)}</h1></td>
<td className='together'>{chainLink}</td>
<td className='all start together hash'>{headHex}</td>
<td className='all start together hash'>{paraInfo.headHex}</td>
<td className='number'>{blockDelay && <BlockToTime blocks={blockDelay} />}</td>
<td className='number'>
{lastInclusion && lastRelayNumber && (
@ -97,15 +108,15 @@ function Parachain ({ bestNumber, className = '', id, lastInclusion }: Props): R
<td className='number media--900'>{paraBest && <>{formatNumber(paraBest)}</>}</td>
<td className='number media--1100'>{paraIssu && <FormatBalance valueFormatted={paraIssu.toHuman()} />}</td>
<td className='number media--1300'>
{updateAt && bestNumber && (
{paraInfo.updateAt && bestNumber && (
<>
<BlockToTime blocks={bestNumber.sub(updateAt)} />
#{formatNumber(updateAt)}
<BlockToTime blocks={bestNumber.sub(paraInfo.updateAt)} />
#{formatNumber(paraInfo.updateAt)}
</>
)}
</td>
<td className='number media--1200'>
{formatNumber(qUmp)}&nbsp;/&nbsp;{formatNumber(qDmp)}&nbsp;/&nbsp;{formatNumber(qHrmpE)}&nbsp;/&nbsp;{formatNumber(qHrmpI)}
{formatNumber(paraInfo.qUmp)}&nbsp;/&nbsp;{formatNumber(paraInfo.qDmp)}&nbsp;/&nbsp;{formatNumber(paraInfo.qHrmpE)}&nbsp;/&nbsp;{formatNumber(paraInfo.qHrmpI)}
</td>
</tr>
);

View File

@ -11,6 +11,7 @@ export { useBlockTime } from './useBlockTime';
export { useBlocksPerDays } from './useBlocksPerDays';
export { useCacheKey } from './useCacheKey';
export { useCall } from './useCall';
export { useCallMulti } from './useCallMulti';
export { useContracts } from './useContracts';
export { useFormField } from './useFormField';
export { useDebounce } from './useDebounce';

View File

@ -4,12 +4,13 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { Codec } from '@polkadot/types/types';
import type { CallOptions, CallParam, CallParams } from './types';
import type { MountedRef } from './useIsMountedRef';
import { useEffect, useRef, useState } from 'react';
import { isNull, isUndefined } from '@polkadot/util';
import { MountedRef, useIsMountedRef } from './useIsMountedRef';
import { useIsMountedRef } from './useIsMountedRef';
type VoidFn = () => void;
@ -28,7 +29,7 @@ interface TrackFn {
};
}
interface Tracker {
export interface Tracker {
isActive: boolean;
serialized: string | null;
subscriber: TrackFnResult | null;
@ -39,7 +40,7 @@ interface TrackerRef {
}
// the default transform, just returns what we have
function transformIdentity <T> (value: unknown): T {
export function transformIdentity <T> (value: unknown): T {
return value as T;
}
@ -54,7 +55,7 @@ function extractParams <T> (fn: unknown, params: unknown[], { paramMap = transfo
}
// unsubscribe and remove from the tracker
function unsubscribe (tracker: TrackerRef): void {
export function unsubscribe (tracker: TrackerRef): void {
tracker.current.isActive = false;
if (tracker.current.subscriber) {

View File

@ -0,0 +1,76 @@
// Copyright 2017-2021 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { QueryableStorageMultiArg } from '@polkadot/api/types';
import type { Tracker } from './useCall';
import type { MountedRef } from './useIsMountedRef';
import { useEffect, useRef, useState } from 'react';
import { useApi } from './useApi';
import { transformIdentity, unsubscribe } from './useCall';
import { useIsMountedRef } from './useIsMountedRef';
interface TrackerRef {
current: Tracker;
}
interface CallOptions <T> {
defaultValue?: T;
transform?: (value: any) => T;
}
// subscribe, trying to play nice with the browser threads
function subscribe <T> (api: ApiPromise, mountedRef: MountedRef, tracker: TrackerRef, calls: QueryableStorageMultiArg<'promise'>[], setValue: (value: T) => void, { transform = transformIdentity }: CallOptions<T> = {}): void {
unsubscribe(tracker);
setTimeout((): void => {
if (mountedRef.current) {
if (calls.length) {
// swap to acive mode
tracker.current.isActive = true;
tracker.current.subscriber = api.queryMulti(calls, (value): void => {
// we use the isActive flag here since .subscriber may not be set on immediate callback)
if (mountedRef.current && tracker.current.isActive) {
mountedRef.current && tracker.current.isActive && setValue(
transform(value)
);
}
});
} else {
tracker.current.subscriber = null;
}
}
}, 0);
}
// very much copied from useCall
export function useCallMulti <T> (calls?: QueryableStorageMultiArg<'promise'>[] | null | false, options?: CallOptions<T>): T | undefined {
const { api } = useApi();
const mountedRef = useIsMountedRef();
const tracker = useRef<Tracker>({ isActive: false, serialized: null, subscriber: null });
const [value, setValue] = useState<T | undefined>((options || {}).defaultValue);
// initial effect, we need an un-subscription
useEffect((): () => void => {
return () => unsubscribe(tracker);
}, []);
// on changes, re-subscribe
useEffect((): void => {
// check if we have a function & that we are mounted
if (mountedRef.current && calls) {
const serialized = JSON.stringify(calls);
if (serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;
subscribe(api, mountedRef, tracker, calls, setValue, options);
}
}
}, [api, calls, options, mountedRef]);
return value;
}