From f88007632976ba4c30528ca58b391a53cc2adab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Tue, 23 Jul 2024 09:30:41 +0200 Subject: [PATCH 1/2] fix(#1582): Add support for CIP-100 on vote metadata --- CHANGELOG.md | 1 + .../VoteContextStoringInformation.tsx | 4 +- govtool/frontend/src/consts/CIP100Context.ts | 51 +++++++++++++++++++ govtool/frontend/src/consts/index.ts | 3 +- .../src/hooks/forms/useVoteContextForm.tsx | 39 ++++++++++---- govtool/frontend/src/i18n/locales/en.ts | 2 +- 6 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 govtool/frontend/src/consts/CIP100Context.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ef62c9ee6..de47dd8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ changes. - Add PDF_API_URL to the frontend config [Issue 1575](https://github.com/IntersectMBO/govtool/issues/1575) - Provide DB-Sync Postgres envs to the backend config - Add redirects to cards on Home after user connects [Issue 1442](https://github.com/IntersectMBO/govtool/issues/1442) +- Add support for CIP-100 for context to Governance Action Vote [Issue 1582](https://github.com/IntersectMBO/govtool/issues/1582) ### Fixed diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx index 95485fcd1..d5a583f37 100644 --- a/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx @@ -31,7 +31,7 @@ export const VoteContextStoringInformation = ({ validateURL, watch, generateMetadata, - onClickDownloadFile, + onClickDownloadJson, } = useVoteContextForm(setSavedHash, setStep, setErrorMessage); const openGuideAboutStoringInformation = () => @@ -78,7 +78,7 @@ export const VoteContextStoringInformation = ({ } sx={{ diff --git a/govtool/frontend/src/consts/CIP100Context.ts b/govtool/frontend/src/consts/CIP100Context.ts new file mode 100644 index 000000000..2cfa2127f --- /dev/null +++ b/govtool/frontend/src/consts/CIP100Context.ts @@ -0,0 +1,51 @@ +export const CIP_100_CONTEXT = { + "@language": "en-us", + CIP100: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + hashAlgorithm: "CIP100:hashAlgorithm", + body: { + "@id": "CIP100:body", + "@context": { + references: { + "@id": "CIP100:references", + "@container": "@set" as const, + "@context": { + GovernanceMetadata: "CIP100:GovernanceMetadataReference", + Other: "CIP100:OtherReference", + label: "CIP100:reference-label", + uri: "CIP100:reference-uri", + referenceHash: { + "@id": "CIP100:referenceHash", + "@context": { + hashDigest: "CIP100:hashDigest", + hashAlgorithm: "CIP100:hashAlgorithm", + }, + }, + }, + }, + comment: "CIP100:comment", + externalUpdates: { + "@id": "CIP100:externalUpdates", + "@context": { + title: "CIP100:update-title", + uri: "CIP100:uri", + }, + }, + }, + }, + authors: { + "@id": "CIP100:authors", + "@container": "@set" as const, + "@context": { + name: "http://xmlns.com/foaf/0.1/name", + witness: { + "@id": "CIP100:witness", + "@context": { + witnessAlgorithm: "CIP100:witnessAlgorithm", + publicKey: "CIP100:publicKey", + signature: "CIP100:signature", + }, + }, + }, + }, +}; diff --git a/govtool/frontend/src/consts/index.ts b/govtool/frontend/src/consts/index.ts index e4e44f2d5..e1e7275f0 100644 --- a/govtool/frontend/src/consts/index.ts +++ b/govtool/frontend/src/consts/index.ts @@ -1,7 +1,8 @@ -export * from "./externalDataModalConfig"; +export * from "./CIP100Context"; export * from "./colors"; export * from "./dRepActions"; export * from "./dRepDirectory"; +export * from "./externalDataModalConfig"; export * from "./governanceAction"; export * from "./icons"; export * from "./images"; diff --git a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx index e94e80a79..31a19689a 100644 --- a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx +++ b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx @@ -2,9 +2,16 @@ import { Dispatch, SetStateAction, useCallback, useState } from "react"; import { useFormContext } from "react-hook-form"; import { blake2bHex } from "blakejs"; import * as Sentry from "@sentry/react"; - -import { downloadTextFile } from "@utils"; +import { NodeObject } from "jsonld"; + +import { + canonizeJSON, + downloadJson, + generateJsonld, + generateMetadataBody, +} from "@utils"; import { MetadataValidationStatus } from "@models"; +import { CIP_100, CIP_100_CONTEXT } from "@/consts"; import { useValidateMutation } from "../mutations"; @@ -21,6 +28,7 @@ export const useVoteContextForm = ( ) => { const { validateMetadata } = useValidateMutation(); const [hash, setHash] = useState(null); + const [json, setJson] = useState(null); const { control, @@ -35,20 +43,29 @@ export const useVoteContextForm = ( const generateMetadata = useCallback(async () => { const { voteContextText } = getValues(); - - const canonizedJsonHash = blake2bHex(voteContextText, undefined, 32); + const body = generateMetadataBody({ + data: { + comment: voteContextText, + }, + acceptedKeys: ["comment"], + standardReference: CIP_100, + }); + const jsonld = await generateJsonld(body, CIP_100_CONTEXT, CIP_100); + + const canonizedJson = await canonizeJSON(jsonld); + const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); // That allows to validate metadata hash setHash(canonizedJsonHash); + setJson(jsonld); - return voteContextText; + return jsonld; }, [getValues]); - const onClickDownloadFile = useCallback(() => { - const { voteContextText } = getValues(); - if (!voteContextText) return; - downloadTextFile(voteContextText, "Vote_Context"); - }, [getValues]); + const onClickDownloadJson = () => { + if (!json) return; + downloadJson(json, "Vote_Context"); + }; const validateHash = useCallback( async (url: string, localHash: string | null) => { @@ -102,7 +119,7 @@ export const useVoteContextForm = ( generateMetadata, getValues, isValid, - onClickDownloadFile, + onClickDownloadJson, register, reset, setValue, diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index b50bff7d6..9d85b6baf 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -434,7 +434,7 @@ export const en = { viewOtherDetails: "View other details", viewProposalDetails: "View proposal details", vote: "Vote", - voteContextFileName: "Vote_Context.txt", + voteContextFileName: "Vote_Context.jsonld", votedOnByMe: "Voted on by me", voteOnGovActions: "Vote on Governance Action", voteSubmitted: "Vote submitted", From 6d0fac55a23f70d97873969a6c075c1c14579880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Mon, 29 Jul 2024 17:41:08 +0200 Subject: [PATCH 2/2] chore(#1582): get rid of canonization of CIP-100 metadata --- .../frontend/src/context/governanceAction.test.tsx | 2 +- govtool/frontend/src/context/governanceAction.tsx | 7 +++---- .../hooks/forms/useCreateGovernanceActionForm.ts | 6 ++---- .../frontend/src/hooks/forms/useEditDRepInfoForm.ts | 12 +++--------- .../src/hooks/forms/useRegisterAsdRepForm.tsx | 6 ++---- .../frontend/src/hooks/forms/useVoteContextForm.tsx | 12 +++--------- govtool/metadata-validation/src/app.service.test.ts | 8 +------- govtool/metadata-validation/src/app.service.ts | 13 ++----------- 8 files changed, 17 insertions(+), 49 deletions(-) diff --git a/govtool/frontend/src/context/governanceAction.test.tsx b/govtool/frontend/src/context/governanceAction.test.tsx index f32cd30ee..87d4d2835 100644 --- a/govtool/frontend/src/context/governanceAction.test.tsx +++ b/govtool/frontend/src/context/governanceAction.test.tsx @@ -92,7 +92,7 @@ describe("GovernanceActionProvider", () => { const hash = await createHash(jsonld!); expect(hash).toBeDefined(); expect(hash).toBe( - "5eebd2c216f3e0718283eb8c0c9ac27ba0a1fd04a7ca849b8c739fb546311931", + "b84e9890a34d6e59a983cf8f695214162893de5fc5310b12b75f4fe3dab0d7ab", ); }; test(); diff --git a/govtool/frontend/src/context/governanceAction.tsx b/govtool/frontend/src/context/governanceAction.tsx index a1f1066e2..807a22608 100644 --- a/govtool/frontend/src/context/governanceAction.tsx +++ b/govtool/frontend/src/context/governanceAction.tsx @@ -11,7 +11,7 @@ import { blake2bHex } from "blakejs"; import * as Sentry from "@sentry/react"; import { CIP_108, GOVERNANCE_ACTION_CONTEXT } from "@/consts"; -import { canonizeJSON, generateJsonld, generateMetadataBody } from "@/utils"; +import { generateJsonld, generateMetadataBody } from "@/utils"; type GovActionMetadata = { title: string; @@ -76,9 +76,8 @@ const GovernanceActionProvider = ({ children }: PropsWithChildren) => { */ const createHash = useCallback(async (jsonLD: NodeObject) => { try { - const canonizedJson = await canonizeJSON(jsonLD); - const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); - return canonizedJsonHash; + const jsonHash = blake2bHex(JSON.stringify(jsonLD), undefined, 32); + return jsonHash; } catch (error) { Sentry.captureException(error); console.error(error); diff --git a/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts b/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts index 9fdaae96f..02eea1a1f 100644 --- a/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts +++ b/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts @@ -14,7 +14,6 @@ import { } from "@consts"; import { useCardano, useModal } from "@context"; import { - canonizeJSON, correctAdaFormat, downloadJson, generateJsonld, @@ -109,11 +108,10 @@ export const useCreateGovernanceActionForm = ( const jsonld = await generateJsonld(body, GOVERNANCE_ACTION_CONTEXT); - const canonizedJson = await canonizeJSON(jsonld); - const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); + const jsonHash = blake2bHex(JSON.stringify(jsonld), undefined, 32); // That allows to validate metadata hash - setHash(canonizedJsonHash); + setHash(jsonHash); setJson(jsonld); return jsonld; diff --git a/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts b/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts index 3ea4e1872..12f5e964b 100644 --- a/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts +++ b/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts @@ -13,12 +13,7 @@ import { storageInformationErrorModals, } from "@consts"; import { useCardano, useModal } from "@context"; -import { - canonizeJSON, - downloadJson, - generateJsonld, - generateMetadataBody, -} from "@utils"; +import { downloadJson, generateJsonld, generateMetadataBody } from "@utils"; import { MetadataStandard, MetadataValidationStatus } from "@models"; import { useWalletErrorModal } from "@hooks"; import { useValidateMutation } from "../mutations"; @@ -97,10 +92,9 @@ export const useEditDRepInfoForm = ( const jsonld = await generateJsonld(body, DREP_CONTEXT, CIP_QQQ); - const canonizedJson = await canonizeJSON(jsonld); - const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); + const jsonHash = blake2bHex(JSON.stringify(jsonld), undefined, 32); - setHash(canonizedJsonHash); + setHash(jsonHash); setJson(jsonld); return jsonld; diff --git a/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx b/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx index 39c590d95..4a08d2520 100644 --- a/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx +++ b/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx @@ -15,7 +15,6 @@ import { import { useCardano, useModal } from "@context"; import { MetadataStandard, MetadataValidationStatus } from "@models"; import { - canonizeJSON, downloadJson, ellipsizeText, generateJsonld, @@ -106,10 +105,9 @@ export const useRegisterAsdRepForm = ( const jsonld = await generateJsonld(body, DREP_CONTEXT, CIP_QQQ); - const canonizedJson = await canonizeJSON(jsonld); - const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); + const jsonHash = blake2bHex(JSON.stringify(jsonld), undefined, 32); - setHash(canonizedJsonHash); + setHash(jsonHash); setJson(jsonld); return jsonld; diff --git a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx index 31a19689a..25fc2ef24 100644 --- a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx +++ b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx @@ -4,12 +4,7 @@ import { blake2bHex } from "blakejs"; import * as Sentry from "@sentry/react"; import { NodeObject } from "jsonld"; -import { - canonizeJSON, - downloadJson, - generateJsonld, - generateMetadataBody, -} from "@utils"; +import { downloadJson, generateJsonld, generateMetadataBody } from "@utils"; import { MetadataValidationStatus } from "@models"; import { CIP_100, CIP_100_CONTEXT } from "@/consts"; @@ -52,11 +47,10 @@ export const useVoteContextForm = ( }); const jsonld = await generateJsonld(body, CIP_100_CONTEXT, CIP_100); - const canonizedJson = await canonizeJSON(jsonld); - const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); + const jsonHash = blake2bHex(JSON.stringify(jsonld), undefined, 32); // That allows to validate metadata hash - setHash(canonizedJsonHash); + setHash(jsonHash); setJson(jsonld); return jsonld; diff --git a/govtool/metadata-validation/src/app.service.test.ts b/govtool/metadata-validation/src/app.service.test.ts index 0e3d8f946..8b7fb402f 100644 --- a/govtool/metadata-validation/src/app.service.test.ts +++ b/govtool/metadata-validation/src/app.service.test.ts @@ -7,7 +7,7 @@ import { AppService } from './app.service'; import { ValidateMetadataDTO } from '@dto'; import { MetadataValidationStatus } from '@enums'; import { MetadataStandard } from '@types'; -import { canonizeJSON, validateMetadataStandard, parseMetadata } from '@utils'; +import { validateMetadataStandard, parseMetadata } from '@utils'; import { AxiosResponse, AxiosRequestHeaders } from 'axios'; jest.mock('@utils'); @@ -42,7 +42,6 @@ describe('AppService', () => { body: 'testBody', headers: {}, }; - const canonizedMetadata = 'canonizedMetadata'; const parsedMetadata = { parsed: 'metadata' }; const response: AxiosResponse = { data, @@ -57,7 +56,6 @@ describe('AppService', () => { jest.spyOn(httpService, 'get').mockReturnValueOnce(of(response)); (validateMetadataStandard as jest.Mock).mockResolvedValueOnce(undefined); (parseMetadata as jest.Mock).mockReturnValueOnce(parsedMetadata); - (canonizeJSON as jest.Mock).mockResolvedValueOnce(canonizedMetadata); jest.spyOn(blake, 'blake2bHex').mockReturnValueOnce(hash); const result = await service.validateMetadata(validateMetadataDTO); @@ -69,7 +67,6 @@ describe('AppService', () => { }); expect(validateMetadataStandard).toHaveBeenCalledWith(data, standard); expect(parseMetadata).toHaveBeenCalledWith(data.body, standard); - expect(canonizeJSON).toHaveBeenCalledWith(data); }); it('should handle URL_NOT_FOUND error', async () => { @@ -100,7 +97,6 @@ describe('AppService', () => { const data = { body: 'testBody', }; - const canonizedMetadata = 'canonizedMetadata'; const parsedMetadata = { parsed: 'metadata' }; const response: AxiosResponse = { @@ -116,7 +112,6 @@ describe('AppService', () => { jest.spyOn(httpService, 'get').mockReturnValueOnce(of(response)); (validateMetadataStandard as jest.Mock).mockResolvedValueOnce(undefined); (parseMetadata as jest.Mock).mockReturnValueOnce(parsedMetadata); - (canonizeJSON as jest.Mock).mockResolvedValueOnce(canonizedMetadata); jest.spyOn(blake, 'blake2bHex').mockReturnValueOnce('differentHash'); const result = await service.validateMetadata(validateMetadataDTO); @@ -151,7 +146,6 @@ describe('AppService', () => { jest.spyOn(httpService, 'get').mockReturnValueOnce(of(response)); (validateMetadataStandard as jest.Mock).mockResolvedValueOnce(undefined); (parseMetadata as jest.Mock).mockReturnValueOnce(parsedMetadata); - (canonizeJSON as jest.Mock).mockRejectedValueOnce(new Error()); const result = await service.validateMetadata(validateMetadataDTO); diff --git a/govtool/metadata-validation/src/app.service.ts b/govtool/metadata-validation/src/app.service.ts index 633d77f2a..6775e9d3f 100644 --- a/govtool/metadata-validation/src/app.service.ts +++ b/govtool/metadata-validation/src/app.service.ts @@ -5,7 +5,7 @@ import * as blake from 'blakejs'; import { ValidateMetadataDTO } from '@dto'; import { LoggerMessage, MetadataValidationStatus } from '@enums'; -import { canonizeJSON, validateMetadataStandard, parseMetadata } from '@utils'; +import { validateMetadataStandard, parseMetadata } from '@utils'; import { MetadataStandard, ValidateMetadataResult } from '@types'; @Injectable() @@ -40,17 +40,8 @@ export class AppService { metadata = parseMetadata(data.body, standard); } - let canonizedMetadata; - if (!noStandard) { - try { - canonizedMetadata = await canonizeJSON(data); - } catch (error) { - throw MetadataValidationStatus.INVALID_JSONLD; - } - } - const hashedMetadata = blake.blake2bHex( - !standard ? data : canonizedMetadata, + !standard ? data : metadata, undefined, 32, );