Commit c876efe0 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #735 from blockscout/blocks/consensus-lost

display error when block has lost consensus
parents be08fd58 6f85391d
export default function getErrorCause(error: Error | undefined): Record<string, unknown> | undefined {
return (
error && 'cause' in error &&
typeof error.cause === 'object' && error.cause !== null &&
error.cause as Record<string, unknown>
) ||
undefined;
}
import getErrorCause from './getErrorCause';
export default function getErrorStatusCode(error: Error | undefined): number | undefined { export default function getErrorStatusCode(error: Error | undefined): number | undefined {
return ( const cause = getErrorCause(error);
error && 'cause' in error && return cause && 'status' in cause && typeof cause.status === 'number' ? cause.status : undefined;
typeof error.cause === 'object' && error.cause !== null &&
'status' in error.cause && typeof error.cause.status === 'number' &&
error.cause.status
) ||
undefined;
} }
import type { ResourceError } from 'lib/api/resources';
import getErrorCause from './getErrorCause';
export default function getResourceErrorPayload<Payload = Record<string, unknown>>(error: Error | undefined): ResourceError<Payload>['payload'] | undefined {
const cause = getErrorCause(error);
return cause && 'payload' in cause ? cause.payload as ResourceError<Payload>['payload'] : undefined;
}
...@@ -5,6 +5,7 @@ import React from 'react'; ...@@ -5,6 +5,7 @@ import React from 'react';
import getSeo from 'lib/next/block/getSeo'; import getSeo from 'lib/next/block/getSeo';
import Block from 'ui/pages/Block'; import Block from 'ui/pages/Block';
import Page from 'ui/shared/Page/Page';
const BlockPage: NextPage<RoutedQuery<'/block/[height]'>> = ({ height }: RoutedQuery<'/block/[height]'>) => { const BlockPage: NextPage<RoutedQuery<'/block/[height]'>> = ({ height }: RoutedQuery<'/block/[height]'>) => {
const { title, description } = getSeo({ height }); const { title, description } = getSeo({ height });
...@@ -14,7 +15,9 @@ const BlockPage: NextPage<RoutedQuery<'/block/[height]'>> = ({ height }: RoutedQ ...@@ -14,7 +15,9 @@ const BlockPage: NextPage<RoutedQuery<'/block/[height]'>> = ({ height }: RoutedQ
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Block/> <Page>
<Block/>
</Page>
</> </>
); );
}; };
......
...@@ -11,7 +11,6 @@ import useQueryWithPages from 'lib/hooks/useQueryWithPages'; ...@@ -11,7 +11,6 @@ import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import BlockDetails from 'ui/block/BlockDetails'; import BlockDetails from 'ui/block/BlockDetails';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
...@@ -39,7 +38,7 @@ const BlockPageContent = () => { ...@@ -39,7 +38,7 @@ const BlockPageContent = () => {
resourceName: 'block_txs', resourceName: 'block_txs',
pathParams: { height }, pathParams: { height },
options: { options: {
enabled: Boolean(height && tab === 'txs'), enabled: Boolean(blockQuery.data?.height && tab === 'txs'),
}, },
}); });
...@@ -47,6 +46,10 @@ const BlockPageContent = () => { ...@@ -47,6 +46,10 @@ const BlockPageContent = () => {
throw new Error('Block not found', { cause: { status: 404 } }); throw new Error('Block not found', { cause: { status: 404 } });
} }
if (blockQuery.isError) {
throw new Error(undefined, { cause: blockQuery.error });
}
const tabs: Array<RoutedTab> = React.useMemo(() => ([ const tabs: Array<RoutedTab> = React.useMemo(() => ([
{ id: 'index', title: 'Details', component: <BlockDetails query={ blockQuery }/> }, { id: 'index', title: 'Details', component: <BlockDetails query={ blockQuery }/> },
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> }, { id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> },
...@@ -57,7 +60,7 @@ const BlockPageContent = () => { ...@@ -57,7 +60,7 @@ const BlockPageContent = () => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/blocks'); const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/blocks');
return ( return (
<Page> <>
{ blockQuery.isLoading ? <Skeleton h={{ base: 12, lg: 6 }} mb={ 6 } w="100%" maxW="680px"/> : <TextAd mb={ 6 }/> } { blockQuery.isLoading ? <Skeleton h={{ base: 12, lg: 6 }} mb={ 6 } w="100%" maxW="680px"/> : <TextAd mb={ 6 }/> }
{ blockQuery.isLoading ? ( { blockQuery.isLoading ? (
<Skeleton h={ 10 } w="300px" mb={ 6 }/> <Skeleton h={ 10 } w="300px" mb={ 6 }/>
...@@ -74,7 +77,7 @@ const BlockPageContent = () => { ...@@ -74,7 +77,7 @@ const BlockPageContent = () => {
rightSlot={ hasPagination ? <Pagination { ...blockTxsQuery.pagination }/> : null } rightSlot={ hasPagination ? <Pagination { ...blockTxsQuery.pagination }/> : null }
stickyEnabled={ hasPagination } stickyEnabled={ hasPagination }
/> />
</Page> </>
); );
}; };
......
import { Box, Button, Heading, Icon, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import icon404 from 'icons/error-pages/404.svg';
interface Props {
hash?: string;
className?: string;
}
const AppErrorBlockConsensus = ({ hash, className }: Props) => {
return (
<Box className={ className }>
<Icon as={ icon404 } width="200px" height="auto"/>
<Heading mt={ 8 } size="2xl" fontFamily="body">Block has lost consensus</Heading>
<Button
mt={ 8 }
size="lg"
variant="outline"
as="a"
href={ hash ? route({ pathname: '/block/[height]', query: { height: hash } }) : route({ pathname: '/' }) }
>
{ hash ? 'View reorg' : 'Back to home' }
</Button>
</Box>
);
};
export default chakra(AppErrorBlockConsensus);
...@@ -2,9 +2,11 @@ import { Flex } from '@chakra-ui/react'; ...@@ -2,9 +2,11 @@ import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getErrorStatusCode from 'lib/errors/getErrorStatusCode'; import getErrorStatusCode from 'lib/errors/getErrorStatusCode';
import getResourceErrorPayload from 'lib/errors/getResourceErrorPayload';
import useAdblockDetect from 'lib/hooks/useAdblockDetect'; import useAdblockDetect from 'lib/hooks/useAdblockDetect';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import AppError from 'ui/shared/AppError/AppError'; import AppError from 'ui/shared/AppError/AppError';
import AppErrorBlockConsensus from 'ui/shared/AppError/AppErrorBlockConsensus';
import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorBoundary from 'ui/shared/ErrorBoundary';
import ErrorInvalidTxHash from 'ui/shared/ErrorInvalidTxHash'; import ErrorInvalidTxHash from 'ui/shared/ErrorInvalidTxHash';
import PageContent from 'ui/shared/Page/PageContent'; import PageContent from 'ui/shared/Page/PageContent';
...@@ -31,15 +33,27 @@ const Page = ({ ...@@ -31,15 +33,27 @@ const Page = ({
const renderErrorScreen = React.useCallback((error?: Error) => { const renderErrorScreen = React.useCallback((error?: Error) => {
const statusCode = getErrorStatusCode(error) || 500; const statusCode = getErrorStatusCode(error) || 500;
const resourceErrorPayload = getResourceErrorPayload(error);
const messageInPayload = resourceErrorPayload && 'message' in resourceErrorPayload && typeof resourceErrorPayload.message === 'string' ?
resourceErrorPayload.message :
undefined;
const isInvalidTxHash = error?.message.includes('Invalid tx hash'); const isInvalidTxHash = error?.message.includes('Invalid tx hash');
const isBlockConsensus = messageInPayload?.includes('Block lost consensus');
if (isInvalidTxHash) {
return <PageContent isHomePage={ isHomePage }><ErrorInvalidTxHash/></PageContent>;
}
if (wrapChildren) { if (isBlockConsensus) {
const content = isInvalidTxHash ? <ErrorInvalidTxHash/> : <AppError statusCode={ statusCode } mt="50px"/>; const hash = resourceErrorPayload && 'hash' in resourceErrorPayload && typeof resourceErrorPayload.hash === 'string' ?
return <PageContent isHomePage={ isHomePage }>{ content }</PageContent>; resourceErrorPayload.hash :
undefined;
return <PageContent isHomePage={ isHomePage }><AppErrorBlockConsensus hash={ hash } mt="50px"/></PageContent>;
} }
return isInvalidTxHash ? <ErrorInvalidTxHash/> : <AppError statusCode={ statusCode }/>; return <PageContent isHomePage={ isHomePage }><AppError statusCode={ statusCode } mt="50px"/></PageContent>;
}, [ isHomePage, wrapChildren ]); }, [ isHomePage ]);
const renderedChildren = wrapChildren ? ( const renderedChildren = wrapChildren ? (
<PageContent isHomePage={ isHomePage }>{ children }</PageContent> <PageContent isHomePage={ isHomePage }>{ children }</PageContent>
......
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