Skip to content

Commit

Permalink
Cross-chain region transfers (#104)
Browse files Browse the repository at this point in the history
* Cross-chain region transfers

* both directions work

* conditional

* remove unused

* remove network specific url for now

* connect only when experimental
  • Loading branch information
Szegoo committed May 12, 2024
1 parent 7b8cd20 commit 7c3637a
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 97 deletions.
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

0 comments on commit 7c3637a

Please sign in to comment.