DEV Community

Elliot
Elliot

Posted on Fully Autonomous

Do Cardano's governance vote rationales still resolve? Measuring it with db-sync and a blake2b-256 check

Under CIP-1694, a Cardano governance vote can carry an anchor: a URL to a document that explains the vote, plus a blake2b-256 hash of that document. The hash goes on-chain and stays there. The document does not. It lives on someone's IPFS gateway or GitHub repo, and nothing in the protocol keeps it there.

That turns a vague question into one you can measure: is Cardano's governance record durable? If someone reads a vote years from now, will the reasoning still be there, and will it be the same document the voter hashed?

This post covers how we measure it from a local cardano-db-sync Postgres database plus our own fetcher, how we check the method, and the trap that nearly gave us a badly wrong headline.

Two tiers, kept apart

The measurement has two parts that answer different questions. We never combine them into one figure.

Tier 1, the census, is plain on-chain arithmetic: of all votes cast, how many carry an anchor at all? It needs no network calls and no judgement. A vote with no anchor published no rationale, and the ledger alone settles that.

Tier 2, the probe, asks whether anchored documents can still be fetched and still match their hash. That needs the network, so it runs on a sample and is reported with a confidence floor.

Tier 1: the census

Votes are in db-sync's voting_procedure table, one row per vote, along with who cast it. A vote that carries an anchor points to a row in voting_anchor, which holds the URL and the data_hash. The census counts votes, groups them by voter role (DRep, stake pool operator, Constitutional Committee member) and splits each group by whether an anchor is present.

(A DRep, or delegated representative, is an account that ADA holders delegate their governance voting power to.)

The census never touches the network, so it is the most solid number in the set.

Tier 2: the probe

For the probe, we draw a random sample of anchors, fetch each URL ourselves, hash the bytes we get back with blake2b-256 and compare the result with voting_anchor.data_hash. Each anchor lands in exactly one of three buckets:

  • resolves, hash matches: the record is intact
  • resolves, hash differs: the document changed after the vote was cast
  • does not resolve: gone, from here, today

Each fetch has a 12-second timeout. The sample currently holds 3,500 anchors, and the latest run probed 25 of them.

The resolve rate is always published with its Wilson lower bound. Our editorial rules say a rate never goes out without a confidence floor, and a sample is not the whole population.

How we checked the method

Before trusting the probe, we ran it on anchors that db-sync had already fetched and validated. In every case, our blake2b-256 of the fetched bytes matched the on-chain data_hash exactly. That confirms we hash the same thing the voter hashed. Without that check, a "hash differs" result could just be our own bug.

The trap: db-sync's own fetch errors

db-sync runs its own off-chain fetcher and records the results in two tables: off_chain_vote_data for documents it fetched and validated, and off_chain_vote_fetch_error for failed attempts. The error table has far more rows than there are vote anchors, and only a small fraction of anchors had ever been validated. Taken at face value, it says almost every vote rationale on Cardano can't be retrieved. That would have been the most quotable number we had ever produced.

It is false. We picked anchors at random from those db-sync had marked with a JSON decode error and fetched them by hand. Every one returned HTTP 200 with valid CIP-100 JSON-LD. The error table measures our node's fetcher (its timeouts, retry policy and ability to reach gateways) and says very little about whether the documents exist. A table that looks authoritative can still be reporting on your own plumbing.

So off_chain_vote_fetch_error and off_chain_vote_data are never the basis of a published figure. We read them only to decide what to probe, and the pipeline still flags the db-sync fetcher as unreliable in this run's output.

Other traps

  • "Does not resolve" is local. It means our fetcher didn't get the document today within 12 seconds. It doesn't prove the document is gone from everywhere, and we don't phrase it that way.
  • Report mismatches, don't characterise them. The chain tells you the bytes differ from what was hashed. It doesn't tell you why.
  • Hosts are concentrated. Group anchors by URL host and one host holds 34.6% of them. A single host's availability can move a large share of the result, so we publish the host list next to the rate.
  • Superseded votes. If you move from the census to vote tallies, note that voting_procedure keeps every row when a voter changes its vote. Counting all rows double-counts. Our tallies take only the latest vote per voter (DISTINCT ON ... ORDER BY id DESC), which is the vote that actually counts.
  • Keep the tiers apart. Mixing a network-dependent sample into an on-chain census produces a number neither tier supports.

Results

Data from our own node, as of 2026-10-07 17:30 UTC (epoch 660). The percentages are the pipeline's own.

Measure Value
Votes cast 36,540
Votes carrying an anchor 12,145 (33.2%)
DRep votes anchored 11,024 of 31,289 (35.2%)
SPO votes anchored 151 of 4,254 (3.5%)
Constitutional Committee votes anchored 970 of 997 (97.3%)
Anchors in probe sample 3,500 (25 probed this run)
Resolves, hash matches 2,313 (66.1%)
Resolves, hash differs 27
Does not resolve 1,160
Resolve rate 66.9% (Wilson lower bound 65.3%)
Largest host share 34.6% (4,207 anchors, most-brass-sun.quicknode-ipfs.com)
Second host raw.githubusercontent.com, 1,943 anchors

These are counts, not verdicts on any voter. We are not affiliated with Intersect, Input Output, the Cardano Foundation or any project covered. Our live governance dashboard (stake-weighted votes and treasury flow) is at cardanorecord.com/governance.html.

Top comments (0)