Commit 3c0727cb authored by tom goriunov's avatar tom goriunov Committed by GitHub

Socket updates: on-demand contract verification fix, indexing event rename (#2629)

* Contract verification not appearing as completed following the message from websocket

Fixes #2618

* fix tests

* Change indexing status event name

* Replace `optimism_deposits:new_deposits` socket topic with `optimism:new_deposits`
parent 82f49a49
...@@ -49,13 +49,13 @@ interface SocketMessageParamsGeneric<Event extends string | undefined, Payload e ...@@ -49,13 +49,13 @@ interface SocketMessageParamsGeneric<Event extends string | undefined, Payload e
export namespace SocketMessage { export namespace SocketMessage {
export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>; export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>;
export type BlocksIndexStatus = SocketMessageParamsGeneric<'block_index_status', { finished: boolean; ratio: string }>; export type BlocksIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>;
export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'internal_txs_index_status', { finished: boolean; ratio: string }>; export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>;
export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>; export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>;
export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>; export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type NewOptimisticDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; export type NewOptimisticDeposits = SocketMessageParamsGeneric<'new_optimism_deposits', { deposits: number }>;
export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>; export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
export type AddressCurrentCoinBalance = export type AddressCurrentCoinBalance =
......
...@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; ...@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import { useSocket } from './context'; import { useSocket } from './context';
const CHANNEL_REGISTRY: Record<string, Channel> = {}; const CHANNEL_REGISTRY: Record<string, { channel: Channel; subscribers: number }> = {};
interface Params { interface Params {
topic: string | undefined; topic: string | undefined;
...@@ -53,11 +53,12 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on ...@@ -53,11 +53,12 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
let ch: Channel; let ch: Channel;
if (CHANNEL_REGISTRY[topic]) { if (CHANNEL_REGISTRY[topic]) {
ch = CHANNEL_REGISTRY[topic]; ch = CHANNEL_REGISTRY[topic].channel;
CHANNEL_REGISTRY[topic].subscribers++;
onJoinRef.current?.(ch, ''); onJoinRef.current?.(ch, '');
} else { } else {
ch = socket.channel(topic); ch = socket.channel(topic);
CHANNEL_REGISTRY[topic] = ch; CHANNEL_REGISTRY[topic] = { channel: ch, subscribers: 1 };
ch.join() ch.join()
.receive('ok', (message) => onJoinRef.current?.(ch, message)) .receive('ok', (message) => onJoinRef.current?.(ch, message))
.receive('error', () => { .receive('error', () => {
...@@ -68,8 +69,14 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on ...@@ -68,8 +69,14 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
setChannel(ch); setChannel(ch);
return () => { return () => {
ch.leave(); if (CHANNEL_REGISTRY[topic]) {
delete CHANNEL_REGISTRY[topic]; CHANNEL_REGISTRY[topic].subscribers > 0 && CHANNEL_REGISTRY[topic].subscribers--;
if (CHANNEL_REGISTRY[topic].subscribers === 0) {
ch.leave();
delete CHANNEL_REGISTRY[topic];
}
}
setChannel(undefined); setChannel(undefined);
}; };
}, [ socket, topic, params, isDisabled, onSocketError ]); }, [ socket, topic, params, isDisabled, onSocketError ]);
......
...@@ -40,7 +40,8 @@ test.describe('full view', () => { ...@@ -40,7 +40,8 @@ test.describe('full view', () => {
}, },
}; };
const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
await createSocket(); const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -51,7 +52,8 @@ test.describe('full view', () => { ...@@ -51,7 +52,8 @@ test.describe('full view', () => {
}, },
}; };
const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
await createSocket(); const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -62,7 +64,8 @@ test.describe('full view', () => { ...@@ -62,7 +64,8 @@ test.describe('full view', () => {
}, },
}; };
const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
await createSocket(); const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -73,7 +76,8 @@ test.describe('full view', () => { ...@@ -73,7 +76,8 @@ test.describe('full view', () => {
}, },
}; };
const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
await createSocket(); const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
}); });
...@@ -85,7 +89,8 @@ test.describe('mobile view', () => { ...@@ -85,7 +89,8 @@ test.describe('mobile view', () => {
await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.hash } });
await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.implementations?.[0].address as string } }); await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.implementations?.[0].address as string } });
const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
await createSocket(); const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
}); });
...@@ -95,7 +100,7 @@ test('verified via lookup in eth_bytecode_db', async({ render, mockApiResponse, ...@@ -95,7 +100,7 @@ test('verified via lookup in eth_bytecode_db', async({ render, mockApiResponse,
await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket(); const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'addresses:' + addressMock.contract.hash.toLowerCase()); const channel = await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await page.waitForResponse(contractApiUrl); await page.waitForResponse(contractApiUrl);
socketServer.sendMessage(socket, channel, 'smart_contract_was_verified', {}); socketServer.sendMessage(socket, channel, 'smart_contract_was_verified', {});
const request = await page.waitForRequest(addressApiUrl); const request = await page.waitForRequest(addressApiUrl);
...@@ -103,9 +108,11 @@ test('verified via lookup in eth_bytecode_db', async({ render, mockApiResponse, ...@@ -103,9 +108,11 @@ test('verified via lookup in eth_bytecode_db', async({ render, mockApiResponse,
expect(request).toBeTruthy(); expect(request).toBeTruthy();
}); });
test('verified with multiple sources', async({ render, page, mockApiResponse }) => { test('verified with multiple sources', async({ render, page, mockApiResponse, createSocket }) => {
await mockApiResponse('contract', contractMock.withMultiplePaths, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('contract', contractMock.withMultiplePaths, { pathParams: { hash: addressMock.contract.hash } });
await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
const section = page.locator('section', { hasText: 'Contract source code' }); const section = page.locator('section', { hasText: 'Contract source code' });
await expect(section).toHaveScreenshot(); await expect(section).toHaveScreenshot();
...@@ -117,7 +124,7 @@ test('verified with multiple sources', async({ render, page, mockApiResponse }) ...@@ -117,7 +124,7 @@ test('verified with multiple sources', async({ render, page, mockApiResponse })
await expect(section).toHaveScreenshot(); await expect(section).toHaveScreenshot();
}); });
test('self destructed', async({ render, mockApiResponse, page }) => { test('self destructed', async({ render, mockApiResponse, page, createSocket }) => {
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { hash: addressMock.contract.hash, tab: 'contract_bytecode' }, query: { hash: addressMock.contract.hash, tab: 'contract_bytecode' },
...@@ -125,15 +132,19 @@ test('self destructed', async({ render, mockApiResponse, page }) => { ...@@ -125,15 +132,19 @@ test('self destructed', async({ render, mockApiResponse, page }) => {
}; };
await mockApiResponse('contract', contractMock.selfDestructed, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('contract', contractMock.selfDestructed, { pathParams: { hash: addressMock.contract.hash } });
await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
const section = page.locator('section', { hasText: 'Contract creation code' }); const section = page.locator('section', { hasText: 'Contract creation code' });
await expect(section).toHaveScreenshot(); await expect(section).toHaveScreenshot();
}); });
test('non verified', async({ render, mockApiResponse }) => { test('non verified', async({ render, mockApiResponse, createSocket }) => {
await mockApiResponse('address', { ...addressMock.contract, name: null }, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('address', { ...addressMock.contract, name: null }, { pathParams: { hash: addressMock.contract.hash } });
await mockApiResponse('contract', contractMock.nonVerified, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('contract', contractMock.nonVerified, { pathParams: { hash: addressMock.contract.hash } });
const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true }); const component = await render(<ContractDetails/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -57,7 +57,7 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) => ...@@ -57,7 +57,7 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) =>
const contractQuery = useApiQuery('contract', { const contractQuery = useApiQuery('contract', {
pathParams: { hash: selectedItem?.address }, pathParams: { hash: selectedItem?.address },
queryOptions: { queryOptions: {
enabled: Boolean(selectedItem?.address), enabled: Boolean(selectedItem?.address && !mainContractQuery.isPlaceholderData),
refetchOnMount: false, refetchOnMount: false,
placeholderData: addressInfo?.is_verified ? stubs.CONTRACT_CODE_VERIFIED : stubs.CONTRACT_CODE_UNVERIFIED, placeholderData: addressInfo?.is_verified ? stubs.CONTRACT_CODE_VERIFIED : stubs.CONTRACT_CODE_UNVERIFIED,
}, },
......
...@@ -37,7 +37,7 @@ const LatestOptimisticDeposits = () => { ...@@ -37,7 +37,7 @@ const LatestOptimisticDeposits = () => {
}, [ setNum ]); }, [ setNum ]);
const channel = useSocketChannel({ const channel = useSocketChannel({
topic: 'optimism_deposits:new_deposits', topic: 'optimism:new_deposits',
onSocketClose: handleSocketClose, onSocketClose: handleSocketClose,
onSocketError: handleSocketError, onSocketError: handleSocketError,
isDisabled: false, isDisabled: false,
...@@ -45,7 +45,7 @@ const LatestOptimisticDeposits = () => { ...@@ -45,7 +45,7 @@ const LatestOptimisticDeposits = () => {
useSocketMessage({ useSocketMessage({
channel, channel,
event: 'deposits', event: 'new_optimism_deposits',
handler: handleNewDepositMessage, handler: handleNewDepositMessage,
}); });
......
...@@ -39,7 +39,7 @@ const IntTxsIndexingStatus = () => { ...@@ -39,7 +39,7 @@ const IntTxsIndexingStatus = () => {
useSocketMessage({ useSocketMessage({
channel: internalTxsIndexingChannel, channel: internalTxsIndexingChannel,
event: 'internal_txs_index_status', event: 'index_status',
handler: handleInternalTxsIndexStatus, handler: handleInternalTxsIndexStatus,
}); });
......
...@@ -51,7 +51,7 @@ const IndexingBlocksAlert = () => { ...@@ -51,7 +51,7 @@ const IndexingBlocksAlert = () => {
useSocketMessage({ useSocketMessage({
channel: blockIndexingChannel, channel: blockIndexingChannel,
event: 'block_index_status', event: 'index_status',
handler: handleBlocksIndexStatus, handler: handleBlocksIndexStatus,
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment