Skip to content

Commit

Permalink
fix: support caching of new Metadata_metadata_at_version method, fix …
Browse files Browse the repository at this point in the history
…of cache miss (#2864)
  • Loading branch information
johnthecat authored Dec 17, 2024
1 parent 308bba7 commit 656749b
Show file tree
Hide file tree
Showing 15 changed files with 544 additions and 207 deletions.
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@
"@apollo/client": "3.11.8",
"@ariakit/react": "0.4.14",
"@headlessui/react": "^1.7.17",
"@polkadot/api": "14.3.1",
"@polkadot/api": "15.0.2",
"@polkadot/keyring": "13.2.3",
"@polkadot/react-identicon": "3.11.3",
"@polkadot/rpc-provider": "14.3.1",
"@polkadot/types": "14.3.1",
"@polkadot/rpc-provider": "15.0.2",
"@polkadot/types": "15.0.2",
"@polkadot/util": "13.2.3",
"@polkadot/util-crypto": "13.2.3",
"@radix-ui/react-accordion": "1.2.1",
Expand All @@ -87,9 +87,9 @@
"@radix-ui/react-tooltip": "1.1.3",
"@react-spring/web": "9.7.5",
"@remote-ui/rpc": "^1.4.4",
"@substrate/connect": "1.3.1",
"@substrate/txwrapper-orml": "7.5.2",
"@substrate/txwrapper-polkadot": "7.5.2",
"@substrate/connect": "2.1.1",
"@substrate/txwrapper-orml": "7.5.3",
"@substrate/txwrapper-polkadot": "7.5.3",
"@walletconnect/types": "2.17.2",
"@walletconnect/universal-provider": "^2.17.2",
"@walletconnect/utils": "2.17.2",
Expand Down Expand Up @@ -119,6 +119,7 @@
"lodash": "4.17.21",
"lodash-es": "4.17.21",
"lottie-react": "2.4.0",
"mitt": "3.0.1",
"parity-scale-codec": "^0.6.1",
"patronum": "2.2.0",
"qr-code-styling": "1.6.0-rc.1",
Expand Down
362 changes: 285 additions & 77 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/renderer/entities/network/lib/network-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function isAutoBalanceConnection(connection: Connection): boolean {
function getNewestMetadata(metadata: ChainMetadata[]): Record<ChainId, ChainMetadata> {
return metadata.reduce<Record<ChainId, ChainMetadata>>(
(acc, data) => {
if (data.version >= (acc[data.chainId]?.version || -1)) {
if (data.runtimeVersion >= (acc[data.chainId]?.runtimeVersion || -1)) {
acc[data.chainId] = data;
}

Expand Down
30 changes: 15 additions & 15 deletions src/renderer/entities/network/model/__tests__/network-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('entities/network/model/network-model', () => {

const mockMetadata: ChainMetadata = {
id: 1,
version: 1,
runtimeVersion: 1,
metadataVersion: 15,
chainId: '0x01',
metadata: '0x123',
};
Expand Down Expand Up @@ -82,23 +83,21 @@ describe('entities/network/model/network-model', () => {
connections: [mockConnection],
metadata: [mockMetadata],
});

const provider = { isConnected: true, onMetadataReceived: () => {} } as unknown as ProviderWithMetadata;
const scope = fork();

const spyCreateProvider = jest
.spyOn(networkService, 'createProvider')
.mockReturnValue({ isConnected: true } as ProviderWithMetadata);
const spyCreateProvider = jest.spyOn(networkService, 'createProvider').mockReturnValue(provider);

await allSettled(networkModel.events.networkStarted, { scope });

expect(spyCreateProvider).toHaveBeenCalledWith(
mockChainMap['0x01'].chainId,
ProviderType.WEB_SOCKET,
{ metadata: mockMetadata.metadata, nodes: ['http://localhost:8080'] },
{ metadata: mockMetadata, nodes: ['http://localhost:8080'] },
{ onConnected: expect.any(Function), onDisconnected: expect.any(Function), onError: expect.any(Function) },
);
expect(scope.getState(networkModel._test.$providers)).toEqual({
'0x01': { isConnected: true },
'0x01': provider,
});
});

Expand All @@ -114,25 +113,26 @@ describe('entities/network/model/network-model', () => {
],
metadata: [mockMetadata],
});

const scope = fork();

const connectMock = jest.fn();
const spyCreateProvider = jest
.spyOn(networkService, 'createProvider')
.mockReturnValue({ connect: connectMock, isConnected: true } as unknown as ProviderWithMetadata);
const provider = {
connect: connectMock,
isConnected: true,
onMetadataReceived: () => {},
} as unknown as ProviderWithMetadata;
const spyCreateProvider = jest.spyOn(networkService, 'createProvider').mockReturnValue(provider);
const scope = fork();

await allSettled(networkModel.events.networkStarted, { scope });

expect(connectMock).toHaveBeenCalled();
expect(spyCreateProvider).toHaveBeenCalledWith(
mockChainMap['0x01'].chainId,
ProviderType.LIGHT_CLIENT,
{ metadata: mockMetadata.metadata, nodes: [''] },
{ metadata: mockMetadata, nodes: [''] },
{ onConnected: expect.any(Function), onDisconnected: expect.any(Function), onError: expect.any(Function) },
);
expect(scope.getState(networkModel._test.$providers)).toEqual({
'0x01': { connect: connectMock, isConnected: true },
'0x01': provider,
});
});
});
127 changes: 68 additions & 59 deletions src/renderer/entities/network/model/network-model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ApiPromise } from '@polkadot/api';
import { type VoidFn } from '@polkadot/api/types';
import { createEffect, createEvent, createStore, sample, scopeBind } from 'effector';
import { spread } from 'patronum';
import { combineEvents, spread } from 'patronum';

import {
ProviderType,
Expand All @@ -19,10 +19,9 @@ import {
ConnectionStatus,
ConnectionType,
type ID,
type Metadata,
type NoID,
} from '@/shared/core';
import { series } from '@/shared/effector';
import { createBuffer, series } from '@/shared/effector';
import { dictionary, nonNullable } from '@/shared/lib/utils';
import { networkUtils } from '../lib/network-utils';

Expand Down Expand Up @@ -66,36 +65,30 @@ type MetadataSubResult = {
chainId: ChainId;
unsubscribe: VoidFn;
};
const subscribeMetadataFx = createEffect(async (api: ApiPromise): Promise<MetadataSubResult> => {
const unsubscribe = await metadataService.subscribeMetadata(api, requestMetadataFx);

return { chainId: api.genesisHash.toHex(), unsubscribe };
});
const subscribeRuntimeVersionFx = createEffect(
async ({ api, cachedVersion }: { api: ApiPromise; cachedVersion: number | null }): Promise<MetadataSubResult> => {
const unsubscribe = await metadataService.subscribeRuntimeVersion({
api,
cachedRuntimeVersion: cachedVersion,
callback: removeMetadata,
});

const requestMetadataFx = createEffect((api: ApiPromise): Promise<NoID<ChainMetadata>> => {
return metadataService.requestMetadata(api);
});
return { chainId: api.genesisHash.toHex(), unsubscribe };
},
);

const unsubscribeMetadataFx = createEffect((unsubscribe: VoidFn) => {
unsubscribe();
});

const saveMetadataFx = createEffect((metadata: NoID<ChainMetadata>): Promise<ChainMetadata | undefined> => {
return storageService.metadata.put(metadata);
const saveMetadataFx = createEffect((metadata: NoID<ChainMetadata>[]): Promise<ChainMetadata[] | undefined> => {
return storageService.metadata.createAll(metadata);
});

const removeMetadataFx = createEffect((ids: ID[]): Promise<ID[] | undefined> => {
return storageService.metadata.deleteAll(ids);
});

type ProviderMetadataParams = {
provider: ProviderWithMetadata;
metadata: Metadata;
};
const updateProviderMetadataFx = createEffect(({ provider, metadata }: ProviderMetadataParams) => {
provider.updateMetadata(metadata);
});

type CreateProviderParams = {
chainId: ChainId;
nodes: string[];
Expand All @@ -118,7 +111,7 @@ const createProviderFx = createEffect(
const provider = networkService.createProvider(
chainId,
providerType,
{ nodes, metadata: metadata?.metadata },
{ nodes, metadata },
{
onConnected: () => {
if (DEBUG_NETWORKS) {
Expand All @@ -141,6 +134,10 @@ const createProviderFx = createEffect(
},
);

provider.onMetadataReceived(({ metadata, metadataVersion, runtimeVersion }) => {
metadataReceived({ chainId, metadata, metadataVersion, runtimeVersion });
});

if (providerType === ProviderType.LIGHT_CLIENT) {
/**
* HINT: Light Client provider must be connected manually GitHub Light
Expand All @@ -156,18 +153,17 @@ const createProviderFx = createEffect(

type CreateApiParams = {
chainId: ChainId;
providers: Record<ChainId, ProviderWithMetadata>;
apis: Record<ChainId, ApiPromise>;
provider: ProviderWithMetadata;
existingApi: ApiPromise | null;
};
const createApiFx = createEffect(async ({ chainId, providers, apis }: CreateApiParams): Promise<ApiPromise> => {
if (chainId in apis) {
const api = apis[chainId];
await api.connect();
const createApiFx = createEffect(async ({ chainId, provider, existingApi }: CreateApiParams): Promise<ApiPromise> => {
if (existingApi) {
await existingApi.connect();

return api;
return existingApi;
}

return networkService.createApi(chainId, providers[chainId]);
return networkService.createApi(chainId, provider);
});

type DisconnectParams = {
Expand Down Expand Up @@ -226,8 +222,13 @@ sample({
target: $connections,
});

const readyToConnect = combineEvents({
events: [populateConnectionsFx.done, populateMetadataFx.done, populateChainsFx.done],
reset: networkStarted,
});

sample({
clock: populateConnectionsFx.doneData,
clock: readyToConnect,
source: $chains,
fn: (chains) => {
return Object.keys(chains).map((chainId) => chainId as ChainId);
Expand Down Expand Up @@ -294,7 +295,11 @@ sample({
sample({
clock: connected,
source: { providers: $providers, apis: $apis },
fn: ({ providers, apis }, chainId) => ({ chainId, providers, apis }),
fn: ({ providers, apis }, chainId) => ({
chainId,
provider: providers[chainId],
existingApi: apis[chainId] ?? null,
}),
target: createApiFx,
});

Expand Down Expand Up @@ -366,13 +371,35 @@ sample({
// ================ Metadata section ===================
// =====================================================

const metadataReceived = createEvent<NoID<ChainMetadata>>();
const saveMetadata = createBuffer({ source: metadataReceived, timeframe: 2000 });
const removeMetadata = createEvent<ApiPromise>();

sample({
clock: createApiFx.doneData,
target: subscribeMetadataFx,
clock: removeMetadata,
source: $metadata,
fn: (list, removed) => {
return list.filter((x) => x.chainId === removed.genesisHash.toHex()).map((x) => x.id);
},
target: removeMetadataFx,
});

sample({
clock: createApiFx.done,
source: $metadata,
fn: (metadata, { params, result }) => {
const cachedVersion = metadata.find((m) => m.chainId === params.chainId)?.runtimeVersion ?? null;

return {
api: result,
cachedVersion,
};
},
target: subscribeRuntimeVersionFx,
});

sample({
clock: subscribeMetadataFx.doneData,
clock: subscribeRuntimeVersionFx.doneData,
source: $metadataSubscriptions,
fn: (subscriptions, { chainId, unsubscribe }) => ({
...subscriptions,
Expand Down Expand Up @@ -415,28 +442,21 @@ sample({
});

sample({
clock: requestMetadataFx.doneData,
source: $metadata,
filter: (metadata, newMetadata) => {
return metadata.every(({ chainId, version }) => {
return chainId !== newMetadata.chainId || version !== newMetadata.version;
});
},
fn: (_, metadata) => metadata,
clock: saveMetadata,
target: saveMetadataFx,
});

sample({
clock: saveMetadataFx.doneData,
source: $metadata,
filter: (_, newMetadata) => Boolean(newMetadata),
filter: (_, newMetadata) => nonNullable(newMetadata),
fn: (metadata, newMetadata) => {
const oldMetadata = metadata.filter(({ chainId }) => chainId === newMetadata!.chainId).map(({ id }) => id);
const cleanMetadata = metadata.filter(({ chainId }) => chainId !== newMetadata!.chainId);
const oldMetadata = metadata.filter(({ chainId }) => newMetadata!.find((m) => m.chainId === chainId));
const cleanMetadata = metadata.filter((x) => !oldMetadata.includes(x));

return {
metadata: [...cleanMetadata, newMetadata!],
oldMetadata,
metadata: cleanMetadata.concat(newMetadata!),
oldMetadata: oldMetadata.map((x) => x.id),
};
},
target: spread({
Expand All @@ -445,17 +465,6 @@ sample({
}),
});

sample({
clock: saveMetadataFx.doneData,
source: $providers,
filter: (_, metadata) => nonNullable(metadata),
fn: (providers, metadata) => ({
provider: providers[metadata!.chainId],
metadata: metadata!.metadata,
}),
target: updateProviderMetadataFx,
});

export const networkModel = {
$chains,
$apis,
Expand Down
26 changes: 6 additions & 20 deletions src/renderer/shared/api/network/__tests__/metadataService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,18 @@ import { type ApiPromise } from '@polkadot/api';
import { metadataService } from '../service/metadataService';

describe('shared/api/network/services/metadataService', () => {
test('should return UnsubscribePromise on subscribeMetadata', async () => {
test('should return UnsubscribePromise on subscribeRuntimeVersion', async () => {
const unsub = () => 5;
const apiMock = {
rpc: { state: { subscribeRuntimeVersion: () => Promise.resolve(unsub) } },
} as unknown as ApiPromise;

const result = await metadataService.subscribeMetadata(apiMock, () => {});
const result = await metadataService.subscribeRuntimeVersion({
api: apiMock,
cachedRuntimeVersion: null,
callback: () => {},
});
expect(result).toEqual(unsub);
expect(unsub()).toEqual(5);
});

test('should return metadata on requestMetadata', async () => {
const version = { specVersion: { toNumber: () => 5 } };
const metadata = { toHex: () => '0x11' };

const apiMock = {
genesisHash: { toHex: () => '0x00' },
rpc: {
state: {
getMetadata: () => Promise.resolve(metadata),
getRuntimeVersion: () => Promise.resolve(version),
},
},
} as unknown as ApiPromise;

const result = await metadataService.requestMetadata(apiMock);
expect(result).toEqual({ metadata: '0x11', version: 5, chainId: '0x00' });
});
});
Loading

0 comments on commit 656749b

Please sign in to comment.