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

Cross-chain region transfers #104

Merged
merged 6 commits into from
May 12, 2024
Merged
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ WS_ROCOCO_CORETIME_CHAIN="WSS endpoint of the coretime chain"
WS_KUSAMA_CORETIME_CHAIN="WSS endpoint of the coretime chain"
WS_ROCOCO_RELAY_CHAIN="WSS endpoint of the coretime relay chain"
WS_KUSAMA_RELAY_CHAIN="WSS endpoint of the coretime relay chain"
WS_REGIONX_CHAIN="WSS endpoint of the regionx chain"
EXPERIMENTAL=false
2 changes: 2 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ const nextConfig = {
env: {
WS_ROCOCO_CORETIME_CHAIN: process.env.WS_ROCOCO_CORETIME_CHAIN || '',
WS_KUSAMA_CORETIME_CHAIN: process.env.WS_KUSAMA_CORETIME_CHAIN || '',
WS_REGIONX_CHAIN: process.env.WS_REGIONX_CHAIN || '',
WS_ROCOCO_RELAY_CHAIN: process.env.WS_ROCOCO_RELAY_CHAIN,
WS_KUSAMA_RELAY_CHAIN: process.env.WS_KUSAMA_RELAY_CHAIN,
EXPERIMENTAL: process.env.EXPERIMENTAL,
},
};

Expand Down
8 changes: 8 additions & 0 deletions src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import React from 'react';

import Logo from '@/assets/logo.png';
import { useCoretimeApi, useRelayApi } from '@/contexts/apis';
import { EXPERIMENTAL } from '@/contexts/apis/consts';
import { useRegionXApi } from '@/contexts/apis/RegionXApi';
import { RenewIcon } from '@/icons';

import styles from './index.module.scss';
Expand Down Expand Up @@ -76,6 +78,9 @@ export const Sidebar = () => {
const {
state: { apiState: coretimeApiState },
} = useCoretimeApi();
const {
state: { apiState: regionxApiState },
} = useRegionXApi();

const menu = {
general: [
Expand Down Expand Up @@ -175,6 +180,9 @@ export const Sidebar = () => {
<div className={styles.statusContainer}>
<StatusIndicator state={relayApiState} label='Relay chain' />
<StatusIndicator state={coretimeApiState} label='Coretime chain' />
{EXPERIMENTAL && (
<StatusIndicator state={regionxApiState} label='RegionX chain' />
)}
</div>
</Box>
</div>
Expand Down
68 changes: 68 additions & 0 deletions src/contexts/apis/RegionXApi/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useContext, useEffect, useReducer } from 'react';

import { ApiState } from '@/contexts/apis/types';
import { useToast } from '@/contexts/toast';

import { connect, disconnect, initialState, reducer } from '../common';
import { EXPERIMENTAL, WS_REGIONX_CHAIN } from '../consts';

const types = {
CoreIndex: 'u32',
CoreMask: 'Vec<u8>',
Timeslice: 'u32',
RegionId: {
begin: 'Timeslice',
core: 'CoreIndex',
mask: 'CoreMask',
},
RegionRecord: {
end: 'Timeslice',
owner: 'AccountId',
paid: 'Option<Balance>',
},
};

const defaultValue = {
state: { ...initialState },
disconnectRegionX: (): void => {
/** */
},
};

const RegionXApiContext = React.createContext(defaultValue);

const RegionXApiContextProvider = (props: any) => {
const [state, dispatch] = useReducer(reducer, initialState);
const { toastError, toastSuccess } = useToast();

useEffect(() => {
state.apiError && toastError(`Failed to connect to RegionX chain`);
}, [state.apiError, toastError]);

useEffect(() => {
state.apiState === ApiState.READY &&
state.name &&
toastSuccess(`Successfully connected to ${state.name}`);
}, [state.apiState, state.name, toastSuccess]);

const disconnectRegionX = () => disconnect(state);

useEffect(() => {
if (!EXPERIMENTAL) return;
// TODO: currently we use the RegionX chain only when the experimental flag is on.
//
// For this reason we don't have different urls based on the network. However, this
// should be updated once this is in production.
connect(state, WS_REGIONX_CHAIN, dispatch, false, types);
}, [state]);

return (
<RegionXApiContext.Provider value={{ state, disconnectRegionX }}>
{props.children}
</RegionXApiContext.Provider>
);
};

const useRegionXApi = () => useContext(RegionXApiContext);

export { RegionXApiContextProvider, useRegionXApi };
2 changes: 2 additions & 0 deletions src/contexts/apis/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export const WS_ROCOCO_CORETIME_CHAIN =
process.env.WS_ROCOCO_CORETIME_CHAIN ?? '';
export const WS_KUSAMA_CORETIME_CHAIN =
process.env.WS_KUSAMA_CORETIME_CHAIN ?? '';
export const WS_REGIONX_CHAIN = process.env.WS_REGIONX_CHAIN ?? '';
export const EXPERIMENTAL = process.env.EXPERIMENTAL == 'true' ? true : false;
36 changes: 21 additions & 15 deletions src/contexts/regions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
CoreIndex,
getEncodedRegionId,
Region,
RegionId,
} from 'coretime-utils';
import { CoreIndex, getEncodedRegionId } from 'coretime-utils';
import React, {
createContext,
useCallback,
Expand All @@ -15,8 +10,11 @@ import React, {
import { RegionLocation, RegionMetadata } from '@/models';

import * as NativeRegions from './native';
import * as RegionXRegions from './regionx';
import { useAccounts } from '../account';
import { useCoretimeApi } from '../apis';
import { EXPERIMENTAL } from '../apis/consts';
import { useRegionXApi } from '../apis/RegionXApi';
import { useCommon } from '../common';
import { useTasks } from '../tasks';

Expand All @@ -25,7 +23,6 @@ interface RegionsData {
loading: boolean;
updateRegionName: (_index: number, _name: string) => void;
fetchRegions: () => Promise<void>;
fetchRegion: (_regionId: RegionId) => Promise<Region | null>;
}
const defaultRegionData: RegionsData = {
regions: [],
Expand All @@ -36,9 +33,6 @@ const defaultRegionData: RegionsData = {
fetchRegions: async () => {
/** */
},
fetchRegion: async () => {
return null;
},
};

const RegionDataContext = createContext<RegionsData>(defaultRegionData);
Expand All @@ -51,6 +45,9 @@ const RegionDataProvider = ({ children }: Props) => {
const {
state: { api: coretimeApi },
} = useCoretimeApi();
const {
state: { api: regionxApi },
} = useRegionXApi();
const {
state: { activeAccount },
} = useAccounts();
Expand Down Expand Up @@ -80,22 +77,33 @@ const RegionDataProvider = ({ children }: Props) => {
const tasks = await fetchWorkplan();

const brokerRegions = await NativeRegions.fetchRegions(coretimeApi);
const regionxRegions = EXPERIMENTAL
? await RegionXRegions.fetchRegions(regionxApi)
: [];

const _regions: Array<RegionMetadata> = [];

for await (const region of [...brokerRegions]) {
for await (const region of [...brokerRegions, ...regionxRegions]) {
// Only user owned non-expired regions.
if (
region.getOwner() !== activeAccount.address ||
region.consumed(context) > 1
)
continue;

const location = brokerRegions.find(
(r) =>
JSON.stringify(r.getRegionId()) ===
JSON.stringify(region.getRegionId()) &&
r.getOwner() === region.getOwner()
)
? RegionLocation.CORETIME_CHAIN
: RegionLocation.REGIONX_CHAIN;

const rawId = getEncodedRegionId(
region.getRegionId(),
coretimeApi
location === RegionLocation.CORETIME_CHAIN ? coretimeApi : regionxApi
).toString();
const location = RegionLocation.CORETIME_CHAIN;

const name =
localStorage.getItem(`region-${rawId}`) ??
Expand Down Expand Up @@ -153,8 +161,6 @@ const RegionDataProvider = ({ children }: Props) => {
loading,
updateRegionName,
fetchRegions,
fetchRegion: (_r: RegionId) =>
NativeRegions.fetchRegion(coretimeApi, _r),
}}
>
{children}
Expand Down
25 changes: 1 addition & 24 deletions src/contexts/regions/native/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiPromise } from '@polkadot/api';
import { Region, RegionId } from 'coretime-utils';
import { Region } from 'coretime-utils';

import { parseHNString } from '@/utils/functions';

Expand Down Expand Up @@ -35,26 +35,3 @@ export const fetchRegions = async (
return [];
}
};

export const fetchRegion = async (
coretimeApi: ApiPromise | null,
regionId: RegionId
): Promise<Region | null> => {
if (!coretimeApi) return null;

const record: any = (
await coretimeApi.query.broker.regions(regionId)
).toHuman();

if (record) {
const { end, owner, paid } = record;

return new Region(regionId, {
end: parseHNString(end),
owner,
paid: paid ? parseHNString(paid) : null,
});
} else {
return null;
}
};
40 changes: 40 additions & 0 deletions src/contexts/regions/regionx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ApiPromise } from '@polkadot/api';
import { Region } from 'coretime-utils';

import { parseHNString } from '@/utils/functions';

export const fetchRegions = async (
regionxApi: ApiPromise | null
): Promise<Array<Region>> => {
if (!regionxApi) {
return [];
}
try {
const regionEntries = await regionxApi.query.regions.regions.entries();

const regions: Array<Region> = regionEntries
.map(([key, value]) => {
const keyTuple: any = key.toHuman(undefined, true);
const { begin, core, mask } = keyTuple[0] as any;
const { owner, record: _record } = value.toHuman() as any;

const regionId = {
begin: parseHNString(begin.toString()),
core: parseHNString(core.toString()),
mask: mask,
};
return new Region(
regionId,
/*dummy data*/ {
end: 0,
owner,
paid: null,
}
);
})
.filter((entry) => !!entry) as Array<Region>;
return regions;
} catch (_) {
return [];
}
};
1 change: 1 addition & 0 deletions src/contexts/tasks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const TaskDataProvider = ({ children }: Props) => {
).toHuman() as ScheduleItem[]
)[0];

if (!workload) return null;
if (workload.assignment === 'Pool') {
return POOLING_TASK_ID;
} else {
Expand Down
42 changes: 27 additions & 15 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
CoretimeApiContextProvider,
RelayApiContextProvider,
} from '@/contexts/apis';
import { EXPERIMENTAL } from '@/contexts/apis/consts';
import { RegionXApiContextProvider } from '@/contexts/apis/RegionXApi';
import { BalanceProvider } from '@/contexts/balance';
import { ContextDataProvider } from '@/contexts/common';
import { MarketProvider } from '@/contexts/market';
Expand All @@ -41,6 +43,24 @@ export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
const getLayout = Component.getLayout ?? ((page) => <Layout>{page}</Layout>);

const Content = (
<RelayApiContextProvider>
<BalanceProvider>
<ContextDataProvider>
<TaskDataProvider>
<RegionDataProvider>
<MarketProvider>
<SaleInfoProvider>
{getLayout(<Component {...pageProps} />)}
</SaleInfoProvider>
</MarketProvider>
</RegionDataProvider>
</TaskDataProvider>
</ContextDataProvider>
</BalanceProvider>
</RelayApiContextProvider>
);

return (
<CacheProvider value={emotionCache}>
<Head>
Expand All @@ -53,21 +73,13 @@ export default function MyApp(props: MyAppProps) {
<NetworkProvider>
<AccountProvider>
<CoretimeApiContextProvider>
<RelayApiContextProvider>
<BalanceProvider>
<ContextDataProvider>
<TaskDataProvider>
<RegionDataProvider>
<MarketProvider>
<SaleInfoProvider>
{getLayout(<Component {...pageProps} />)}
</SaleInfoProvider>
</MarketProvider>
</RegionDataProvider>
</TaskDataProvider>
</ContextDataProvider>
</BalanceProvider>
</RelayApiContextProvider>
{EXPERIMENTAL ? (
<RegionXApiContextProvider>
{Content}
</RegionXApiContextProvider>
) : (
<>{Content}</>
)}
</CoretimeApiContextProvider>
</AccountProvider>
</NetworkProvider>
Expand Down
Loading
Loading