Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[add] slashed validator data in the validator page #182

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 143 additions & 1 deletion packages/client/pages/validator/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ import { LargeTable, LargeTableHeader, LargeTableRow, SmallTable, SmallTableCard
import ShareMenu from '../../components/ui/ShareMenu';

// Types
import { Validator, Slot, Withdrawal } from '../../types';
import { Validator, Slot, Withdrawal, SlashedVal } from '../../types';
import LinkValidator from '../../components/ui/LinkValidator';
import { getShortAddress } from '../../helpers/addressHelper';
import { getTimeAgo } from '../../helpers/timeHelper';
import TooltipContainer from '../../components/ui/TooltipContainer';
import TooltipResponsive from '../../components/ui/TooltipResponsive';

// Props
interface Props {
Expand Down Expand Up @@ -66,12 +71,14 @@ const ValidatorComponent = ({ id, network }: Props) => {
const [validatorWeek, setValidatorWeek] = useState<Validator | null>(null);
const [proposedBlocks, setProposedBlocks] = useState<Slot[]>([]);
const [withdrawals, setWithdrawals] = useState<Withdrawal[]>([]);
const [slashedVals, setSlashedValidator] = useState<SlashedVal[]>([]);
const [showInfoBox, setShowInfoBox] = useState(false);
const [tabPageIndex, setTabPageIndex] = useState(0);
const [tabPageIndexValidatorPerformance, setTabPageIndexValidatorPerformance] = useState(0);
const [loadingValidator, setLoadingValidator] = useState(true);
const [loadingProposedBlocks, setLoadingProposedBlocks] = useState(true);
const [loadingWithdrawals, setLoadingWithdrawals] = useState(true);
const [loadingSlashedValidator, setLoadingSlashedValidator] = useState(true);
const [blockGenesis, setBlockGenesis] = useState(0);

// UseEffect
Expand All @@ -81,6 +88,7 @@ const ValidatorComponent = ({ id, network }: Props) => {
getValidator();
getProposedBlocks();
getWithdrawals();
getSlashedValidatorById();
}

// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -174,6 +182,24 @@ const ValidatorComponent = ({ id, network }: Props) => {
}
};

const getSlashedValidatorById = async () => {
try {
setLoadingSlashedValidator(true);

const response = await axiosClient.get(`/api/slashedValidators/${id}`, {
params: {
network,
},
});

setSlashedValidator(response.data.slashedValidatorResult);
} catch (error) {
console.log(error);
} finally {
setLoadingSlashedValidator(false);
}
};

const getContentValidator = () => (
<>
<div
Expand Down Expand Up @@ -300,6 +326,16 @@ const ValidatorComponent = ({ id, network }: Props) => {
} else {
return isLargeView ? getWithdrawalsLargeView() : getWithdrawalsSmallView();
}
case 2:
if (loadingSlashedValidator) {
return (
<div className='mt-6'>
<Loader />
</div>
);
} else {
return isLargeView ? getSlashedValidatorsLargeView() : getSlashedValidatorSmallView();
}
}
};

Expand Down Expand Up @@ -558,6 +594,107 @@ const ValidatorComponent = ({ id, network }: Props) => {
</SmallTable>
);

const getSlashedValidatorsLargeView = () => (
<LargeTable minWidth={700} fullWidth noRowsText='No Slashed Validators' fetchingRows={loadingSlashedValidator}>
<LargeTableHeader text='Slashed Validator' />
<LargeTableHeader text='Time' />
<LargeTableHeader text='Reason' />
<LargeTableHeader text='Slot' />
<LargeTableHeader text='Epoch' />

{slashedVals.map((slashedVal, idx) => (
<LargeTableRow key={idx}>
<div className="w-[20%] flex justify-center items-center ">
<LinkValidator validator={slashedVal.f_slashed_validator_index} />
</div>

<div className='w-[20%]'>
<TooltipContainer>
<div>
<p>{getTimeAgo(blockGenesis + slashedVal.f_slot * 12000)}</p>
</div>
<TooltipResponsive
width={120}
content={
<>
<p>{new Date(blockGenesis + slashedVal.f_slot * 12000).toLocaleDateString('ja-JP')}</p>
<p>{new Date(blockGenesis + slashedVal.f_slot * 12000).toLocaleTimeString('ja-JP')}</p>
</>
}
top='34px'
/>
</TooltipContainer>
</div>

<p className='w-[20%]'>
{slashedVal.f_slashing_reason.split(/(?=[A-Z])/).join(" ")}
</p>

<div className='w-[20%]'>
<LinkSlot slot={slashedVal.f_slot} mxAuto />
</div>

<div className='w-[20%]'>
<LinkEpoch epoch={slashedVal.f_epoch} mxAuto />
</div>
</LargeTableRow>
))}
</LargeTable>
);

// Slashed Validators Small View
const getSlashedValidatorSmallView = () => (
<SmallTable fullWidth noRowsText='No Slashed Validators' fetchingRows={loadingSlashedValidator}>
{slashedVals.map((slashedVal, idx) => (
<SmallTableCard key={idx}>

<div className='flex w-full items-center justify-between'>
<p className='font-semibold text-[var(--darkGray)] dark:text-[var(--white)]'>Slashed Validator:</p>
<div className=' md:hover:underline underline-offset-4 decoration-2 text-[var(--darkPurple)] dark:text-[var(--purple)]'>
<LinkValidator validator={slashedVal.f_slashed_validator_index} mxAuto />
</div>
</div>

<div className='flex w-full items-center justify-between'>
<p className='font-semibold text-[var(--darkGray)] dark:text-[var(--white)]'>Time:</p>
<div className='flex flex-col text-end'>
<TooltipContainer>
<div>
<p>{getTimeAgo(blockGenesis + slashedVal.f_slot * 12000)}</p>
</div>
<TooltipResponsive
width={120}
content={
<>
<p>{new Date(blockGenesis + slashedVal.f_slot * 12000).toLocaleDateString('ja-JP')}</p>
<p>{new Date(blockGenesis + slashedVal.f_slot * 12000).toLocaleTimeString('ja-JP')}</p>
</>
}
top='34px'
/>
</TooltipContainer>
</div>
</div>

<div className='flex w-full items-center justify-between'>
<p className='font-semibold text-[var(--darkGray)] dark:text-[var(--white)]'>Reason:</p>
<p>{slashedVal.f_slashing_reason.split(/(?=[A-Z])/).join(" ")}</p>
</div>

<div className='flex w-full items-center justify-between'>
<p className='font-semibold text-[var(--darkGray)] dark:text-[var(--white)]'>Slot:</p>
<LinkSlot slot={slashedVal.f_slot} />
</div>

<div className='flex w-full items-center justify-between'>
<p className='font-semibold text-[var(--darkGray)] dark:text-[var(--white)]'>Epoch:</p>
<LinkEpoch epoch={slashedVal.f_epoch} />
</div>
</SmallTableCard>
))}
</SmallTable>
);

//OVERVIEW PAGE
//Overview validator page
return (
Expand Down Expand Up @@ -592,6 +729,11 @@ const ValidatorComponent = ({ id, network }: Props) => {
isSelected={tabPageIndex === 1}
onClick={() => setTabPageIndex(1)}
/>
<TabHeader
header='Slashings'
isSelected={tabPageIndex === 2}
onClick={() => setTabPageIndex(2)}
/>
</div>

{getSelectedTab()}
Expand Down
39 changes: 39 additions & 0 deletions packages/server/controllers/slashedValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,42 @@ export const getSlashedVals = async (req: Request, res: Response) => {
});
}
};

export const getSlashedValsById = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { network } = req.query;

const clickhouseClient = clickhouseClients[network as string];

const [slashedValidatorResultSet] = await Promise.all([
clickhouseClient.query({
query: `
SELECT
f_slashed_validator_index,
f_slashing_reason,
f_slot,
f_epoch,
FROM
t_slashings
WHERE
f_slashed_by_validator_index = ${id}
ORDER BY
f_epoch DESC
`,
format: 'JSONEachRow',
})
]);

const slashedValidatorResult: any[] = await slashedValidatorResultSet.json();

res.json({
slashedValidatorResult,
});
} catch (error) {
console.log(error);
return res.status(500).json({
msg: 'An error occurred on the server',
});
}
};
13 changes: 12 additions & 1 deletion packages/server/routes/slashedValidators.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Router } from 'express';
import { check, query } from 'express-validator';
import { check, checkSchema, query } from 'express-validator';

import { checkFields } from '../middlewares/check-fields';

import {
getSlashedVals,
getSlashedValsById
} from '../controllers/slashedValidators';

import { existsNetwork } from '../helpers/network-validator';
Expand All @@ -12,6 +15,14 @@ const router = Router();
router.get('/', [
query('network').not().isEmpty(),
query('network').custom(existsNetwork),
checkFields
], getSlashedVals);

router.get('/:id', [
check('id').isInt({ min: 0, max: 2147483647 }),
query('network').not().isEmpty(),
query('network').custom(existsNetwork),
checkFields,
], getSlashedValsById);

export default router;