Skip to content

Commit

Permalink
fix: upgrade page (#181)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Félix C. Morency <[email protected]>
Co-authored-by: Félix C. Morency <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 8, 2025
1 parent 12555f6 commit fe9bf87
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 13 deletions.
93 changes: 81 additions & 12 deletions components/admins/modals/upgradeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { cosmos } from '@liftedinit/manifestjs';
import { useTx, useFeeEstimation, useGitHubReleases, GitHubRelease } from '@/hooks';
import { useTx, useFeeEstimation, useGitHubReleases, GitHubRelease, useBlockHeight } from '@/hooks';
import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any';
import { MsgSoftwareUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx';
import { Formik, Form } from 'formik';
Expand All @@ -27,18 +27,19 @@ interface UpgradeInfo {

const parseReleaseBody = (body: string): UpgradeInfo | null => {
try {
const nameMatch = body.match(/\*\*Upgrade Handler Name\*\*:\s*`([^`]+)`/);
const upgradeableMatch = body.match(/\*\*Upgradeable\*\*:\s*`([^`]+)`/);
const commitHashMatch = body.match(/\*\*Commit Hash\*\*:\s*`([^`]+)`/);
const nameMatch = body.match(/- \*\*Upgrade Handler Name\*\*: (.*?)(?:\r?\n|$)/);
const upgradeableMatch = body.match(/- \*\*Upgradeable\*\*: (.*?)(?:\r?\n|$)/);
const commitHashMatch = body.match(/- \*\*Commit Hash\*\*: (.*?)(?:\r?\n|$)/);

if (!nameMatch || !upgradeableMatch || !commitHashMatch) {
console.warn('Failed matches:', { nameMatch, upgradeableMatch, commitHashMatch });
return null;
}

return {
name: nameMatch[1],
upgradeable: upgradeableMatch[1].toLowerCase() === 'true',
commitHash: commitHashMatch[1],
name: nameMatch[1].trim(),
upgradeable: upgradeableMatch[1].trim().toLowerCase() === 'true',
commitHash: commitHashMatch[1].trim(),
};
} catch (error) {
console.error('Error parsing release body:', error);
Expand All @@ -47,13 +48,34 @@ const parseReleaseBody = (body: string): UpgradeInfo | null => {
};

const UpgradeSchema = Yup.object().shape({
height: Yup.number().required('Height is required').integer('Must be a valid number'),
height: Yup.number()
.typeError('Height must be a number')
.required('Height is required')
.integer('Must be a valid number')
.test(
'min-height',
'Height must be at least 1000 blocks above current height',
function (inputHeight) {
const proposedHeight = Number(inputHeight);
const chainHeight = Number(this.options.context?.chainData?.currentHeight || 0);

if (Number.isNaN(proposedHeight) || Number.isNaN(chainHeight)) {
return false;
}

const minimumAllowedHeight = chainHeight + 1000;

return proposedHeight >= minimumAllowedHeight;
}
),
});

export function UpgradeModal({ isOpen, onClose, admin, address, refetchPlan }: BaseModalProps) {
const [searchTerm, setSearchTerm] = useState('');
const { releases, isReleasesLoading } = useGitHubReleases();

const { blockHeight } = useBlockHeight();

// Filter releases that are upgradeable
const upgradeableReleases = useMemo(() => {
const allReleases = [...(releases || [])];
Expand Down Expand Up @@ -85,14 +107,41 @@ export function UpgradeModal({ isOpen, onClose, admin, address, refetchPlan }: B
const { tx, isSigning, setIsSigning } = useTx(env.chain);
const { estimateFee } = useFeeEstimation(env.chain);

const handleUpgrade = async (values: { name: string; height: string; info: string }) => {
const handleUpgrade = async (values: {
name: string;
height: string;
info: string;
selectedVersion: (GitHubRelease & { upgradeInfo?: UpgradeInfo | null }) | null;
}) => {
setIsSigning(true);

const selectedRelease = values.selectedVersion;
const binaryLinks: { [key: string]: string } = {};

// Map assets to their platform-specific links
selectedRelease?.assets?.forEach(asset => {
if (asset.name.includes('linux-amd64')) {
binaryLinks['linux/amd64'] = asset.browser_download_url;
} else if (asset.name.includes('linux-arm64')) {
binaryLinks['linux/arm64'] = asset.browser_download_url;
} else if (asset.name.includes('darwin-amd64')) {
binaryLinks['darwin/amd64'] = asset.browser_download_url;
} else if (asset.name.includes('darwin-arm64')) {
binaryLinks['darwin/arm64'] = asset.browser_download_url;
}
});

const infoObject = {
commitHash: values.selectedVersion?.upgradeInfo?.commitHash || '',
binaries: binaryLinks,
};

const msgUpgrade = softwareUpgrade({
plan: {
name: values.name,
height: BigInt(values.height),
time: new Date(0),
info: values.info,
info: JSON.stringify(infoObject),
},
authority: admin,
});
Expand Down Expand Up @@ -129,7 +178,14 @@ export function UpgradeModal({ isOpen, onClose, admin, address, refetchPlan }: B
info: '',
selectedVersion: null as (GitHubRelease & { upgradeInfo?: UpgradeInfo | null }) | null,
};

const validationContext = useMemo(
() => ({
chainData: {
currentHeight: Number(blockHeight),
},
}),
[blockHeight]
);
const modalContent = (
<dialog
className={`modal ${isOpen ? 'modal-open' : ''}`}
Expand All @@ -153,11 +209,22 @@ export function UpgradeModal({ isOpen, onClose, admin, address, refetchPlan }: B
<Formik
initialValues={initialValues}
validationSchema={UpgradeSchema}
validate={values => {
return UpgradeSchema.validate(values, { context: validationContext })
.then(() => ({}))
.catch(err => {
return err.errors.reduce((acc: { [key: string]: string }, curr: string) => {
acc[err.path] = curr;
return acc;
}, {});
});
}}
onSubmit={values => {
handleUpgrade({
name: values.selectedVersion?.upgradeInfo?.name || '',
height: values.height,
info: values.selectedVersion?.upgradeInfo?.commitHash || '',
selectedVersion: values.selectedVersion,
});
}}
validateOnChange={true}
Expand Down Expand Up @@ -266,11 +333,13 @@ export function UpgradeModal({ isOpen, onClose, admin, address, refetchPlan }: B
</div>
</div>
<TextInput
label="HEIGHT"
label={`HEIGHT (Current: ${blockHeight})`}
name="height"
type="number"
value={values.height}
onChange={handleChange}
placeholder="Block height for upgrade"
min="0"
/>
<TextInput
label="NAME"
Expand Down
4 changes: 3 additions & 1 deletion components/react/inputs/BaseInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const BaseInput: React.FC<BaseInputProps & React.InputHTMLAttributes<HTML
<div className="flex justify-between items-center">
{label && (
<label className="label" htmlFor={id}>
<span className="label-text text-[#00000099] dark:text-[#FFFFFF99]">{label}</span>
<span className="label-text text-[#00000099] dark:text-[#FFFFFF99] select-text">
{label}
</span>
</label>
)}
{helperText && (
Expand Down
24 changes: 24 additions & 0 deletions hooks/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -958,3 +958,27 @@ export const useGitHubReleases = () => {
refetchReleases: releasesQuery.refetch,
};
};

export const useBlockHeight = () => {
const { lcdQueryClient } = useLcdQueryClient();

const fetchBlockHeight = async () => {
const response = await lcdQueryClient?.cosmos.base.node.v1beta1.status();
return response?.height;
};

const blockHeightQuery = useQuery({
queryKey: ['blockHeight'],
queryFn: fetchBlockHeight,
enabled: !!lcdQueryClient,
staleTime: 0,
refetchInterval: 6000,
});

return {
blockHeight: blockHeightQuery.data,
isBlockHeightLoading: blockHeightQuery.isLoading,
isBlockHeightError: blockHeightQuery.isError,
refetchBlockHeight: blockHeightQuery.refetch,
};
};

0 comments on commit fe9bf87

Please sign in to comment.