From d4eb74967da571df72a1b3faa31ab797fea4857e Mon Sep 17 00:00:00 2001 From: Cmdv Date: Tue, 23 Sep 2025 14:38:35 +0100 Subject: [PATCH 1/6] update nix flake --- cabal.project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal.project b/cabal.project index 5e9549e2a..7f7d5eed3 100644 --- a/cabal.project +++ b/cabal.project @@ -11,7 +11,7 @@ repository cardano-haskell-packages index-state: , hackage.haskell.org 2025-08-03T21:32:16Z - , cardano-haskell-packages 2025-07-30T14:13:57Z + , cardano-haskell-packages 2025-06-25T13:51:34Z packages: cardano-db From fc9ff2060b1d8eabb37b51f64f53a72a65231cf7 Mon Sep 17 00:00:00 2001 From: Sean D Gillespie Date: Tue, 23 Sep 2025 15:28:54 -0400 Subject: [PATCH 2/6] Disable split static postgresql outputs In it's current state, we're unable to build cardana-chain-gen: cannot find -lpgcommon: No such file or directory cannot find -lpgport: No such file or directory collect2: error: ld returned 1 exit status Remove the extra configure flags, and see if we can still build all the other artifacts. --- cabal.project | 2 +- config/pgpass-mainnet | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 7f7d5eed3..5e9549e2a 100644 --- a/cabal.project +++ b/cabal.project @@ -11,7 +11,7 @@ repository cardano-haskell-packages index-state: , hackage.haskell.org 2025-08-03T21:32:16Z - , cardano-haskell-packages 2025-06-25T13:51:34Z + , cardano-haskell-packages 2025-07-30T14:13:57Z packages: cardano-db diff --git a/config/pgpass-mainnet b/config/pgpass-mainnet index fde56efbc..bb4733a29 100644 --- a/config/pgpass-mainnet +++ b/config/pgpass-mainnet @@ -1 +1 @@ -/var/run/postgresql:5432:cexplorer:*:* +/tmp:5432:cexplorer:*:* From 0125a8a0a917c5da210709aa72826ecaf8406c46 Mon Sep 17 00:00:00 2001 From: Cmdv Date: Wed, 15 Oct 2025 13:41:23 +0100 Subject: [PATCH 3/6] 1995 fix offchain vote data --- .../testfiles/invalid-vote-doNotList.jsonld | 110 ++++++++++++++++++ cardano-db-sync/app/http-get-json-metadata.hs | 4 +- cardano-db-sync/cardano-db-sync.cabal | 1 + .../src/Cardano/DbSync/OffChain.hs | 63 ++++++---- .../src/Cardano/DbSync/OffChain/Http.hs | 17 +-- cardano-db-sync/src/Cardano/DbSync/Types.hs | 2 +- .../test/Cardano/DbSync/OffChain/VoteTest.hs | 67 +++++++++++ cardano-db-sync/test/Main.hs | 2 + config/pgpass-mainnet | 2 +- 9 files changed, 236 insertions(+), 32 deletions(-) create mode 100644 cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld create mode 100644 cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs diff --git a/cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld b/cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld new file mode 100644 index 000000000..5567e2d10 --- /dev/null +++ b/cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld @@ -0,0 +1,110 @@ +{ + "@context": { + "CIP100": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + "CIP119": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0119/README.md#", + "hashAlgorithm": "CIP100:hashAlgorithm", + "body": { + "@id": "CIP119:body", + "@context": { + "references": { + "@id": "CIP119:references", + "@container": "@set", + "@context": { + "GovernanceMetadata": "CIP100:GovernanceMetadataReference", + "Identity": "CIP119:IdentityReference", + "Link": "CIP119:LinkReference", + "Other": "CIP100:OtherReference", + "label": "CIP100:reference-label", + "uri": "CIP100:reference-uri", + "referenceHash": { + "@id": "CIP119:referenceHash", + "@context": { + "hashDigest": "CIP119:hashDigest", + "hashAlgorithm": "CIP100:hashAlgorithm" + } + } + } + }, + "paymentAddress": "CIP119:paymentAddress", + "givenName": "CIP119:givenName", + "image": "CIP119:image", + "objectives": "CIP119:objectives", + "motivations": "CIP119:motivations", + "qualifications": "CIP119:qualifications", + "doNotList": "CIP119:doNotList" + } + }, + "authors": { + "@id": "CIP100:authors", + "@container": "@set", + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "witness": { + "@id": "CIP100:witness", + "@context": { + "witnessAlgorithm": "CIP100:witnessAlgorithm", + "publicKey": "CIP100:publicKey", + "signature": "CIP100:signature" + } + } + } + } + }, + "authors": [], + "hashAlgorithm": "blake2b-256", + "body": { + "doNotList": "false", + "givenName": "CaFi", + "motivations": "With a strong belief in the potential of blockchain technology and the development of the Cardano ecosystem, the CaFi team wishes to contribute to innovation and progress through supporting community projects and initiatives. The main motivation of CaFi is to make the Catalyst more accessible to Vietnamese, while creating an environment where people can learn, share and develop their skills in writing proposals, evaluating and participating in the democratic voting process.\n\nThe CaFi team understands that the blockchain ecosystem in general and Cardano in particular are in the development stage and require community participation to achieve sustainability and success. By guiding the community to participate in Catalyst, CaFi wants to promote creativity, innovation and help valuable ideas receive the necessary support to grow.", + "objectives": "1. Community Support: CaFi team focuses on helping the Vietnam community actively participate in the Catalyst through consulting and training activities on proposal writing, proposal evaluation and voting.\n\n2. Promoting innovation: We wishe to support and develop innovative projects in the Cardano ecosystem, especially those with practical value and sustainable development potential.\n\n3. Building the dRep role: We aim to become a responsible and reputable dRep in Cardano's decentralized governance model, contributing to the long-term development of the ecosystem.\n\n4. Community development: We want to build a dynamic, creative and self-directed community in the Blockchain ecosystem through participating in funding opportunities from Catalyst.\n\nWith the experience gained, CaFi team believes that working with the community will bring many values, while helping projects from Vietnam to have better access to resources from Catalyst to develop strongly on the Cardano platform.", + "paymentAddress": "addr1q9mrk85srqxk88239chlxd73ct2s4nl20jzfwgu9k7tzxe60knquflpw8mtql842hyundh3vdy4e6zhw8f6z7afxjvys34qg2p", + "qualifications": "1. Do Viet Cuong\n● Master, high school teacher\n● Blockchain research since 2017\n● Cardano Vietnam community manager\n● Co-host of 9 Catalyst funded proposals\n● Admin of youtube channel, podcast, discussion group about Cardano and Catalyst project\n● Certified CBCA\n\n2. Tu Nguyen\n● Vietnam Drep Pioneer Program #1\n● FIMI Translator team\n● CBCA Alpha Program 2, Certificate ID: 64898843661a2dbd9402d27b\n● CBCA Course, Certificate ID: 5351588\n● Catalyst Funded Proposer\n● Admin of the Catalyst discussion group\n\n3. Duc Nguyen\n● Cardano Blockchain Researcher from 2018 to present: Research and develop project related to Cardano Blockchain\n● Skill: Financial analysis; IT system management, database; Graphic design\n● Language: Vietnamese; English\n\n4. Hoang Phuong Linh\n● Translation and research in Cardano Blockchain since 2017\n● Marketer, content creator, trainer, event organizer, community connector\n● Certified CBCA\n● Catalyst Funded Proposer\n● Languages: Vietnamese, English", + "references": [ + { + "@type": "Link", + "label": "Catalyst Discussion Group", + "uri": "https://t.me/Fimi_PA" + }, + { + "@type": "Link", + "label": "Youtube Channel", + "uri": "https://www.youtube.com/@taichinh-taman5516" + }, + { + "@type": "Link", + "label": "General Cardano Discussion Group", + "uri": "https://t.me/StakingADA" + }, + { + "@type": "Link", + "label": "Cardano Podcast Channel", + "uri": "https://t.me/fimi_podcast" + }, + { + "@type": "Link", + "label": "IOG Reseacher Paper Library in Vietnamese Version", + "uri": "https://cardano.vn/docs/Cardano360/TechDocs/intro/" + }, + { + "@type": "Identity", + "label": "Do Viet Cuong", + "uri": "https://t.me/dovietcuong" + }, + { + "@type": "Identity", + "label": "Tu Nguyen", + "uri": "https://t.me/Tulibra" + }, + { + "@type": "Identity", + "label": "Duc Nguyen", + "uri": "https://www.linkedin.com/in/minh-983aa4241/" + }, + { + "@type": "Identity", + "label": "Hoang Phuong Linh", + "uri": "https://www.linkedin.com/in/phuong-linh-hoang-a318bb138/" + } + ] + } +} \ No newline at end of file diff --git a/cardano-db-sync/app/http-get-json-metadata.hs b/cardano-db-sync/app/http-get-json-metadata.hs index 760a20222..5d66da1ca 100644 --- a/cardano-db-sync/app/http-get-json-metadata.hs +++ b/cardano-db-sync/app/http-get-json-metadata.hs @@ -143,8 +143,8 @@ runGetVote :: Text.Text -> Maybe VoteMetaHash -> DB.AnchorType -> IO () runGetVote file mExpectedHash vtype = do respBs <- BS.readFile (Text.unpack file) let respLBs = fromStrict respBs - (ocvd, val, hsh, mWarning) <- runOrThrowIO $ runExceptT $ parseAndValidateVoteData respBs respLBs mExpectedHash vtype Nothing - print ocvd + (mocvd, val, hsh, mWarning) <- runOrThrowIO $ runExceptT $ parseAndValidateVoteData respBs respLBs mExpectedHash vtype Nothing + print mocvd print val print $ bsBase16Encode hsh print mWarning diff --git a/cardano-db-sync/cardano-db-sync.cabal b/cardano-db-sync/cardano-db-sync.cabal index d3e136fa6..1e9329f61 100644 --- a/cardano-db-sync/cardano-db-sync.cabal +++ b/cardano-db-sync/cardano-db-sync.cabal @@ -334,6 +334,7 @@ test-suite test Cardano.DbSync.Era.Shelley.Generic.ScriptDataTest Cardano.DbSync.Era.Shelley.Generic.ScriptTest Cardano.DbSync.Gen + Cardano.DbSync.OffChain.VoteTest Cardano.DbSync.Util.AddressTest Cardano.DbSync.Util.Bech32Test Cardano.DbSync.Util.CborTest diff --git a/cardano-db-sync/src/Cardano/DbSync/OffChain.hs b/cardano-db-sync/src/Cardano/DbSync/OffChain.hs index ee94a64ec..874d3c2d7 100644 --- a/cardano-db-sync/src/Cardano/DbSync/OffChain.hs +++ b/cardano-db-sync/src/Cardano/DbSync/OffChain.hs @@ -349,27 +349,48 @@ fetchOffChainVoteData gateways time oVoteWorkQ = convert eres = case eres of Right sVoteData -> - let - offChainData = sovaOffChainVoteData sVoteData - minimalBody = Vote.getMinimalBody offChainData - vdt = - DB.OffChainVoteData - { DB.offChainVoteDataLanguage = Vote.getLanguage offChainData - , DB.offChainVoteDataComment = Vote.textValue <$> Vote.comment minimalBody - , DB.offChainVoteDataBytes = sovaBytes sVoteData - , DB.offChainVoteDataHash = sovaHash sVoteData - , DB.offChainVoteDataJson = sovaJson sVoteData - , DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ - , DB.offChainVoteDataWarning = sovaWarning sVoteData - , DB.offChainVoteDataIsValid = Nothing - } - gaF ocvdId = mkGovAction ocvdId offChainData - drepF ocvdId = mkDrep ocvdId offChainData - authorsF ocvdId = map (mkAuthor ocvdId) $ Vote.getAuthors offChainData - referencesF ocvdId = map (mkReference ocvdId) $ mListToList $ Vote.references minimalBody - externalUpdatesF ocvdId = map (mkexternalUpdates ocvdId) $ mListToList $ Vote.externalUpdates minimalBody - in - OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF) + case sovaOffChainVoteData sVoteData of + Just offChainData -> + let + minimalBody = Vote.getMinimalBody offChainData + vdt = + DB.OffChainVoteData + { DB.offChainVoteDataLanguage = Vote.getLanguage offChainData + , DB.offChainVoteDataComment = Vote.textValue <$> Vote.comment minimalBody + , DB.offChainVoteDataBytes = sovaBytes sVoteData + , DB.offChainVoteDataHash = sovaHash sVoteData + , DB.offChainVoteDataJson = sovaJson sVoteData + , DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ + , DB.offChainVoteDataWarning = sovaWarning sVoteData + , DB.offChainVoteDataIsValid = Just True + } + gaF ocvdId = mkGovAction ocvdId offChainData + drepF ocvdId = mkDrep ocvdId offChainData + authorsF ocvdId = map (mkAuthor ocvdId) $ Vote.getAuthors offChainData + referencesF ocvdId = map (mkReference ocvdId) $ mListToList $ Vote.references minimalBody + externalUpdatesF ocvdId = map (mkexternalUpdates ocvdId) $ mListToList $ Vote.externalUpdates minimalBody + in + OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF) + Nothing -> + let + vdt = + DB.OffChainVoteData + { DB.offChainVoteDataLanguage = "" + , DB.offChainVoteDataComment = Nothing + , DB.offChainVoteDataBytes = sovaBytes sVoteData + , DB.offChainVoteDataHash = sovaHash sVoteData + , DB.offChainVoteDataJson = sovaJson sVoteData + , DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ + , DB.offChainVoteDataWarning = sovaWarning sVoteData + , DB.offChainVoteDataIsValid = Just False + } + gaF _ = Nothing + drepF _ = Nothing + authorsF _ = [] + referencesF _ = [] + externalUpdatesF _ = [] + in + OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF) Left err -> OffChainVoteResultError $ DB.OffChainVoteFetchError diff --git a/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs b/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs index 4252a0f27..c6be4b97e 100644 --- a/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs +++ b/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs @@ -108,35 +108,38 @@ httpGetOffChainVoteDataSingle vurl metaHash anchorType = do let req = httpGetBytes manager request 3000000 3000000 url httpRes <- handleExceptT (convertHttpException url) req (respBS, respLBS, mContentType) <- hoistEither httpRes - (ocvd, decodedValue, metadataHash, mWarning) <- parseAndValidateVoteData respBS respLBS metaHash anchorType (Just $ OffChainVoteUrl vurl) + (mocvd, decodedValue, metadataHash, mWarning) <- parseAndValidateVoteData respBS respLBS metaHash anchorType (Just $ OffChainVoteUrl vurl) pure $ SimplifiedOffChainVoteData { sovaHash = metadataHash , sovaBytes = respBS , sovaJson = Text.decodeUtf8 $ LBS.toStrict (Aeson.encode decodedValue) , sovaContentType = mContentType - , sovaOffChainVoteData = ocvd + , sovaOffChainVoteData = mocvd , sovaWarning = mWarning } where url = OffChainVoteUrl vurl -parseAndValidateVoteData :: ByteString -> LBS.ByteString -> Maybe VoteMetaHash -> DB.AnchorType -> Maybe OffChainUrlType -> ExceptT OffChainFetchError IO (Vote.OffChainVoteData, Aeson.Value, ByteString, Maybe Text) +parseAndValidateVoteData :: ByteString -> LBS.ByteString -> Maybe VoteMetaHash -> DB.AnchorType -> Maybe OffChainUrlType -> ExceptT OffChainFetchError IO (Maybe Vote.OffChainVoteData, Aeson.Value, ByteString, Maybe Text) parseAndValidateVoteData bs lbs metaHash anchorType murl = do let metadataHash = Crypto.digest (Proxy :: Proxy Crypto.Blake2b_256) bs + -- First check if hash matches - this is critical and must fail if mismatch (hsh, mWarning) <- case unVoteMetaHash <$> metaHash of Just expectedMetaHashBs | metadataHash /= expectedMetaHashBs -> left $ OCFErrHashMismatch murl (renderByteArray expectedMetaHashBs) (renderByteArray metadataHash) _ -> pure (metadataHash, Nothing) + -- Hash matches, now decode as generic JSON (this should still fail if not valid JSON) decodedValue <- case Aeson.eitherDecode' @Aeson.Value lbs of Left err -> left $ OCFErrJsonDecodeFail murl (Text.pack err) Right res -> pure res - ocvd <- - case Vote.eitherDecodeOffChainVoteData lbs anchorType of - Left err -> left $ OCFErrJsonDecodeFail murl (Text.pack err) - Right res -> pure res + -- Try to decode into strongly-typed vote data structure + -- If this fails (e.g., doNotList is string instead of bool), we still store with is_valid = false + let ocvd = case Vote.eitherDecodeOffChainVoteData lbs anchorType of + Left _err -> Nothing -- Don't fail, just return Nothing (will set is_valid = false) + Right res -> Just res pure (ocvd, decodedValue, hsh, mWarning) httpGetBytes :: diff --git a/cardano-db-sync/src/Cardano/DbSync/Types.hs b/cardano-db-sync/src/Cardano/DbSync/Types.hs index 5dc2cc58e..3b1ae88a3 100644 --- a/cardano-db-sync/src/Cardano/DbSync/Types.hs +++ b/cardano-db-sync/src/Cardano/DbSync/Types.hs @@ -193,7 +193,7 @@ data SimplifiedOffChainVoteData = SimplifiedOffChainVoteData , sovaBytes :: !ByteString , sovaJson :: !Text , sovaContentType :: !(Maybe ByteString) - , sovaOffChainVoteData :: !Vote.OffChainVoteData + , sovaOffChainVoteData :: !(Maybe Vote.OffChainVoteData) , sovaWarning :: !(Maybe Text) } diff --git a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs new file mode 100644 index 000000000..a411cf6e4 --- /dev/null +++ b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs @@ -0,0 +1,67 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +module Cardano.DbSync.OffChain.VoteTest (tests) where + +import qualified Cardano.Db as DB +import Cardano.DbSync.Error (runOrThrowIO) +import Cardano.DbSync.OffChain.Http (parseAndValidateVoteData) +import Cardano.Prelude hiding ((%)) +import qualified Data.Aeson as Aeson +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as LBS +import Hedgehog + +tests :: IO Bool +tests = + checkParallel $ + Group + "Cardano.DbSync.OffChain.Vote" + [ ("parseAndValidateVoteData handles invalid CIP format", prop_parseInvalidCIPFormat) + , ("parseAndValidateVoteData handles valid JSON but invalid structure", prop_parseValidJsonInvalidStructure) + ] + +-- | Test that we can parse JSON with incorrect field types (e.g., doNotList as string instead of bool) +-- This is based on the issue https://github.com/IntersectMBO/cardano-db-sync/issues/1995 +prop_parseInvalidCIPFormat :: Property +prop_parseInvalidCIPFormat = withTests 1 $ property $ do + -- Read the test file with invalid doNotList field (string instead of bool) + fileContent <- liftIO $ BS.readFile "../cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld" + let lbsContent = LBS.fromStrict fileContent + + -- Run the parser + result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData fileContent lbsContent Nothing DB.DrepAnchor Nothing + + let (mocvd, val, _hash, _warning) = result + + -- Should succeed in parsing generic JSON + annotate "Successfully parsed as generic JSON" + + -- Should fail to parse into strongly-typed OffChainVoteData + assert $ isNothing mocvd + + -- But should have valid Aeson.Value + case Aeson.toJSON val of + Aeson.Object _obj -> do + annotate "Has valid JSON object" + success + _ -> do + annotate "Expected JSON object" + failure + +-- | Test with completely valid JSON but not matching the CIP schema +prop_parseValidJsonInvalidStructure :: Property +prop_parseValidJsonInvalidStructure = property $ do + -- Create a valid JSON that doesn't match CIP schema at all + let invalidJson = "{\"randomField\": \"value\", \"number\": 42}" + bs = encodeUtf8 invalidJson + lbs = LBS.fromStrict bs + + -- This should succeed because it's valid JSON, just not matching the schema + result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData bs lbs Nothing DB.DrepAnchor Nothing + + let (mocvd, _val, _hash, _warning) = result + + annotate "Successfully parsed generic JSON" + -- Should not parse into OffChainVoteData + assert $ isNothing mocvd diff --git a/cardano-db-sync/test/Main.hs b/cardano-db-sync/test/Main.hs index b629870a1..b32b9ce61 100644 --- a/cardano-db-sync/test/Main.hs +++ b/cardano-db-sync/test/Main.hs @@ -4,6 +4,7 @@ import qualified Cardano.DbSync.ApiTest as Api import qualified Cardano.DbSync.Config.TypesTest as Types import qualified Cardano.DbSync.Era.Shelley.Generic.ScriptDataTest as ScriptData import qualified Cardano.DbSync.Era.Shelley.Generic.ScriptTest as Script +import qualified Cardano.DbSync.OffChain.VoteTest as VoteTest import qualified Cardano.DbSync.Util.AddressTest as Address import qualified Cardano.DbSync.Util.Bech32Test as Bech32 import qualified Cardano.DbSync.Util.CborTest as Cbor @@ -23,4 +24,5 @@ main = , DbSync.tests , Types.tests , Api.tests + , VoteTest.tests ] diff --git a/config/pgpass-mainnet b/config/pgpass-mainnet index bb4733a29..fde56efbc 100644 --- a/config/pgpass-mainnet +++ b/config/pgpass-mainnet @@ -1 +1 @@ -/tmp:5432:cexplorer:*:* +/var/run/postgresql:5432:cexplorer:*:* From 5ed94e2de6d3012755cf8a3e91b602eeb7647e65 Mon Sep 17 00:00:00 2001 From: Cmdv Date: Wed, 15 Oct 2025 13:41:23 +0100 Subject: [PATCH 4/6] add another scenario for offchain vote data --- cardano-db-sync/app/http-get-json-metadata.hs | 3 +- .../src/Cardano/DbSync/OffChain.hs | 14 ++++-- .../src/Cardano/DbSync/OffChain/Http.hs | 29 ++++++----- cardano-db-sync/src/Cardano/DbSync/Types.hs | 1 + .../test/Cardano/DbSync/OffChain/VoteTest.hs | 49 ++++++++++++++++++- .../src/Cardano/Db/Schema/Core/OffChain.hs | 10 +++- doc/schema.md | 11 ++++- 7 files changed, 96 insertions(+), 21 deletions(-) diff --git a/cardano-db-sync/app/http-get-json-metadata.hs b/cardano-db-sync/app/http-get-json-metadata.hs index 5d66da1ca..e9e20f1b3 100644 --- a/cardano-db-sync/app/http-get-json-metadata.hs +++ b/cardano-db-sync/app/http-get-json-metadata.hs @@ -143,11 +143,12 @@ runGetVote :: Text.Text -> Maybe VoteMetaHash -> DB.AnchorType -> IO () runGetVote file mExpectedHash vtype = do respBs <- BS.readFile (Text.unpack file) let respLBs = fromStrict respBs - (mocvd, val, hsh, mWarning) <- runOrThrowIO $ runExceptT $ parseAndValidateVoteData respBs respLBs mExpectedHash vtype Nothing + (mocvd, val, hsh, mWarning, isValidJson) <- runOrThrowIO $ runExceptT $ parseAndValidateVoteData respBs respLBs mExpectedHash vtype Nothing print mocvd print val print $ bsBase16Encode hsh print mWarning + putStrLn $ "Is valid JSON: " ++ show isValidJson -- ------------------------------------------------------------------------------------------------ diff --git a/cardano-db-sync/src/Cardano/DbSync/OffChain.hs b/cardano-db-sync/src/Cardano/DbSync/OffChain.hs index 874d3c2d7..ebe44ea62 100644 --- a/cardano-db-sync/src/Cardano/DbSync/OffChain.hs +++ b/cardano-db-sync/src/Cardano/DbSync/OffChain.hs @@ -349,8 +349,9 @@ fetchOffChainVoteData gateways time oVoteWorkQ = convert eres = case eres of Right sVoteData -> - case sovaOffChainVoteData sVoteData of - Just offChainData -> + case (sovaIsValidJson sVoteData, sovaOffChainVoteData sVoteData) of + -- Scenario 1: Valid JSON + Valid CIP schema + (True, Just offChainData) -> let minimalBody = Vote.getMinimalBody offChainData vdt = @@ -371,7 +372,8 @@ fetchOffChainVoteData gateways time oVoteWorkQ = externalUpdatesF ocvdId = map (mkexternalUpdates ocvdId) $ mListToList $ Vote.externalUpdates minimalBody in OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF) - Nothing -> + -- Scenario 2 & 3: Valid JSON but invalid CIP schema OR Invalid JSON + (_, _) -> let vdt = DB.OffChainVoteData @@ -382,7 +384,11 @@ fetchOffChainVoteData gateways time oVoteWorkQ = , DB.offChainVoteDataJson = sovaJson sVoteData , DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ , DB.offChainVoteDataWarning = sovaWarning sVoteData - , DB.offChainVoteDataIsValid = Just False + , -- Just False if valid JSON but invalid schema, NULL if unparseable JSON + DB.offChainVoteDataIsValid = + if sovaIsValidJson sVoteData + then Just False + else Nothing } gaF _ = Nothing drepF _ = Nothing diff --git a/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs b/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs index c6be4b97e..9c576b87c 100644 --- a/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs +++ b/cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs @@ -108,7 +108,7 @@ httpGetOffChainVoteDataSingle vurl metaHash anchorType = do let req = httpGetBytes manager request 3000000 3000000 url httpRes <- handleExceptT (convertHttpException url) req (respBS, respLBS, mContentType) <- hoistEither httpRes - (mocvd, decodedValue, metadataHash, mWarning) <- parseAndValidateVoteData respBS respLBS metaHash anchorType (Just $ OffChainVoteUrl vurl) + (mocvd, decodedValue, metadataHash, mWarning, isValidJson) <- parseAndValidateVoteData respBS respLBS metaHash anchorType (Just $ OffChainVoteUrl vurl) pure $ SimplifiedOffChainVoteData { sovaHash = metadataHash @@ -117,11 +117,12 @@ httpGetOffChainVoteDataSingle vurl metaHash anchorType = do , sovaContentType = mContentType , sovaOffChainVoteData = mocvd , sovaWarning = mWarning + , sovaIsValidJson = isValidJson } where url = OffChainVoteUrl vurl -parseAndValidateVoteData :: ByteString -> LBS.ByteString -> Maybe VoteMetaHash -> DB.AnchorType -> Maybe OffChainUrlType -> ExceptT OffChainFetchError IO (Maybe Vote.OffChainVoteData, Aeson.Value, ByteString, Maybe Text) +parseAndValidateVoteData :: ByteString -> LBS.ByteString -> Maybe VoteMetaHash -> DB.AnchorType -> Maybe OffChainUrlType -> ExceptT OffChainFetchError IO (Maybe Vote.OffChainVoteData, Aeson.Value, ByteString, Maybe Text, Bool) parseAndValidateVoteData bs lbs metaHash anchorType murl = do let metadataHash = Crypto.digest (Proxy :: Proxy Crypto.Blake2b_256) bs -- First check if hash matches - this is critical and must fail if mismatch @@ -130,17 +131,23 @@ parseAndValidateVoteData bs lbs metaHash anchorType murl = do | metadataHash /= expectedMetaHashBs -> left $ OCFErrHashMismatch murl (renderByteArray expectedMetaHashBs) (renderByteArray metadataHash) _ -> pure (metadataHash, Nothing) - -- Hash matches, now decode as generic JSON (this should still fail if not valid JSON) - decodedValue <- + -- Hash matches, now try to decode as generic JSON + -- If this fails, we still want to store the data with is_valid = NULL and an error message + (decodedValue, isValidJson) <- case Aeson.eitherDecode' @Aeson.Value lbs of - Left err -> left $ OCFErrJsonDecodeFail murl (Text.pack err) - Right res -> pure res - -- Try to decode into strongly-typed vote data structure + Left err -> + -- Not valid JSON - create an error message object + pure (Aeson.object [("error", Aeson.String "Content is not valid JSON. See bytes column for raw data."), ("parse_error", Aeson.String $ Text.pack err)], False) + Right res -> pure (res, True) + -- Try to decode into strongly-typed vote data structure (only if JSON was valid) -- If this fails (e.g., doNotList is string instead of bool), we still store with is_valid = false - let ocvd = case Vote.eitherDecodeOffChainVoteData lbs anchorType of - Left _err -> Nothing -- Don't fail, just return Nothing (will set is_valid = false) - Right res -> Just res - pure (ocvd, decodedValue, hsh, mWarning) + let ocvd = + if isValidJson + then case Vote.eitherDecodeOffChainVoteData lbs anchorType of + Left _err -> Nothing -- Don't fail, just return Nothing (will set is_valid = false) + Right res -> Just res + else Nothing -- Not valid JSON, so can't parse as CIP + pure (ocvd, decodedValue, hsh, mWarning, isValidJson) httpGetBytes :: Http.Manager -> diff --git a/cardano-db-sync/src/Cardano/DbSync/Types.hs b/cardano-db-sync/src/Cardano/DbSync/Types.hs index 3b1ae88a3..ae50eb596 100644 --- a/cardano-db-sync/src/Cardano/DbSync/Types.hs +++ b/cardano-db-sync/src/Cardano/DbSync/Types.hs @@ -195,6 +195,7 @@ data SimplifiedOffChainVoteData = SimplifiedOffChainVoteData , sovaContentType :: !(Maybe ByteString) , sovaOffChainVoteData :: !(Maybe Vote.OffChainVoteData) , sovaWarning :: !(Maybe Text) + , sovaIsValidJson :: !Bool } data Retry = Retry diff --git a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs index a411cf6e4..b3b13d980 100644 --- a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs +++ b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs @@ -8,8 +8,11 @@ import Cardano.DbSync.Error (runOrThrowIO) import Cardano.DbSync.OffChain.Http (parseAndValidateVoteData) import Cardano.Prelude hiding ((%)) import qualified Data.Aeson as Aeson +import qualified Data.Aeson.Key as AesonKey +import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS +import qualified Data.Text as Text import Hedgehog tests :: IO Bool @@ -19,10 +22,12 @@ tests = "Cardano.DbSync.OffChain.Vote" [ ("parseAndValidateVoteData handles invalid CIP format", prop_parseInvalidCIPFormat) , ("parseAndValidateVoteData handles valid JSON but invalid structure", prop_parseValidJsonInvalidStructure) + , ("parseAndValidateVoteData handles unparseable JSON", prop_parseUnparseableJson) ] -- | Test that we can parse JSON with incorrect field types (e.g., doNotList as string instead of bool) -- This is based on the issue https://github.com/IntersectMBO/cardano-db-sync/issues/1995 +-- Scenario: Valid JSON but invalid CIP schema -> is_valid = false prop_parseInvalidCIPFormat :: Property prop_parseInvalidCIPFormat = withTests 1 $ property $ do -- Read the test file with invalid doNotList field (string instead of bool) @@ -32,10 +37,11 @@ prop_parseInvalidCIPFormat = withTests 1 $ property $ do -- Run the parser result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData fileContent lbsContent Nothing DB.DrepAnchor Nothing - let (mocvd, val, _hash, _warning) = result + let (mocvd, val, _hash, _warning, isValidJson) = result -- Should succeed in parsing generic JSON annotate "Successfully parsed as generic JSON" + assert isValidJson -- Should fail to parse into strongly-typed OffChainVoteData assert $ isNothing mocvd @@ -50,6 +56,7 @@ prop_parseInvalidCIPFormat = withTests 1 $ property $ do failure -- | Test with completely valid JSON but not matching the CIP schema +-- Scenario: Valid JSON but invalid CIP schema -> is_valid = false prop_parseValidJsonInvalidStructure :: Property prop_parseValidJsonInvalidStructure = property $ do -- Create a valid JSON that doesn't match CIP schema at all @@ -60,8 +67,46 @@ prop_parseValidJsonInvalidStructure = property $ do -- This should succeed because it's valid JSON, just not matching the schema result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData bs lbs Nothing DB.DrepAnchor Nothing - let (mocvd, _val, _hash, _warning) = result + let (mocvd, _val, _hash, _warning, isValidJson) = result annotate "Successfully parsed generic JSON" + assert isValidJson -- Should not parse into OffChainVoteData assert $ isNothing mocvd + +-- | Test with completely unparseable content (not valid JSON at all) +-- Scenario: Invalid JSON but hash matches -> is_valid = NULL +prop_parseUnparseableJson :: Property +prop_parseUnparseableJson = property $ do + -- Create content that is not valid JSON + let notJson = "This is just plain text, not JSON at all!" + bs = encodeUtf8 notJson + lbs = LBS.fromStrict bs + + -- This should not fail, but instead return an error message in the JSON field + result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData bs lbs Nothing DB.DrepAnchor Nothing + + let (mocvd, val, _hash, _warning, isValidJson) = result + + annotate "Content is not valid JSON" + -- Should flag as invalid JSON + assert $ not isValidJson + + -- Should not parse into OffChainVoteData + assert $ isNothing mocvd + + -- Should have an error message in the JSON value + case val of + Aeson.Object obj -> do + annotate "Has error message object" + -- Check that error field exists + case KeyMap.lookup (AesonKey.fromString "error") obj of + Just (Aeson.String msg) -> do + annotate $ "Error message: " <> show msg + assert $ Text.isInfixOf "not valid JSON" msg + _ -> do + annotate "Expected error field with string value" + failure + _ -> do + annotate "Expected JSON object with error message" + failure diff --git a/cardano-db/src/Cardano/Db/Schema/Core/OffChain.hs b/cardano-db/src/Cardano/Db/Schema/Core/OffChain.hs index d7c345fec..a4e539a4d 100644 --- a/cardano-db/src/Cardano/Db/Schema/Core/OffChain.hs +++ b/cardano-db/src/Cardano/Db/Schema/Core/OffChain.hs @@ -90,7 +90,15 @@ offChainPoolFetchErrorEncoder = -- | -- Table Name: off_chain_vote_data --- Description: +-- Description: Stores off-chain voting anchor data with validation status +-- +-- The is_valid column indicates the parsing status: +-- • TRUE: Content is valid JSON AND conforms to CIP schema +-- All related tables are populated +-- • FALSE: Content is valid JSON BUT does not conform to CIP schema +-- The json column contains the actual JSON, but related tables are empty +-- • NULL: Content is not valid JSON at all +-- The json column contains an error message, bytes column has raw data data OffChainVoteData = OffChainVoteData { offChainVoteDataVotingAnchorId :: !Id.VotingAnchorId -- noreference , offChainVoteDataHash :: !ByteString diff --git a/doc/schema.md b/doc/schema.md index 14c6481bd..133a93518 100644 --- a/doc/schema.md +++ b/doc/schema.md @@ -1014,7 +1014,14 @@ A table containing pool offchain data fetch errors. ### `off_chain_vote_data` -The table with the offchain metadata related to Vote Anchors. It accepts metadata in a more lenient way than what's decribed in CIP-100. New in 13.2-Conway. +Stores off-chain voting anchor data with validation status. The table accepts metadata in a more lenient way than what's described in CIP-100. Only data with hash matches are stored here; hash mismatches are stored in off_chain_vote_fetch_error for retry. + +The is_valid column indicates the parsing status: + • TRUE: Content is valid JSON AND conforms to CIP-100 schema. All related fields (language, comment) and related tables (off_chain_vote_gov_action_data, off_chain_vote_drep_data, off_chain_vote_author, off_chain_vote_reference, off_chain_vote_external_update) are populated. + • FALSE: Hash matches and content is valid JSON BUT does not conform to CIP-100 schema. The json column contains the actual JSON, but language/comment fields and related tables remain empty. + • NULL: Hash matches but content is not valid JSON at all. The json column contains an error message, bytes column has raw data. Language/comment fields and related tables remain empty. + +New in 13.2-Conway. * Primary Id: `id` @@ -1028,7 +1035,7 @@ The table with the offchain metadata related to Vote Anchors. It accepts metadat | `json` | jsonb | The payload as JSON. | | `bytes` | bytea | The raw bytes of the payload. | | `warning` | string | A warning that occured while validating the metadata. | -| `is_valid` | boolean | False if the data is found invalid. db-sync leaves this field null since it normally populates off_chain_vote_fetch_error for invalid data. It can be used manually to mark some metadata invalid by clients. | +| `is_valid` | boolean | Indicates validation status: TRUE for valid JSON conforming to CIP-100 schema, FALSE for valid JSON not conforming to CIP-100 schema, NULL for content that is not valid JSON at all. | ### `off_chain_vote_gov_action_data` From ac2b5f080ee4d6ea8ed02a459183520ea2defed5 Mon Sep 17 00:00:00 2001 From: Cmdv Date: Fri, 28 Nov 2025 13:19:02 +0000 Subject: [PATCH 5/6] change file location --- cardano-db-sync/cardano-db-sync.cabal | 1 + cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs | 2 +- .../test/testfiles/invalid-vote-doNotList.jsonld | 0 flake.nix | 2 ++ 4 files changed, 4 insertions(+), 1 deletion(-) rename {cardano-chain-gen => cardano-db-sync}/test/testfiles/invalid-vote-doNotList.jsonld (100%) diff --git a/cardano-db-sync/cardano-db-sync.cabal b/cardano-db-sync/cardano-db-sync.cabal index 1e9329f61..a3a6c49ef 100644 --- a/cardano-db-sync/cardano-db-sync.cabal +++ b/cardano-db-sync/cardano-db-sync.cabal @@ -16,6 +16,7 @@ category: Cryptocurrency build-type: Custom extra-source-files: CHANGELOG.md schema/*.sql + test/testfiles/*.jsonld custom-setup setup-depends: diff --git a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs index b3b13d980..fbebda5c5 100644 --- a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs +++ b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs @@ -31,7 +31,7 @@ tests = prop_parseInvalidCIPFormat :: Property prop_parseInvalidCIPFormat = withTests 1 $ property $ do -- Read the test file with invalid doNotList field (string instead of bool) - fileContent <- liftIO $ BS.readFile "../cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld" + fileContent <- liftIO $ BS.readFile "test/testfiles/invalid-vote-doNotList.jsonld" let lbsContent = LBS.fromStrict fileContent -- Run the parser diff --git a/cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld b/cardano-db-sync/test/testfiles/invalid-vote-doNotList.jsonld similarity index 100% rename from cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld rename to cardano-db-sync/test/testfiles/invalid-vote-doNotList.jsonld diff --git a/flake.nix b/flake.nix index 734c9d5a8..dc07f879b 100644 --- a/flake.nix +++ b/flake.nix @@ -218,6 +218,8 @@ # Override extra-source-files packages.cardano-db-sync.package.extraSrcFiles = [ "../schema/*.sql" ]; + packages.cardano-db-sync.components.tests.test.extraSrcFiles = + [ "test/testfiles/*.jsonld" ]; packages.cardano-db.package.extraSrcFiles = ["../config/pgpass-testnet"]; packages.cardano-db.components.tests.schema-rollback.extraSrcFiles = From 19a7e0446128e016c4596cd72067d353b0d9ee47 Mon Sep 17 00:00:00 2001 From: Cmdv Date: Tue, 2 Dec 2025 09:53:55 +0000 Subject: [PATCH 6/6] add more generic jsonld CIP-100 test files --- .../test/Cardano/DbSync/OffChain/VoteTest.hs | 62 +++++++--- .../testfiles/invalid-vote-doNotList.jsonld | 110 ------------------ .../invalid-vote-malformed-json.jsonld | 4 + .../testfiles/invalid-vote-type-error.jsonld | 68 +++++++++++ .../invalid-vote-wrong-structure.jsonld | 17 +++ .../test/testfiles/valid-vote-minimal.jsonld | 68 +++++++++++ 6 files changed, 205 insertions(+), 124 deletions(-) delete mode 100644 cardano-db-sync/test/testfiles/invalid-vote-doNotList.jsonld create mode 100644 cardano-db-sync/test/testfiles/invalid-vote-malformed-json.jsonld create mode 100644 cardano-db-sync/test/testfiles/invalid-vote-type-error.jsonld create mode 100644 cardano-db-sync/test/testfiles/invalid-vote-wrong-structure.jsonld create mode 100644 cardano-db-sync/test/testfiles/valid-vote-minimal.jsonld diff --git a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs index fbebda5c5..e53e40164 100644 --- a/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs +++ b/cardano-db-sync/test/Cardano/DbSync/OffChain/VoteTest.hs @@ -20,18 +20,54 @@ tests = checkParallel $ Group "Cardano.DbSync.OffChain.Vote" - [ ("parseAndValidateVoteData handles invalid CIP format", prop_parseInvalidCIPFormat) + [ ("parseAndValidateVoteData handles valid CIP-119 format", prop_parseValidCIPFormat) + , ("parseAndValidateVoteData handles invalid CIP format (type error)", prop_parseInvalidCIPFormat) , ("parseAndValidateVoteData handles valid JSON but invalid structure", prop_parseValidJsonInvalidStructure) , ("parseAndValidateVoteData handles unparseable JSON", prop_parseUnparseableJson) ] +-- | Test that we can parse valid CIP-119 format correctly +-- Scenario: Valid JSON + Valid CIP schema -> is_valid = true +prop_parseValidCIPFormat :: Property +prop_parseValidCIPFormat = withTests 1 $ property $ do + -- Read the test file with valid CIP-119 format + fileContent <- liftIO $ BS.readFile "test/testfiles/valid-vote-minimal.jsonld" + let lbsContent = LBS.fromStrict fileContent + + -- Run the parser + result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData fileContent lbsContent Nothing DB.DrepAnchor Nothing + + let (mocvd, val, _hash, _warning, isValidJson) = result + + -- Should succeed in parsing generic JSON + annotate "Successfully parsed as generic JSON" + assert isValidJson + + -- Should successfully parse into strongly-typed OffChainVoteData + case mocvd of + Just _ocvd -> do + annotate "Successfully parsed into OffChainVoteData" + success + Nothing -> do + annotate "Failed to parse into OffChainVoteData" + failure + + -- Should have valid Aeson.Value + case Aeson.toJSON val of + Aeson.Object _obj -> do + annotate "Has valid JSON object" + success + _ -> do + annotate "Expected JSON object" + failure + -- | Test that we can parse JSON with incorrect field types (e.g., doNotList as string instead of bool) -- This is based on the issue https://github.com/IntersectMBO/cardano-db-sync/issues/1995 -- Scenario: Valid JSON but invalid CIP schema -> is_valid = false prop_parseInvalidCIPFormat :: Property prop_parseInvalidCIPFormat = withTests 1 $ property $ do -- Read the test file with invalid doNotList field (string instead of bool) - fileContent <- liftIO $ BS.readFile "test/testfiles/invalid-vote-doNotList.jsonld" + fileContent <- liftIO $ BS.readFile "test/testfiles/invalid-vote-type-error.jsonld" let lbsContent = LBS.fromStrict fileContent -- Run the parser @@ -58,14 +94,13 @@ prop_parseInvalidCIPFormat = withTests 1 $ property $ do -- | Test with completely valid JSON but not matching the CIP schema -- Scenario: Valid JSON but invalid CIP schema -> is_valid = false prop_parseValidJsonInvalidStructure :: Property -prop_parseValidJsonInvalidStructure = property $ do - -- Create a valid JSON that doesn't match CIP schema at all - let invalidJson = "{\"randomField\": \"value\", \"number\": 42}" - bs = encodeUtf8 invalidJson - lbs = LBS.fromStrict bs +prop_parseValidJsonInvalidStructure = withTests 1 $ property $ do + -- Read the test file with valid JSON but wrong structure + fileContent <- liftIO $ BS.readFile "test/testfiles/invalid-vote-wrong-structure.jsonld" + let lbsContent = LBS.fromStrict fileContent -- This should succeed because it's valid JSON, just not matching the schema - result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData bs lbs Nothing DB.DrepAnchor Nothing + result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData fileContent lbsContent Nothing DB.DrepAnchor Nothing let (mocvd, _val, _hash, _warning, isValidJson) = result @@ -77,14 +112,13 @@ prop_parseValidJsonInvalidStructure = property $ do -- | Test with completely unparseable content (not valid JSON at all) -- Scenario: Invalid JSON but hash matches -> is_valid = NULL prop_parseUnparseableJson :: Property -prop_parseUnparseableJson = property $ do - -- Create content that is not valid JSON - let notJson = "This is just plain text, not JSON at all!" - bs = encodeUtf8 notJson - lbs = LBS.fromStrict bs +prop_parseUnparseableJson = withTests 1 $ property $ do + -- Read the test file with malformed JSON + fileContent <- liftIO $ BS.readFile "test/testfiles/invalid-vote-malformed-json.jsonld" + let lbsContent = LBS.fromStrict fileContent -- This should not fail, but instead return an error message in the JSON field - result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData bs lbs Nothing DB.DrepAnchor Nothing + result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData fileContent lbsContent Nothing DB.DrepAnchor Nothing let (mocvd, val, _hash, _warning, isValidJson) = result diff --git a/cardano-db-sync/test/testfiles/invalid-vote-doNotList.jsonld b/cardano-db-sync/test/testfiles/invalid-vote-doNotList.jsonld deleted file mode 100644 index 5567e2d10..000000000 --- a/cardano-db-sync/test/testfiles/invalid-vote-doNotList.jsonld +++ /dev/null @@ -1,110 +0,0 @@ -{ - "@context": { - "CIP100": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", - "CIP119": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0119/README.md#", - "hashAlgorithm": "CIP100:hashAlgorithm", - "body": { - "@id": "CIP119:body", - "@context": { - "references": { - "@id": "CIP119:references", - "@container": "@set", - "@context": { - "GovernanceMetadata": "CIP100:GovernanceMetadataReference", - "Identity": "CIP119:IdentityReference", - "Link": "CIP119:LinkReference", - "Other": "CIP100:OtherReference", - "label": "CIP100:reference-label", - "uri": "CIP100:reference-uri", - "referenceHash": { - "@id": "CIP119:referenceHash", - "@context": { - "hashDigest": "CIP119:hashDigest", - "hashAlgorithm": "CIP100:hashAlgorithm" - } - } - } - }, - "paymentAddress": "CIP119:paymentAddress", - "givenName": "CIP119:givenName", - "image": "CIP119:image", - "objectives": "CIP119:objectives", - "motivations": "CIP119:motivations", - "qualifications": "CIP119:qualifications", - "doNotList": "CIP119:doNotList" - } - }, - "authors": { - "@id": "CIP100:authors", - "@container": "@set", - "@context": { - "name": "http://xmlns.com/foaf/0.1/name", - "witness": { - "@id": "CIP100:witness", - "@context": { - "witnessAlgorithm": "CIP100:witnessAlgorithm", - "publicKey": "CIP100:publicKey", - "signature": "CIP100:signature" - } - } - } - } - }, - "authors": [], - "hashAlgorithm": "blake2b-256", - "body": { - "doNotList": "false", - "givenName": "CaFi", - "motivations": "With a strong belief in the potential of blockchain technology and the development of the Cardano ecosystem, the CaFi team wishes to contribute to innovation and progress through supporting community projects and initiatives. The main motivation of CaFi is to make the Catalyst more accessible to Vietnamese, while creating an environment where people can learn, share and develop their skills in writing proposals, evaluating and participating in the democratic voting process.\n\nThe CaFi team understands that the blockchain ecosystem in general and Cardano in particular are in the development stage and require community participation to achieve sustainability and success. By guiding the community to participate in Catalyst, CaFi wants to promote creativity, innovation and help valuable ideas receive the necessary support to grow.", - "objectives": "1. Community Support: CaFi team focuses on helping the Vietnam community actively participate in the Catalyst through consulting and training activities on proposal writing, proposal evaluation and voting.\n\n2. Promoting innovation: We wishe to support and develop innovative projects in the Cardano ecosystem, especially those with practical value and sustainable development potential.\n\n3. Building the dRep role: We aim to become a responsible and reputable dRep in Cardano's decentralized governance model, contributing to the long-term development of the ecosystem.\n\n4. Community development: We want to build a dynamic, creative and self-directed community in the Blockchain ecosystem through participating in funding opportunities from Catalyst.\n\nWith the experience gained, CaFi team believes that working with the community will bring many values, while helping projects from Vietnam to have better access to resources from Catalyst to develop strongly on the Cardano platform.", - "paymentAddress": "addr1q9mrk85srqxk88239chlxd73ct2s4nl20jzfwgu9k7tzxe60knquflpw8mtql842hyundh3vdy4e6zhw8f6z7afxjvys34qg2p", - "qualifications": "1. Do Viet Cuong\n● Master, high school teacher\n● Blockchain research since 2017\n● Cardano Vietnam community manager\n● Co-host of 9 Catalyst funded proposals\n● Admin of youtube channel, podcast, discussion group about Cardano and Catalyst project\n● Certified CBCA\n\n2. Tu Nguyen\n● Vietnam Drep Pioneer Program #1\n● FIMI Translator team\n● CBCA Alpha Program 2, Certificate ID: 64898843661a2dbd9402d27b\n● CBCA Course, Certificate ID: 5351588\n● Catalyst Funded Proposer\n● Admin of the Catalyst discussion group\n\n3. Duc Nguyen\n● Cardano Blockchain Researcher from 2018 to present: Research and develop project related to Cardano Blockchain\n● Skill: Financial analysis; IT system management, database; Graphic design\n● Language: Vietnamese; English\n\n4. Hoang Phuong Linh\n● Translation and research in Cardano Blockchain since 2017\n● Marketer, content creator, trainer, event organizer, community connector\n● Certified CBCA\n● Catalyst Funded Proposer\n● Languages: Vietnamese, English", - "references": [ - { - "@type": "Link", - "label": "Catalyst Discussion Group", - "uri": "https://t.me/Fimi_PA" - }, - { - "@type": "Link", - "label": "Youtube Channel", - "uri": "https://www.youtube.com/@taichinh-taman5516" - }, - { - "@type": "Link", - "label": "General Cardano Discussion Group", - "uri": "https://t.me/StakingADA" - }, - { - "@type": "Link", - "label": "Cardano Podcast Channel", - "uri": "https://t.me/fimi_podcast" - }, - { - "@type": "Link", - "label": "IOG Reseacher Paper Library in Vietnamese Version", - "uri": "https://cardano.vn/docs/Cardano360/TechDocs/intro/" - }, - { - "@type": "Identity", - "label": "Do Viet Cuong", - "uri": "https://t.me/dovietcuong" - }, - { - "@type": "Identity", - "label": "Tu Nguyen", - "uri": "https://t.me/Tulibra" - }, - { - "@type": "Identity", - "label": "Duc Nguyen", - "uri": "https://www.linkedin.com/in/minh-983aa4241/" - }, - { - "@type": "Identity", - "label": "Hoang Phuong Linh", - "uri": "https://www.linkedin.com/in/phuong-linh-hoang-a318bb138/" - } - ] - } -} \ No newline at end of file diff --git a/cardano-db-sync/test/testfiles/invalid-vote-malformed-json.jsonld b/cardano-db-sync/test/testfiles/invalid-vote-malformed-json.jsonld new file mode 100644 index 000000000..ad07c7392 --- /dev/null +++ b/cardano-db-sync/test/testfiles/invalid-vote-malformed-json.jsonld @@ -0,0 +1,4 @@ +{ + "givenName": "Test DRep", + "objectives": "Missing closing quote and brace + "motivations": "This JSON is malformed diff --git a/cardano-db-sync/test/testfiles/invalid-vote-type-error.jsonld b/cardano-db-sync/test/testfiles/invalid-vote-type-error.jsonld new file mode 100644 index 000000000..c4288706d --- /dev/null +++ b/cardano-db-sync/test/testfiles/invalid-vote-type-error.jsonld @@ -0,0 +1,68 @@ +{ + "@context": { + "CIP100": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + "CIP119": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0119/README.md#", + "hashAlgorithm": "CIP100:hashAlgorithm", + "body": { + "@id": "CIP119:body", + "@context": { + "references": { + "@id": "CIP119:references", + "@container": "@set", + "@context": { + "GovernanceMetadata": "CIP100:GovernanceMetadataReference", + "Identity": "CIP119:IdentityReference", + "Link": "CIP119:LinkReference", + "Other": "CIP100:OtherReference", + "label": "CIP100:reference-label", + "uri": "CIP100:reference-uri" + } + }, + "paymentAddress": "CIP119:paymentAddress", + "givenName": "CIP119:givenName", + "image": "CIP119:image", + "objectives": "CIP119:objectives", + "motivations": "CIP119:motivations", + "qualifications": "CIP119:qualifications", + "doNotList": "CIP119:doNotList" + } + }, + "authors": { + "@id": "CIP100:authors", + "@container": "@set", + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "witness": { + "@id": "CIP100:witness", + "@context": { + "witnessAlgorithm": "CIP100:witnessAlgorithm", + "publicKey": "CIP100:publicKey", + "signature": "CIP100:signature" + } + } + } + } + }, + "authors": [], + "hashAlgorithm": "blake2b-256", + "body": { + "givenName": "Test DRep", + "doNotList": "false", + "motivations": "Test motivations for DRep registration", + "objectives": "Test objectives for governance participation", + "qualifications": "Test qualifications and experience", + "paymentAddress": "addr1test000000000000000000000000000000000000000000000000000000", + "references": [ + { + "@type": "Link", + "label": "Test Website", + "uri": "https://example.com/test" + }, + { + "@type": "Identity", + "label": "Test Identity", + "uri": "https://example.com/identity" + } + ] + } +} diff --git a/cardano-db-sync/test/testfiles/invalid-vote-wrong-structure.jsonld b/cardano-db-sync/test/testfiles/invalid-vote-wrong-structure.jsonld new file mode 100644 index 000000000..6fee94e85 --- /dev/null +++ b/cardano-db-sync/test/testfiles/invalid-vote-wrong-structure.jsonld @@ -0,0 +1,17 @@ +{ + "title": "Some Random Document", + "description": "This is valid JSON but doesn't match CIP-100/119 schema at all", + "version": "1.0", + "data": { + "field1": "value1", + "field2": 42, + "field3": true, + "nested": { + "items": [1, 2, 3], + "metadata": { + "timestamp": "2025-01-01T00:00:00Z" + } + } + }, + "tags": ["test", "example", "invalid-schema"] +} diff --git a/cardano-db-sync/test/testfiles/valid-vote-minimal.jsonld b/cardano-db-sync/test/testfiles/valid-vote-minimal.jsonld new file mode 100644 index 000000000..b6a4701ca --- /dev/null +++ b/cardano-db-sync/test/testfiles/valid-vote-minimal.jsonld @@ -0,0 +1,68 @@ +{ + "@context": { + "CIP100": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + "CIP119": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0119/README.md#", + "hashAlgorithm": "CIP100:hashAlgorithm", + "body": { + "@id": "CIP119:body", + "@context": { + "references": { + "@id": "CIP119:references", + "@container": "@set", + "@context": { + "GovernanceMetadata": "CIP100:GovernanceMetadataReference", + "Identity": "CIP119:IdentityReference", + "Link": "CIP119:LinkReference", + "Other": "CIP100:OtherReference", + "label": "CIP100:reference-label", + "uri": "CIP100:reference-uri" + } + }, + "paymentAddress": "CIP119:paymentAddress", + "givenName": "CIP119:givenName", + "image": "CIP119:image", + "objectives": "CIP119:objectives", + "motivations": "CIP119:motivations", + "qualifications": "CIP119:qualifications", + "doNotList": "CIP119:doNotList" + } + }, + "authors": { + "@id": "CIP100:authors", + "@container": "@set", + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "witness": { + "@id": "CIP100:witness", + "@context": { + "witnessAlgorithm": "CIP100:witnessAlgorithm", + "publicKey": "CIP100:publicKey", + "signature": "CIP100:signature" + } + } + } + } + }, + "authors": [], + "hashAlgorithm": "blake2b-256", + "body": { + "givenName": "Test DRep", + "doNotList": false, + "motivations": "Test motivations for DRep registration", + "objectives": "Test objectives for governance participation", + "qualifications": "Test qualifications and experience", + "paymentAddress": "addr1test000000000000000000000000000000000000000000000000000000", + "references": [ + { + "@type": "Link", + "label": "Test Website", + "uri": "https://example.com/test" + }, + { + "@type": "Identity", + "label": "Test Identity", + "uri": "https://example.com/identity" + } + ] + } +}