Skip to content

Commit

Permalink
Fixes for DB perf and smart provider (#63)
Browse files Browse the repository at this point in the history
- Upgrade to SDK 3.8.0
- Separate where or clauses into separate queries
- Remove SmartMultiProvider
  • Loading branch information
jmrossy authored Mar 13, 2024
1 parent 9f792a4 commit d0f80ad
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 125 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "@hyperlane-xyz/explorer",
"description": "An interchain explorer for the Hyperlane protocol and network.",
"version": "3.7.0",
"version": "3.8.0",
"author": "J M Rossy",
"dependencies": {
"@headlessui/react": "^1.7.17",
"@hyperlane-xyz/sdk": "3.7.0",
"@hyperlane-xyz/utils": "3.7.0",
"@hyperlane-xyz/widgets": "3.7.0",
"@hyperlane-xyz/sdk": "3.8.0",
"@hyperlane-xyz/utils": "3.8.0",
"@hyperlane-xyz/widgets": "3.8.0",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
"@rainbow-me/rainbowkit": "0.12.16",
"@tanstack/react-query": "^4.24.10",
Expand Down
5 changes: 3 additions & 2 deletions src/features/api/getMessages.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Client } from '@urql/core';
import type { NextApiRequest } from 'next';

import { MultiProvider } from '@hyperlane-xyz/sdk';

import { API_GRAPHQL_QUERY_LIMIT } from '../../consts/api';
import { logger } from '../../utils/logger';
import { sanitizeString } from '../../utils/string';
import { MessageIdentifierType, buildMessageQuery } from '../messages/queries/build';
import { MessagesQueryResult } from '../messages/queries/fragments';
import { parseMessageQueryResult } from '../messages/queries/parse';
import { SmartMultiProvider } from '../providers/SmartMultiProvider';

import { ApiHandlerResult, ApiMessage, toApiMessage } from './types';
import { failureResult, successResult } from './utils';
Expand All @@ -26,7 +27,7 @@ export async function handler(
API_GRAPHQL_QUERY_LIMIT,
);
const result = await client.query<MessagesQueryResult>(query, variables).toPromise();
const multiProvider = new SmartMultiProvider();
const multiProvider = new MultiProvider();
const messages = parseMessageQueryResult(multiProvider, result.data);
return successResult(messages.map(toApiMessage));
}
Expand Down
5 changes: 3 additions & 2 deletions src/features/api/getStatus.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Client } from '@urql/core';
import type { NextApiRequest } from 'next';

import { MultiProvider } from '@hyperlane-xyz/sdk';

import { API_GRAPHQL_QUERY_LIMIT } from '../../consts/api';
import { MessageStatus } from '../../types';
import { logger } from '../../utils/logger';
import { buildMessageQuery } from '../messages/queries/build';
import { MessagesStubQueryResult } from '../messages/queries/fragments';
import { parseMessageStubResult } from '../messages/queries/parse';
import { SmartMultiProvider } from '../providers/SmartMultiProvider';

import { parseQueryParams } from './getMessages';
import { ApiHandlerResult } from './types';
Expand All @@ -34,7 +35,7 @@ export async function handler(
);
const result = await client.query<MessagesStubQueryResult>(query, variables).toPromise();

const multiProvider = new SmartMultiProvider();
const multiProvider = new MultiProvider();
const messages = parseMessageStubResult(multiProvider, result.data);

return successResult(messages.map((m) => ({ id: m.msgId, status: m.status })));
Expand Down
5 changes: 3 additions & 2 deletions src/features/api/searchMessages.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Client } from '@urql/core';
import type { NextApiRequest } from 'next';

import { MultiProvider } from '@hyperlane-xyz/sdk';

import { API_GRAPHQL_QUERY_LIMIT } from '../../consts/api';
import { logger } from '../../utils/logger';
import { sanitizeString } from '../../utils/string';
import { buildMessageSearchQuery } from '../messages/queries/build';
import { MessagesQueryResult } from '../messages/queries/fragments';
import { parseMessageQueryResult } from '../messages/queries/parse';
import { SmartMultiProvider } from '../providers/SmartMultiProvider';

import { ApiHandlerResult, ApiMessage, toApiMessage } from './types';
import { failureResult, successResult } from './utils';
Expand All @@ -33,7 +34,7 @@ export async function handler(
);
const result = await client.query<MessagesQueryResult>(query, variables).toPromise();

const multiProvider = new SmartMultiProvider();
const multiProvider = new MultiProvider();
const messages = parseMessageQueryResult(multiProvider, result.data);

return successResult(messages.map(toApiMessage));
Expand Down
1 change: 0 additions & 1 deletion src/features/chains/ConfigureChains.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ const customChainTextareaPlaceholder = `{
} ],
"blocks": { "confirmations": 1, "estimateBlockTime": 13 },
"mailbox": "0x123...",
"interchainGasPaymaster": "0x123..."
}
`;

Expand Down
9 changes: 6 additions & 3 deletions src/features/chains/chainconfig.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { ChainMetadata, ExplorerFamily } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';

import { tryParseChainConfig } from './chainConfig';

const validConfig = {
const validConfig: ChainMetadata<{ mailbox: Address }> = {
chainId: 12345,
name: 'mytestnet',
protocol: 'ethereum',
protocol: ProtocolType.Ethereum,
rpcUrls: [{ http: 'https://fakerpc.com' }],
blockExplorers: [
{
name: 'FakeScan',
family: 'other',
family: ExplorerFamily.Other,
url: 'https://fakeexplorer.com',
apiUrl: 'https://fakeexplorer.com',
},
Expand Down
5 changes: 2 additions & 3 deletions src/features/messages/pi-queries/fetchPiChainMessages.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { chainMetadata, hyperlaneEnvironments } from '@hyperlane-xyz/sdk';
import { MultiProvider, chainMetadata, hyperlaneEnvironments } from '@hyperlane-xyz/sdk';

import { Message, MessageStatus } from '../../../types';
import { ChainConfig } from '../../chains/chainConfig';
import { SmartMultiProvider } from '../../providers/SmartMultiProvider';

import { fetchMessagesFromPiChain } from './fetchPiChainMessages';

Expand Down Expand Up @@ -155,5 +154,5 @@ describe('fetchMessagesFromPiChain', () => {
});

function createMP(config: ChainConfig) {
return new SmartMultiProvider({ ...chainMetadata, sepolia: config });
return new MultiProvider({ ...chainMetadata, sepolia: config });
}
79 changes: 45 additions & 34 deletions src/features/messages/queries/build.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isAddress } from '@hyperlane-xyz/utils';

import { adjustToUtcTime } from '../../../utils/time';

import { stringToPostgresBytea } from './encoding';
Expand Down Expand Up @@ -60,21 +62,6 @@ export function buildMessageQuery(
return { query, variables };
}

// TODO removing destination-based or clauses for now due to DB load
// Queries will need to be restructured into multiple requests
// https://github.com/hyperlane-xyz/hyperlane-explorer/issues/59
// {destination_tx_hash: {_eq: $search}},
// {destination_tx_sender: {_eq: $search}},
const searchWhereClause = `
{_or: [
{msg_id: {_eq: $search}},
{sender: {_eq: $search}},
{recipient: {_eq: $search}},
{origin_tx_hash: {_eq: $search}},
{origin_tx_sender: {_eq: $search}},
]}
`;

export function buildMessageSearchQuery(
searchInput: string,
originFilter: string | null,
Expand All @@ -97,25 +84,49 @@ export function buildMessageSearchQuery(
startTime,
endTime,
};
const whereClauses = buildSearchWhereClauses(searchInput);

const query = `
query ($search: bytea, $originChains: [bigint!], $destinationChains: [bigint!], $startTime: timestamp, $endTime: timestamp) {
message_view(
where: {
_and: [
${originFilter ? '{origin_chain_id: {_in: $originChains}},' : ''}
${destFilter ? '{destination_chain_id: {_in: $destinationChains}},' : ''}
${startTimeFilter ? '{send_occurred_at: {_gte: $startTime}},' : ''}
${endTimeFilter ? '{send_occurred_at: {_lte: $endTime}},' : ''}
${hasInput ? searchWhereClause : ''}
]
},
order_by: {send_occurred_at: desc},
limit: ${limit}
) {
${useStub ? messageStubFragment : messageDetailsFragment}
}
}
`;
// Due to DB performance issues, we cannot use an `_or` clause
// Instead, each where clause for the search will be its own query
const queries = whereClauses.map(
(whereClause, i) =>
`q${i}: message_view(
where: {
_and: [
${originFilter ? '{origin_chain_id: {_in: $originChains}},' : ''}
${destFilter ? '{destination_chain_id: {_in: $destinationChains}},' : ''}
${startTimeFilter ? '{send_occurred_at: {_gte: $startTime}},' : ''}
${endTimeFilter ? '{send_occurred_at: {_lte: $endTime}},' : ''}
${whereClause}
]
},
order_by: {send_occurred_at: desc},
limit: ${limit}
) {
${useStub ? messageStubFragment : messageDetailsFragment}
}`,
);

const query = `query ($search: bytea, $originChains: [bigint!], $destinationChains: [bigint!], $startTime: timestamp, $endTime: timestamp) {
${queries.join('\n')}
}`;
return { query, variables };
}

function buildSearchWhereClauses(searchInput: string) {
if (!searchInput) return [''];
if (isAddress(searchInput)) {
return [
`{sender: {_eq: $search}}`,
`{recipient: {_eq: $search}}`,
`{origin_tx_sender: {_eq: $search}}`,
`{destination_tx_sender: {_eq: $search}}`,
];
} else {
return [
`{msg_id: {_eq: $search}}`,
`{origin_tx_hash: {_eq: $search}}`,
`{destination_tx_hash: {_eq: $search}}`,
];
}
}
9 changes: 2 additions & 7 deletions src/features/messages/queries/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,5 @@ export interface MessageEntry extends MessageStubEntry {
num_payments: number;
}

export interface MessagesStubQueryResult {
message_view: MessageStubEntry[];
}

export interface MessagesQueryResult {
message_view: MessageEntry[];
}
export type MessagesStubQueryResult = Record<string, MessageStubEntry[]>;
export type MessagesQueryResult = Record<string, MessageEntry[]>;
16 changes: 10 additions & 6 deletions src/features/messages/queries/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,24 @@ export function parseMessageStubResult(
multiProvider: MultiProvider,
data: MessagesStubQueryResult | undefined,
): MessageStub[] {
if (!data?.message_view?.length) return [];
return data.message_view
if (!data || !Object.keys(data).length) return [];
return Object.values(data)
.flat()
.map((m) => parseMessageStub(multiProvider, m))
.filter((m): m is MessageStub => !!m);
.filter((m): m is MessageStub => !!m)
.sort((a, b) => b.origin.timestamp - a.origin.timestamp);
}

export function parseMessageQueryResult(
multiProvider: MultiProvider,
data: MessagesQueryResult | undefined,
): Message[] {
if (!data?.message_view?.length) return [];
return data.message_view
if (!data || !Object.keys(data).length) return [];
return Object.values(data)
.flat()
.map((m) => parseMessage(multiProvider, m))
.filter((m): m is Message => !!m);
.filter((m): m is Message => !!m)
.sort((a, b) => b.origin.timestamp - a.origin.timestamp);
}

function parseMessageStub(multiProvider: MultiProvider, m: MessageStubEntry): MessageStub | null {
Expand Down
34 changes: 0 additions & 34 deletions src/features/providers/SmartMultiProvider.ts

This file was deleted.

13 changes: 8 additions & 5 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk';
import { ChainMap, MultiProvider, chainMetadata } from '@hyperlane-xyz/sdk';

import { ChainConfig } from './features/chains/chainConfig';
import { buildSmartProvider } from './features/providers/SmartMultiProvider';
import { logger } from './utils/logger';

// Increment this when persist state has breaking changes
Expand All @@ -26,9 +25,9 @@ export const useStore = create<AppState>()(
(set) => ({
chainConfigs: {},
setChainConfigs: (configs: ChainMap<ChainConfig>) => {
set({ chainConfigs: configs, multiProvider: buildSmartProvider(configs) });
set({ chainConfigs: configs, multiProvider: buildMultiProvider(configs) });
},
multiProvider: buildSmartProvider({}),
multiProvider: buildMultiProvider({}),
setMultiProvider: (mp: MultiProvider) => {
set({ multiProvider: mp });
},
Expand All @@ -46,10 +45,14 @@ export const useStore = create<AppState>()(
logger.error('Error during hydration', error);
return;
}
state.setMultiProvider(buildSmartProvider(state.chainConfigs));
state.setMultiProvider(buildMultiProvider(state.chainConfigs));
logger.debug('Hydration finished');
};
},
},
),
);

function buildMultiProvider(customChainConfigs: ChainMap<ChainConfig>) {
return new MultiProvider({ ...chainMetadata, ...customChainConfigs });
}
Loading

0 comments on commit d0f80ad

Please sign in to comment.