Skip to content

Commit c200d1d

Browse files
committed
1995 fix offchain vote data
1 parent fce8305 commit c200d1d

File tree

9 files changed

+236
-32
lines changed

9 files changed

+236
-32
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"@context": {
3+
"CIP100": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#",
4+
"CIP119": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0119/README.md#",
5+
"hashAlgorithm": "CIP100:hashAlgorithm",
6+
"body": {
7+
"@id": "CIP119:body",
8+
"@context": {
9+
"references": {
10+
"@id": "CIP119:references",
11+
"@container": "@set",
12+
"@context": {
13+
"GovernanceMetadata": "CIP100:GovernanceMetadataReference",
14+
"Identity": "CIP119:IdentityReference",
15+
"Link": "CIP119:LinkReference",
16+
"Other": "CIP100:OtherReference",
17+
"label": "CIP100:reference-label",
18+
"uri": "CIP100:reference-uri",
19+
"referenceHash": {
20+
"@id": "CIP119:referenceHash",
21+
"@context": {
22+
"hashDigest": "CIP119:hashDigest",
23+
"hashAlgorithm": "CIP100:hashAlgorithm"
24+
}
25+
}
26+
}
27+
},
28+
"paymentAddress": "CIP119:paymentAddress",
29+
"givenName": "CIP119:givenName",
30+
"image": "CIP119:image",
31+
"objectives": "CIP119:objectives",
32+
"motivations": "CIP119:motivations",
33+
"qualifications": "CIP119:qualifications",
34+
"doNotList": "CIP119:doNotList"
35+
}
36+
},
37+
"authors": {
38+
"@id": "CIP100:authors",
39+
"@container": "@set",
40+
"@context": {
41+
"name": "http://xmlns.com/foaf/0.1/name",
42+
"witness": {
43+
"@id": "CIP100:witness",
44+
"@context": {
45+
"witnessAlgorithm": "CIP100:witnessAlgorithm",
46+
"publicKey": "CIP100:publicKey",
47+
"signature": "CIP100:signature"
48+
}
49+
}
50+
}
51+
}
52+
},
53+
"authors": [],
54+
"hashAlgorithm": "blake2b-256",
55+
"body": {
56+
"doNotList": "false",
57+
"givenName": "CaFi",
58+
"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.",
59+
"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.",
60+
"paymentAddress": "addr1q9mrk85srqxk88239chlxd73ct2s4nl20jzfwgu9k7tzxe60knquflpw8mtql842hyundh3vdy4e6zhw8f6z7afxjvys34qg2p",
61+
"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",
62+
"references": [
63+
{
64+
"@type": "Link",
65+
"label": "Catalyst Discussion Group",
66+
"uri": "https://t.me/Fimi_PA"
67+
},
68+
{
69+
"@type": "Link",
70+
"label": "Youtube Channel",
71+
"uri": "https://www.youtube.com/@taichinh-taman5516"
72+
},
73+
{
74+
"@type": "Link",
75+
"label": "General Cardano Discussion Group",
76+
"uri": "https://t.me/StakingADA"
77+
},
78+
{
79+
"@type": "Link",
80+
"label": "Cardano Podcast Channel",
81+
"uri": "https://t.me/fimi_podcast"
82+
},
83+
{
84+
"@type": "Link",
85+
"label": "IOG Reseacher Paper Library in Vietnamese Version",
86+
"uri": "https://cardano.vn/docs/Cardano360/TechDocs/intro/"
87+
},
88+
{
89+
"@type": "Identity",
90+
"label": "Do Viet Cuong",
91+
"uri": "https://t.me/dovietcuong"
92+
},
93+
{
94+
"@type": "Identity",
95+
"label": "Tu Nguyen",
96+
"uri": "https://t.me/Tulibra"
97+
},
98+
{
99+
"@type": "Identity",
100+
"label": "Duc Nguyen",
101+
"uri": "https://www.linkedin.com/in/minh-983aa4241/"
102+
},
103+
{
104+
"@type": "Identity",
105+
"label": "Hoang Phuong Linh",
106+
"uri": "https://www.linkedin.com/in/phuong-linh-hoang-a318bb138/"
107+
}
108+
]
109+
}
110+
}

cardano-db-sync/app/http-get-json-metadata.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ runGetVote :: Text.Text -> Maybe VoteMetaHash -> DB.AnchorType -> IO ()
143143
runGetVote file mExpectedHash vtype = do
144144
respBs <- BS.readFile (Text.unpack file)
145145
let respLBs = fromStrict respBs
146-
(ocvd, val, hsh, mWarning) <- runOrThrowIO $ runExceptT $ parseAndValidateVoteData respBs respLBs mExpectedHash vtype Nothing
147-
print ocvd
146+
(mocvd, val, hsh, mWarning) <- runOrThrowIO $ runExceptT $ parseAndValidateVoteData respBs respLBs mExpectedHash vtype Nothing
147+
print mocvd
148148
print val
149149
print $ bsBase16Encode hsh
150150
print mWarning

cardano-db-sync/cardano-db-sync.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ test-suite test
334334
Cardano.DbSync.Era.Shelley.Generic.ScriptDataTest
335335
Cardano.DbSync.Era.Shelley.Generic.ScriptTest
336336
Cardano.DbSync.Gen
337+
Cardano.DbSync.OffChain.VoteTest
337338
Cardano.DbSync.Util.AddressTest
338339
Cardano.DbSync.Util.Bech32Test
339340
Cardano.DbSync.Util.CborTest

cardano-db-sync/src/Cardano/DbSync/OffChain.hs

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -349,27 +349,48 @@ fetchOffChainVoteData gateways time oVoteWorkQ =
349349
convert eres =
350350
case eres of
351351
Right sVoteData ->
352-
let
353-
offChainData = sovaOffChainVoteData sVoteData
354-
minimalBody = Vote.getMinimalBody offChainData
355-
vdt =
356-
DB.OffChainVoteData
357-
{ DB.offChainVoteDataLanguage = Vote.getLanguage offChainData
358-
, DB.offChainVoteDataComment = Vote.textValue <$> Vote.comment minimalBody
359-
, DB.offChainVoteDataBytes = sovaBytes sVoteData
360-
, DB.offChainVoteDataHash = sovaHash sVoteData
361-
, DB.offChainVoteDataJson = sovaJson sVoteData
362-
, DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ
363-
, DB.offChainVoteDataWarning = sovaWarning sVoteData
364-
, DB.offChainVoteDataIsValid = Nothing
365-
}
366-
gaF ocvdId = mkGovAction ocvdId offChainData
367-
drepF ocvdId = mkDrep ocvdId offChainData
368-
authorsF ocvdId = map (mkAuthor ocvdId) $ Vote.getAuthors offChainData
369-
referencesF ocvdId = map (mkReference ocvdId) $ mListToList $ Vote.references minimalBody
370-
externalUpdatesF ocvdId = map (mkexternalUpdates ocvdId) $ mListToList $ Vote.externalUpdates minimalBody
371-
in
372-
OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF)
352+
case sovaOffChainVoteData sVoteData of
353+
Just offChainData ->
354+
let
355+
minimalBody = Vote.getMinimalBody offChainData
356+
vdt =
357+
DB.OffChainVoteData
358+
{ DB.offChainVoteDataLanguage = Vote.getLanguage offChainData
359+
, DB.offChainVoteDataComment = Vote.textValue <$> Vote.comment minimalBody
360+
, DB.offChainVoteDataBytes = sovaBytes sVoteData
361+
, DB.offChainVoteDataHash = sovaHash sVoteData
362+
, DB.offChainVoteDataJson = sovaJson sVoteData
363+
, DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ
364+
, DB.offChainVoteDataWarning = sovaWarning sVoteData
365+
, DB.offChainVoteDataIsValid = Just True
366+
}
367+
gaF ocvdId = mkGovAction ocvdId offChainData
368+
drepF ocvdId = mkDrep ocvdId offChainData
369+
authorsF ocvdId = map (mkAuthor ocvdId) $ Vote.getAuthors offChainData
370+
referencesF ocvdId = map (mkReference ocvdId) $ mListToList $ Vote.references minimalBody
371+
externalUpdatesF ocvdId = map (mkexternalUpdates ocvdId) $ mListToList $ Vote.externalUpdates minimalBody
372+
in
373+
OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF)
374+
Nothing ->
375+
let
376+
vdt =
377+
DB.OffChainVoteData
378+
{ DB.offChainVoteDataLanguage = ""
379+
, DB.offChainVoteDataComment = Nothing
380+
, DB.offChainVoteDataBytes = sovaBytes sVoteData
381+
, DB.offChainVoteDataHash = sovaHash sVoteData
382+
, DB.offChainVoteDataJson = sovaJson sVoteData
383+
, DB.offChainVoteDataVotingAnchorId = oVoteWqReferenceId oVoteWorkQ
384+
, DB.offChainVoteDataWarning = sovaWarning sVoteData
385+
, DB.offChainVoteDataIsValid = Just False
386+
}
387+
gaF _ = Nothing
388+
drepF _ = Nothing
389+
authorsF _ = []
390+
referencesF _ = []
391+
externalUpdatesF _ = []
392+
in
393+
OffChainVoteResultMetadata vdt (OffChainVoteAccessors gaF drepF authorsF referencesF externalUpdatesF)
373394
Left err ->
374395
OffChainVoteResultError $
375396
DB.OffChainVoteFetchError

cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,35 +108,38 @@ httpGetOffChainVoteDataSingle vurl metaHash anchorType = do
108108
let req = httpGetBytes manager request 3000000 3000000 url
109109
httpRes <- handleExceptT (convertHttpException url) req
110110
(respBS, respLBS, mContentType) <- hoistEither httpRes
111-
(ocvd, decodedValue, metadataHash, mWarning) <- parseAndValidateVoteData respBS respLBS metaHash anchorType (Just $ OffChainVoteUrl vurl)
111+
(mocvd, decodedValue, metadataHash, mWarning) <- parseAndValidateVoteData respBS respLBS metaHash anchorType (Just $ OffChainVoteUrl vurl)
112112
pure $
113113
SimplifiedOffChainVoteData
114114
{ sovaHash = metadataHash
115115
, sovaBytes = respBS
116116
, sovaJson = Text.decodeUtf8 $ LBS.toStrict (Aeson.encode decodedValue)
117117
, sovaContentType = mContentType
118-
, sovaOffChainVoteData = ocvd
118+
, sovaOffChainVoteData = mocvd
119119
, sovaWarning = mWarning
120120
}
121121
where
122122
url = OffChainVoteUrl vurl
123123

124-
parseAndValidateVoteData :: ByteString -> LBS.ByteString -> Maybe VoteMetaHash -> DB.AnchorType -> Maybe OffChainUrlType -> ExceptT OffChainFetchError IO (Vote.OffChainVoteData, Aeson.Value, ByteString, Maybe Text)
124+
parseAndValidateVoteData :: ByteString -> LBS.ByteString -> Maybe VoteMetaHash -> DB.AnchorType -> Maybe OffChainUrlType -> ExceptT OffChainFetchError IO (Maybe Vote.OffChainVoteData, Aeson.Value, ByteString, Maybe Text)
125125
parseAndValidateVoteData bs lbs metaHash anchorType murl = do
126126
let metadataHash = Crypto.digest (Proxy :: Proxy Crypto.Blake2b_256) bs
127+
-- First check if hash matches - this is critical and must fail if mismatch
127128
(hsh, mWarning) <- case unVoteMetaHash <$> metaHash of
128129
Just expectedMetaHashBs
129130
| metadataHash /= expectedMetaHashBs ->
130131
left $ OCFErrHashMismatch murl (renderByteArray expectedMetaHashBs) (renderByteArray metadataHash)
131132
_ -> pure (metadataHash, Nothing)
133+
-- Hash matches, now decode as generic JSON (this should still fail if not valid JSON)
132134
decodedValue <-
133135
case Aeson.eitherDecode' @Aeson.Value lbs of
134136
Left err -> left $ OCFErrJsonDecodeFail murl (Text.pack err)
135137
Right res -> pure res
136-
ocvd <-
137-
case Vote.eitherDecodeOffChainVoteData lbs anchorType of
138-
Left err -> left $ OCFErrJsonDecodeFail murl (Text.pack err)
139-
Right res -> pure res
138+
-- Try to decode into strongly-typed vote data structure
139+
-- If this fails (e.g., doNotList is string instead of bool), we still store with is_valid = false
140+
let ocvd = case Vote.eitherDecodeOffChainVoteData lbs anchorType of
141+
Left _err -> Nothing -- Don't fail, just return Nothing (will set is_valid = false)
142+
Right res -> Just res
140143
pure (ocvd, decodedValue, hsh, mWarning)
141144

142145
httpGetBytes ::

cardano-db-sync/src/Cardano/DbSync/Types.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ data SimplifiedOffChainVoteData = SimplifiedOffChainVoteData
193193
, sovaBytes :: !ByteString
194194
, sovaJson :: !Text
195195
, sovaContentType :: !(Maybe ByteString)
196-
, sovaOffChainVoteData :: !Vote.OffChainVoteData
196+
, sovaOffChainVoteData :: !(Maybe Vote.OffChainVoteData)
197197
, sovaWarning :: !(Maybe Text)
198198
}
199199

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE NoImplicitPrelude #-}
3+
4+
module Cardano.DbSync.OffChain.VoteTest (tests) where
5+
6+
import qualified Cardano.Db as DB
7+
import Cardano.DbSync.Error (runOrThrowIO)
8+
import Cardano.DbSync.OffChain.Http (parseAndValidateVoteData)
9+
import Cardano.Prelude hiding ((%))
10+
import qualified Data.Aeson as Aeson
11+
import qualified Data.ByteString as BS
12+
import qualified Data.ByteString.Lazy as LBS
13+
import Hedgehog
14+
15+
tests :: IO Bool
16+
tests =
17+
checkParallel $
18+
Group
19+
"Cardano.DbSync.OffChain.Vote"
20+
[ ("parseAndValidateVoteData handles invalid CIP format", prop_parseInvalidCIPFormat)
21+
, ("parseAndValidateVoteData handles valid JSON but invalid structure", prop_parseValidJsonInvalidStructure)
22+
]
23+
24+
-- | Test that we can parse JSON with incorrect field types (e.g., doNotList as string instead of bool)
25+
-- This is based on the issue https://github.com/IntersectMBO/cardano-db-sync/issues/1995
26+
prop_parseInvalidCIPFormat :: Property
27+
prop_parseInvalidCIPFormat = withTests 1 $ property $ do
28+
-- Read the test file with invalid doNotList field (string instead of bool)
29+
fileContent <- liftIO $ BS.readFile "../cardano-chain-gen/test/testfiles/invalid-vote-doNotList.jsonld"
30+
let lbsContent = LBS.fromStrict fileContent
31+
32+
-- Run the parser
33+
result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData fileContent lbsContent Nothing DB.DrepAnchor Nothing
34+
35+
let (mocvd, val, _hash, _warning) = result
36+
37+
-- Should succeed in parsing generic JSON
38+
annotate "Successfully parsed as generic JSON"
39+
40+
-- Should fail to parse into strongly-typed OffChainVoteData
41+
assert $ isNothing mocvd
42+
43+
-- But should have valid Aeson.Value
44+
case Aeson.toJSON val of
45+
Aeson.Object _obj -> do
46+
annotate "Has valid JSON object"
47+
success
48+
_ -> do
49+
annotate "Expected JSON object"
50+
failure
51+
52+
-- | Test with completely valid JSON but not matching the CIP schema
53+
prop_parseValidJsonInvalidStructure :: Property
54+
prop_parseValidJsonInvalidStructure = property $ do
55+
-- Create a valid JSON that doesn't match CIP schema at all
56+
let invalidJson = "{\"randomField\": \"value\", \"number\": 42}"
57+
bs = encodeUtf8 invalidJson
58+
lbs = LBS.fromStrict bs
59+
60+
-- This should succeed because it's valid JSON, just not matching the schema
61+
result <- liftIO $ runOrThrowIO $ runExceptT $ parseAndValidateVoteData bs lbs Nothing DB.DrepAnchor Nothing
62+
63+
let (mocvd, _val, _hash, _warning) = result
64+
65+
annotate "Successfully parsed generic JSON"
66+
-- Should not parse into OffChainVoteData
67+
assert $ isNothing mocvd

cardano-db-sync/test/Main.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import qualified Cardano.DbSync.ApiTest as Api
44
import qualified Cardano.DbSync.Config.TypesTest as Types
55
import qualified Cardano.DbSync.Era.Shelley.Generic.ScriptDataTest as ScriptData
66
import qualified Cardano.DbSync.Era.Shelley.Generic.ScriptTest as Script
7+
import qualified Cardano.DbSync.OffChain.VoteTest as VoteTest
78
import qualified Cardano.DbSync.Util.AddressTest as Address
89
import qualified Cardano.DbSync.Util.Bech32Test as Bech32
910
import qualified Cardano.DbSync.Util.CborTest as Cbor
@@ -23,4 +24,5 @@ main =
2324
, DbSync.tests
2425
, Types.tests
2526
, Api.tests
27+
, VoteTest.tests
2628
]

config/pgpass-mainnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/tmp:5432:cexplorer:*:*
1+
/var/run/postgresql:5432:cexplorer:*:*

0 commit comments

Comments
 (0)