Skip to content

Commit

Permalink
feat(refactor): Initialise PAPI, ws & smoldot support, fetch network …
Browse files Browse the repository at this point in the history
…consts, tests (#2318)
  • Loading branch information
rossbulat authored Nov 6, 2024
1 parent b399669 commit a670f90
Show file tree
Hide file tree
Showing 41 changed files with 119,341 additions and 158 deletions.
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@ledgerhq/hw-transport-webhid": "^6.29.2",
"@polkadot-api/merkleize-metadata": "^1.1.4",
"@polkadot-api/known-chains": "^0.5.5",
"@polkadot-api/merkleize-metadata": "^1.1.9",
"@polkadot-api/metadata-builders": "^0.9.1",
"@polkadot-api/observable-client": "^0.5.14",
"@polkadot-api/sm-provider": "^0.1.3",
"@polkadot-api/smoldot": "^0.3.3",
"@polkadot-api/substrate-client": "^0.2.2",
"@polkadot-api/ws-provider": "^0.3.4",
"@polkadot/api": "^14.0.1",
"@polkadot/rpc-provider": "^14.0.1",
"@polkawatch/ddp-client": "^2.0.20",
Expand Down
6 changes: 3 additions & 3 deletions src/config/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export const SystemChainList: Record<string, SystemChain> = {
units: 10,
unit: 'DOT',
endpoints: {
lightClient: 'people_polkadot', // NOTE: Currently not being used. TODO: Revise this and activate once People chain specs are available to use.
lightClient: 'polkadot_people',
rpcEndpoints: {
Parity: 'wss://polkadot-people-rpc.polkadot.io',
},
Expand All @@ -202,7 +202,7 @@ export const SystemChainList: Record<string, SystemChain> = {
units: 12,
unit: 'KSM',
endpoints: {
lightClient: 'people_kusama', // NOTE: Currently not being used. TODO: Revise this and activate once People chain specs are available to use.
lightClient: 'ksmcc3_people',
rpcEndpoints: {
Parity: 'wss://kusama-people-rpc.polkadot.io',
},
Expand All @@ -214,7 +214,7 @@ export const SystemChainList: Record<string, SystemChain> = {
units: 12,
unit: 'WND',
endpoints: {
lightClient: 'people_westend', // NOTE: Currently not being used. TODO: Revise this and activate once People chain specs are available to use.
lightClient: 'westend2_people',
rpcEndpoints: {
Parity: 'wss://westend-people-rpc.polkadot.io',
},
Expand Down
16 changes: 11 additions & 5 deletions src/contexts/Api/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ import { stringToU8a } from '@polkadot/util';
import BigNumber from 'bignumber.js';
import type {
APIActiveEra,
APIChainState,
APIConstants,
APIContextInterface,
APINetworkMetrics,
APIPoolsConfig,
APIStakingMetrics,
} from 'contexts/Api/types';
import type { PAPIChainSpecs } from 'model/Api/types';

export const defaultChainState: APIChainState = {
chain: null,
version: {
export const defaultChainSpecs: PAPIChainSpecs & { received: boolean } = {
chain: 'Polkadot',
specs: {
apis: [],
implName: '',
implVersion: 0,
specName: 'polkadot',
specVersion: 0,
transactionVersion: 0,
},
ss58Prefix: 0,
received: false,
};

export const defaultConsts: APIConstants = {
Expand Down Expand Up @@ -76,7 +82,7 @@ export const defaultStakingMetrics: APIStakingMetrics = {
export const defaultApiContext: APIContextInterface = {
api: null,
peopleApi: null,
chainState: defaultChainState,
chainSpecs: defaultChainSpecs,
isReady: false,
apiStatus: 'disconnected',
peopleApiStatus: 'disconnected',
Expand Down
140 changes: 103 additions & 37 deletions src/contexts/Api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { NetworkList } from 'config/networks';

import type {
APIActiveEra,
APIChainState,
APIConstants,
APIContextInterface,
APINetworkMetrics,
Expand All @@ -20,10 +19,10 @@ import {
defaultConsts,
defaultActiveEra,
defaultApiContext,
defaultChainState,
defaultPoolsConfig,
defaultNetworkMetrics,
defaultStakingMetrics,
defaultChainSpecs,
} from './defaults';
import { isCustomEvent } from 'controllers/utils';
import { useEventListener } from 'usehooks-ts';
Expand All @@ -34,15 +33,16 @@ import type {
APIEventDetail,
ApiStatus,
ConnectionType,
PAPIChainSpecs,
} from 'model/Api/types';
import { StakingConstants } from 'model/Query/StakingConstants';
import { Era } from 'model/Query/Era';
import { NetworkMeta } from 'model/Query/NetworkMeta';
import { SubscriptionsController } from 'controllers/Subscriptions';
import { BlockNumber } from 'model/Subscribe/BlockNumber';
import { NetworkMetrics } from 'model/Subscribe/NetworkMetrics';
import { ActiveEra } from 'model/Subscribe/ActiveEra';
import { PoolsConfig } from 'model/Subscribe/PoolsConfig';
import { SmoldotController } from 'controllers/Smoldot';

export const APIContext = createContext<APIContextInterface>(defaultApiContext);

Expand All @@ -67,14 +67,14 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {

// Setter for whether light client is active. Updates state and local storage.
const setConnectionType = (value: ConnectionType) => {
connectionTypeRef.current = value;
setConnectionTypeState(value);

if (value === 'ws') {
SmoldotController.terminate();
localStorage.removeItem('light_client');
return;
} else {
localStorage.setItem('light_client', 'true');
}
localStorage.setItem('light_client', 'true');
connectionTypeRef.current = value;
setConnectionTypeState(value);
};

// Store the active RPC provider.
Expand Down Expand Up @@ -105,12 +105,13 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
setRpcEndpointState(key);
};

// Store chain state.
const [chainState, setChainState] =
useState<APIChainState>(defaultChainState);
// Store network constants. Set in an event callback - ref also needed.
const [consts, setConstsState] = useState<APIConstants>(defaultConsts);
const constsRef = useRef(consts);

// Store network constants.
const [consts, setConsts] = useState<APIConstants>(defaultConsts);
const setConsts = (newConsts: APIConstants) => {
setStateWithRef(newConsts, setConstsState, constsRef);
};

// Store network metrics in state.
const [networkMetrics, setNetworkMetrics] = useState<APINetworkMetrics>(
Expand All @@ -131,27 +132,15 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
const [stakingMetrics, setStakingMetrics] = useState<APIStakingMetrics>(
defaultStakingMetrics
);

const [chainSpecs, setChainSpecs] = useState<
PAPIChainSpecs & { received: boolean }
>(defaultChainSpecs);

const stakingMetricsRef = useRef(stakingMetrics);

// Fetch chain state. Called once `provider` has been initialised.
const onApiReady = async () => {
const { api } = ApiController.get(network);

const newChainState = await Promise.all([
api.rpc.system.chain(),
api.consts.system.version,
api.consts.system.ss58Prefix,
]);

// Check that chain values have been fetched before committing to state. Could be expanded to
// check supported chains.
if (newChainState.every((c) => !!c?.toHuman())) {
const chain = newChainState[0]?.toString();
const version = newChainState[1]?.toJSON();
const ss58Prefix = Number(newChainState[2]?.toString());
setChainState({ chain, version, ss58Prefix });
}

// Assume chain state is correct and bootstrap network consts.
bootstrapNetworkConfig();
};
Expand All @@ -163,9 +152,6 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {

// 1. Fetch network data for bootstrapping app state:

// Get general network constants for staking UI.
const newConsts = await new StakingConstants().fetch(api, network);

// Get active and previous era.
const { activeEra: newActiveEra, previousEra } = await new Era().fetch(api);

Expand All @@ -178,7 +164,6 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {

// 2. Populate all config state:

setConsts(newConsts);
setStateWithRef(newNetworkMetrics, setNetworkMetrics, networkMetricsRef);
const { index, start } = newActiveEra;
setStateWithRef({ index, start }, setActiveEra, activeEraRef);
Expand Down Expand Up @@ -233,6 +218,86 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
}
};

const handlePapiReady = (e: Event) => {
if (isCustomEvent(e)) {
const { chainType, chain, specs, ss58Prefix } = e.detail;
// We are only interested in the Relay chain.
if (chainType === 'relay') {
const newChainSpecs = {
chain,
specs,
ss58Prefix,
};
setChainSpecs({ ...newChainSpecs, received: true });

// Fetch chain constants. NOTE: Once we go chain agnostic default values can be removed in
// favour of throwing an error that'll need UI.
const apiInstance = ApiController.get(network);

const bondingDuration = apiInstance.getConstant(
'Staking',
'BondingDuration',
0
);
const sessionsPerEra = apiInstance.getConstant(
'Staking',
'SessionsPerEra',
0
);
const maxExposurePageSize = apiInstance.getConstant(
'Staking',
'MaxExposurePageSize',
0
);
const historyDepth = apiInstance.getConstant(
'Staking',
'HistoryDepth',
0
);
const expectedBlockTime = apiInstance.getConstant(
'Babe',
'ExpectedBlockTime',
0
);
const epochDuration = apiInstance.getConstant(
'Babe',
'EpochDuration',
0
);
const existentialDeposit = apiInstance.getConstant(
'Balances',
'ExistentialDeposit',
0
);
const fastUnstakeDeposit = apiInstance.getConstant(
'FastUnstake',
'Deposit',
0
);
const poolsPalletId = apiInstance.getConstant(
'NominationPools',
'PalletId',
new Uint8Array(0),
'asBytes'
);

setConsts({
maxNominations: new BigNumber(16),
maxElectingVoters: new BigNumber(22500),
bondDuration: new BigNumber(bondingDuration),
sessionsPerEra: new BigNumber(sessionsPerEra),
maxExposurePageSize: new BigNumber(maxExposurePageSize),
historyDepth: new BigNumber(historyDepth),
expectedBlockTime: new BigNumber(expectedBlockTime.toString()),
epochDuration: new BigNumber(epochDuration.toString()),
existentialDeposit: new BigNumber(existentialDeposit.toString()),
fastUnstakeDeposit: new BigNumber(fastUnstakeDeposit.toString()),
poolsPalletId,
});
}
}
};

// Handle an Api status event for a relay chain.
const handleRelayApiStatus = (detail: APIEventDetail) => {
const {
Expand Down Expand Up @@ -435,7 +500,7 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {

// Reset consts and chain state.
setConsts(defaultConsts);
setChainState(defaultChainState);
setChainSpecs(defaultChainSpecs);
setStateWithRef(
defaultNetworkMetrics,
setNetworkMetrics,
Expand Down Expand Up @@ -463,6 +528,7 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {

// Add event listener for api events and subscription updates.
const documentRef = useRef<Document>(document);
useEventListener('papi-ready', handlePapiReady, documentRef);
useEventListener('api-status', handleNewApiStatus, documentRef);
useEventListener(
'new-network-metrics',
Expand All @@ -482,14 +548,14 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
value={{
api: ApiController.get(network)?.api || null,
peopleApi: ApiController.get(`people-${network}`)?.api || null,
chainState,
chainSpecs,
apiStatus,
peopleApiStatus,
connectionType,
setConnectionType,
rpcEndpoint,
setRpcEndpoint,
isReady: apiStatus === 'ready',
isReady: apiStatus === 'ready' && chainSpecs.received === true,
consts,
networkMetrics,
activeEra,
Expand Down
15 changes: 6 additions & 9 deletions src/contexts/Api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ import type { ApiPromise } from '@polkadot/api';
import type BigNumber from 'bignumber.js';
import type { ReactNode } from 'react';
import type { NetworkName } from '../../types';
import type { ApiStatus, ConnectionType } from 'model/Api/types';
import type { AnyJson } from '@w3ux/types';
import type {
ApiStatus,
ConnectionType,
PAPIChainSpecs,
} from 'model/Api/types';

export interface APIProviderProps {
children: ReactNode;
network: NetworkName;
}

export interface APIChainState {
chain: string | null;
version: AnyJson;
ss58Prefix: number;
}

export interface APIConstants {
bondDuration: BigNumber;
maxNominations: BigNumber;
Expand Down Expand Up @@ -73,7 +70,7 @@ export interface APIStakingMetrics {
export interface APIContextInterface {
api: ApiPromise | null;
peopleApi: ApiPromise | null;
chainState: APIChainState;
chainSpecs: PAPIChainSpecs;
isReady: boolean;
apiStatus: ApiStatus;
peopleApiStatus: ApiStatus;
Expand Down
4 changes: 2 additions & 2 deletions src/contexts/LedgerHardware/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export const LedgerHardwareProvider = ({
children: ReactNode;
}) => {
const { t } = useTranslation('modals');
const { chainState } = useApi();
const { transactionVersion } = chainState.version;
const { chainSpecs } = useApi();
const { transactionVersion } = chainSpecs.specs;

// Store whether a Ledger device task is in progress.
const [isExecuting, setIsExecutingState] = useState<boolean>(false);
Expand Down
Loading

0 comments on commit a670f90

Please sign in to comment.