Commit a2bb162e authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #2150 from blockscout/fe-2098

Arbitrum DA fields update
parents bc62c5c1 0cbc1fdc
......@@ -11,6 +11,7 @@ on:
options:
- none
- arbitrum
- arbitrum_nova
- base
- celo_alfajores
- garnet
......
......@@ -11,6 +11,7 @@ on:
options:
- none
- arbitrum
- arbitrum_nova
- base
- celo_alfajores
- garnet
......
# Set of ENVs for Arbitrum One network explorer
# https://arbitrum.blockscout.com
# This is an auto-generated file. To update all values, run "yarn preset:sync --name=arbitrum"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=arbitrum-nova.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x37c798810d49ba132b40efe7f4fdf6806a8fc58226bb5e185ddc91f896577abf
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(27, 74, 221, 1)
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-arbitrum.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/arbitrum/pools'}}]
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-nova-icon.svg
NEXT_PUBLIC_NETWORK_ID=42170
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-nova.svg
NEXT_PUBLIC_NETWORK_NAME=Arbitrum Nova
NEXT_PUBLIC_NETWORK_RPC_URL=https://arbitrum.llamarpc.com
NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum Nova
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/arbitrum-nova.png
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com
NEXT_PUBLIC_ROLLUP_TYPE=arbitrum
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
/* eslint-disable max-len */
import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2';
import { finalized } from './txnBatches';
......@@ -8,4 +9,32 @@ export const batchData: ArbitrumL2TxnBatch = {
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
start_block: 1245209,
end_block: 1245490,
data_availability: {
batch_data_container: 'in_blob4844',
},
};
export const batchDataAnytrust: ArbitrumL2TxnBatch = {
...finalized,
after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb',
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
start_block: 1245209,
end_block: 1245490,
data_availability: {
batch_data_container: 'in_anytrust',
bls_signature: '0x142577943e30b1ad1b4e40a1c08e00c24a68d6c366f953e361048b7127e327b5bdb8f168ba986beae40cfaf79ea2788004d750555684751e361d6f6445e5c521b45ac93a76da24add241a4a5410ca3a09fa82cf0aafd78801cbd0ad99d5be6b3',
data_hash: '0x4ffada101d8185bcba227f2cff9e0ea0a4deeb08f328601a898131429a436ebe',
timeout: '2024-08-22T12:39:22Z',
signers: [
{
key: '0x0c6694955b524d718ca445831c5375393773401f33725a79661379dddabd5fff28619dc070befd9ed73d699e5c236c1a163be58ba81002b6130709bc064af5d7ba947130b72056bf17263800f1a3ab2269c6a510ef8e7412fd56d1ef1b916a1306e3b1d9c82c099371bd9861582acaada3a16e9dfee5d0ebce61096598a82f112d0a935e8cab5c48d82e3104b0c7ba79157dad1a019a3e7f6ad077b8e6308b116fec0f58239622463c3631fa01e2b4272409215b8009422c16715dbede590906',
proof: '0x06dcb5e56764bb72e6a45e6deb301ca85d8c4315c1da2efa29927f2ac8fb25571ce31d2d603735fe03196f6d56bcbf9a1999a89a74d5369822c4445d676c15ed52e5008daa775dc9a839c99ff963a19946ac740579874dac4f639907ae1bc69f',
trusted: false,
},
{
key: '0x0ee5aaeabd57313285207eb89366b411286cf3f1c5e30eb7e355f55385308b91d5807284323ee89a9743c70676f4949504ced3ed41612cbfda06ad55200c1c77d3fb3700059befd64c44bc4a57cb567ec1481ee564cf6cd6cf1f2f4a2dee6db00c547c38400ab118dedae8afd5bab93b703f76a0991baa5d43fbb125194c06b5461f8c738a3c4278a3d98e5456aec0720883c0d28919537a36e2ffd5f731e742b6653557d154c164e068ef983b367ef626faaed46f4eadecbb12b7e55f23175d',
trusted: true,
},
],
},
};
......@@ -10,6 +10,7 @@ export const finalized: ArbitrumL2TxnBatchesItem = {
hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661',
status: 'finalized',
},
batch_data_container: 'in_blob4844',
};
export const unfinalized: ArbitrumL2TxnBatchesItem = {
......@@ -22,6 +23,8 @@ export const unfinalized: ArbitrumL2TxnBatchesItem = {
hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661',
status: 'unfinalized',
},
batch_data_container: null,
};
export const baseResponse: ArbitrumL2TxnBatchesResponse = {
......
......@@ -23,6 +23,7 @@ export const ARBITRUM_L2_TXN_BATCHES_ITEM: ArbitrumL2TxnBatchesItem = {
hash: TX_HASH,
status: 'finalized',
},
batch_data_container: 'in_blob4844',
};
export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = {
......@@ -31,4 +32,7 @@ export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = {
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
start_block: 1245209,
end_block: 1245490,
data_availability: {
batch_data_container: 'in_blob4844',
},
};
......@@ -33,11 +33,14 @@ type ArbitrumL2BatchCommitmentTx = {
timestamp: string;
}
type BatchDataContainer = 'in_blob4844' | 'in_calldata' | 'in_anytrust' | 'in_celestia' | null;
export type ArbitrumL2TxnBatchesItem = {
blocks_count: number;
commitment_transaction: ArbitrumL2BatchCommitmentTx;
number: number;
transactions_count: number;
batch_data_container: BatchDataContainer;
}
export type ArbitrumL2TxnBatchesResponse = {
......@@ -48,6 +51,22 @@ export type ArbitrumL2TxnBatchesResponse = {
} | null;
}
export type ArbitrumL2TxnBatchDAAnytrust = {
batch_data_container: 'in_anytrust';
bls_signature: string;
data_hash: string;
timeout: string;
signers: Array<{
key: string;
trusted: boolean;
proof?: string;
}>;
}
export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | {
batch_data_container: Exclude<BatchDataContainer, 'in_anytrust'>;
}
export type ArbitrumL2TxnBatch = {
after_acc: string;
before_acc: string;
......@@ -56,6 +75,7 @@ export type ArbitrumL2TxnBatch = {
start_block: number;
number: number;
transactions_count: number;
data_availability: ArbitrumL2TxnBatchDataAvailability;
}
export type ArbitrumL2BatchTxs = {
......
import React from 'react';
import { batchData } from 'mocks/arbitrum/txnBatch';
import { batchData, batchDataAnytrust } from 'mocks/arbitrum/txnBatch';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect, devices } from 'playwright/lib';
......@@ -13,21 +13,36 @@ const hooksConfig = {
},
};
test.beforeEach(async({ mockTextAd, mockApiResponse, mockEnvs }) => {
test.beforeEach(async({ mockTextAd, mockEnvs }) => {
await mockEnvs(ENVS_MAP.arbitrumRollup);
await mockTextAd();
});
test('base view', async({ render, mockApiResponse }) => {
await mockApiResponse('arbitrum_l2_txn_batch', batchData, { pathParams: { number: batchNumber } });
const component = await render(<ArbitrumL2TxnBatch/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
test('base view', async({ render }) => {
test('with anytrust DA', async({ render, mockApiResponse }) => {
await mockApiResponse('arbitrum_l2_txn_batch', batchDataAnytrust, { pathParams: { number: batchNumber } });
const component = await render(<ArbitrumL2TxnBatch/>, { hooksConfig });
await component.getByText('Show data availability info').click();
await expect(component).toHaveScreenshot();
});
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ render }) => {
test('base view', async({ render, mockApiResponse }) => {
await mockApiResponse('arbitrum_l2_txn_batch', batchData, { pathParams: { number: batchNumber } });
const component = await render(<ArbitrumL2TxnBatch/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
test('with anytrust DA', async({ render, mockApiResponse }) => {
await mockApiResponse('arbitrum_l2_txn_batch', batchDataAnytrust, { pathParams: { number: batchNumber } });
const component = await render(<ArbitrumL2TxnBatch/>, { hooksConfig });
await component.getByText('Show data availability info').click();
await expect(component).toHaveScreenshot();
});
});
import { Skeleton, Tag } from '@chakra-ui/react';
import React from 'react';
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
export interface Props {
dataContainer: ArbitrumL2TxnBatchesItem['batch_data_container'];
isLoading?: boolean;
}
const ArbitrumL2TxnBatchDA = ({ dataContainer, isLoading }: Props) => {
let text: string;
if (dataContainer === null) {
return null;
}
switch (dataContainer) {
case 'in_blob4844':
text = 'blob';
break;
case 'in_anytrust':
text = 'anytrust';
break;
case 'in_calldata':
text = 'calldata';
break;
case 'in_celestia':
text = 'celestia';
break;
default:
text = '';
}
if (!text) {
return null;
}
return (
<Skeleton isLoaded={ !isLoading }>
<Tag colorScheme={ dataContainer === 'in_blob4844' ? 'yellow' : 'gray' }>
{ text }
</Tag>
</Skeleton>
);
};
export default ArbitrumL2TxnBatchDA;
import { Grid, Skeleton } from '@chakra-ui/react';
import { Grid, GridItem, Link, Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2';
......@@ -10,6 +11,7 @@ import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import ArbitrumL2TxnBatchDA from 'ui/shared/batch/ArbitrumL2TxnBatchDA';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
......@@ -20,12 +22,22 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/links/LinkInternal';
import PrevNext from 'ui/shared/PrevNext';
import ArbitrumL2TxnBatchDetailsDA from './ArbitrumL2TxnBatchDetailsDA';
interface Props {
query: UseQueryResult<ArbitrumL2TxnBatch, ResourceError>;
}
const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
const router = useRouter();
const [ isExpanded, setIsExpanded ] = React.useState(false);
const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag);
scroller.scrollTo('BatchDetails__cutLink', {
duration: 500,
smooth: true,
});
}, []);
const { data, isPlaceholderData, isError, error } = query;
......@@ -149,6 +161,19 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
/>
</DetailsInfoItem.Value>
{ data.data_availability.batch_data_container && (
<>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
hint="Where the batch data is stored"
>
Batch data container
</DetailsInfoItem.Label><DetailsInfoItem.Value>
<ArbitrumL2TxnBatchDA dataContainer={ data.data_availability.batch_data_container } isLoading={ isPlaceholderData }/>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
hint="The hash of the state before the batch"
......@@ -174,6 +199,35 @@ const ArbitrumL2TxnBatchDetails = ({ query }: Props) => {
<CopyToClipboard text={ data.after_acc }/>
</Skeleton>
</DetailsInfoItem.Value>
{ data.data_availability.batch_data_container === 'in_anytrust' && (
<>
{ /* CUT */ }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Element name="BatchDetails__cutLink">
<Skeleton isLoaded={ !isPlaceholderData } mt={ 6 } display="inline-block">
<Link
fontSize="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ handleCutClick }
>
{ isExpanded ? 'Hide data availability info' : 'Show data availability info' }
</Link>
</Skeleton>
</Element>
</GridItem>
{ /* ADDITIONAL INFO */ }
{ isExpanded && !isPlaceholderData && (
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<ArbitrumL2TxnBatchDetailsDA dataAvailability={ data.data_availability }/>
</>
) }
</>
) }
</Grid>
);
};
......
import { Grid, Text, Flex, Box, useColorModeValue, Show, Hide, VStack } from '@chakra-ui/react';
import React from 'react';
import type { ArbitrumL2TxnBatchDAAnytrust } from 'types/api/arbitrumL2';
import dayjs from 'lib/date/dayjs';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import HashStringShorten from 'ui/shared/HashStringShorten';
import IconSvg from 'ui/shared/IconSvg';
import TextSeparator from 'ui/shared/TextSeparator';
type Props = {
dataAvailability: ArbitrumL2TxnBatchDAAnytrust;
}
const ArbitrumL2TxnBatchDetailsDA = ({ dataAvailability }: Props) => {
const signersBg = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<>
<DetailsInfoItem.Label
hint="Aggregated BLS signature of AnyTrust committee members"
>
Signature
</DetailsInfoItem.Label><DetailsInfoItem.Value wordBreak="break-all" whiteSpace="break-spaces">
{ dataAvailability.bls_signature }
</DetailsInfoItem.Value><DetailsInfoItem.Label
hint="The hash of the data blob stored by the AnyTrust committee"
>
Data hash
</DetailsInfoItem.Label><DetailsInfoItem.Value>
{ dataAvailability.data_hash }
<CopyToClipboard text={ dataAvailability.data_hash } ml={ 2 }/>
</DetailsInfoItem.Value><DetailsInfoItem.Label
hint="Expiration timeout for the data blob"
>
Timeout
</DetailsInfoItem.Label><DetailsInfoItem.Value>
{ dayjs(dataAvailability.timeout) < dayjs() ?
<DetailsTimestamp timestamp={ dataAvailability.timeout }/> :
(
<>
<Text>{ dayjs(dataAvailability.timeout).format('llll') }</Text>
<TextSeparator color="gray.500"/>
<Text color="red.500">{ dayjs(dataAvailability.timeout).diff(dayjs(), 'day') } days left</Text>
</>
) }
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Members of AnyTrust committee"
>
Signers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value overflowX="scroll" fontSize="sm">
<Show above="lg" ssr={ false }>
<Grid
templateColumns="1fr auto auto"
gap={ 5 }
backgroundColor={ signersBg }
padding={ 4 }
borderRadius="md"
minW="600px"
>
<Text fontWeight={ 600 }>Key</Text>
<Text fontWeight={ 600 }>Trusted</Text>
<Text fontWeight={ 600 }>Proof</Text>
{ dataAvailability.signers.map(signer => (
<>
<Flex justifyContent="space-between">
<Text wordBreak="break-all" whiteSpace="break-spaces">{ signer.key }</Text>
<CopyToClipboard text={ signer.key } ml={ 2 }/>
</Flex>
<Box justifySelf="center">
{ signer.trusted ? <IconSvg name="check" boxSize={ 6 }/> : <IconSvg name="cross" boxSize={ 6 }/> }
</Box>
{ signer.proof ? (
<Flex>
<HashStringShorten hash={ signer.proof }/>
<CopyToClipboard text={ signer.proof } ml={ 2 }/>
</Flex>
) : '-' }
</>
)) }
</Grid>
</Show>
<Hide above="lg" ssr={ false }>
<Box backgroundColor={ signersBg } borderRadius="md">
{ dataAvailability.signers.map(signer => (
<VStack padding={ 4 } key={ signer.key } gap={ 2 }>
<Flex w="100%" justifyContent="space-between">
<Text fontWeight={ 600 }>Key</Text>
<CopyToClipboard text={ signer.key }/>
</Flex>
<Text wordBreak="break-all" whiteSpace="break-spaces">{ signer.key }</Text>
<Flex w="100%" alignItems="center">
<Flex alignItems="center" w="50%">
<Text fontWeight={ 600 } mr={ 2 }>Trusted</Text>
{ signer.trusted ? <IconSvg name="check" boxSize={ 6 }/> : <IconSvg name="cross" boxSize={ 6 }/> }
</Flex>
<Flex alignItems="center" w="50%">
<Text fontWeight={ 600 } mr={ 2 }>Proof</Text>
{ signer.proof ? (
<Flex>
<HashStringShorten hash={ signer.proof }/>
<CopyToClipboard text={ signer.proof } ml={ 2 }/>
</Flex>
) : '-' }
</Flex>
</Flex>
</VStack>
)) }
</Box>
</Hide>
</DetailsInfoItem.Value>
</>
);
};
export default ArbitrumL2TxnBatchDetailsDA;
......@@ -6,6 +6,7 @@ import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import ArbitrumL2TxnBatchDA from 'ui/shared/batch/ArbitrumL2TxnBatchDA';
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -95,6 +96,11 @@ const ArbitrumL2TxnBatchesListItem = ({ item, isLoading }: Props) => {
</LinkInternal>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Data container</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<ArbitrumL2TxnBatchDA dataContainer={ item.batch_data_container } isLoading={ isLoading }/>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
......
import { Td, Tr, Skeleton } from '@chakra-ui/react';
import { Td, Tr, Skeleton, HStack } from '@chakra-ui/react';
import React from 'react';
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
......@@ -6,6 +6,7 @@ import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import ArbitrumL2TxnBatchDA from 'ui/shared/batch/ArbitrumL2TxnBatchDA';
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -35,7 +36,10 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
/>
</Td>
<Td verticalAlign="middle">
<HStack gap={ 1 }>
<ArbitrumL2TxnBatchStatus status={ item.commitment_transaction.status } isLoading={ isLoading }/>
<ArbitrumL2TxnBatchDA dataContainer={ item.batch_data_container } isLoading={ isLoading }/>
</HStack>
</Td>
<Td verticalAlign="middle">
<BlockEntityL1
......
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