Skip to content

fix(specs): parse requested block on Cardano /blocks/{hash_or_number} routes - #2335

Merged
EliasiOfir merged 1 commit into
mainfrom
ofir/tsk-44-cardano-spec
Sep 10, 2026
Merged

EliasiOfir merged 1 commit into
mainfrom
ofir/tsk-44-cardano-spec

Conversation

@EliasiOfir

@EliasiOfir EliasiOfir commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Closes: N/A (reported by Stake Village during their audit of the Cardano spec, tracked on the Lava product board as "Cardano Specs Update"; needed for the Sep 17 CARDANO / CARDANOT incentivization)

The bug

The six Blockfrost /blocks/{hash_or_number} routes in the CARDANO spec (and therefore in CARDANOT, which imports it) declared:

"block_parsing": { "parser_arg": ["latest"], "parser_func": "DEFAULT" }

so GET /blocks/1 was classified as a request for the latest block. The URL sent to the provider was untouched, but on the Lava side:

  1. the archive rule ("block": 8192) never fired, so archive providers were not guaranteed for historical blocks and a non-archive provider could return an error even when archive providers were available;
  2. when an archive provider did answer, the archive extension and its cu_multiplier: 5 were not applied;
  3. requested-block tracking, QoS and data-reliability logic saw latest instead of the real height.

The fix

specs/mainnet-1/specs/cardano.json and specs/testnet-2/specs/cardano.json (kept identical, as before):

Block parsing on the six {hash_or_number} routes — /blocks/{hash_or_number}, /next, /previous, /txs, /txs/cbor, /addresses:

"block_parsing": {
    "parser_arg": ["hash_or_number", "=", "0"],
    "parser_func": "PARSE_DICTIONARY_OR_ORDERED",
    "default_value": "latest"
},
"parsers": [
    { "parse_path": ".params.hash_or_number", "parse_type": "BLOCK_HASH" }
]
  • PARSE_DICTIONARY_OR_ORDERED on the path parameter is the existing Aptos (/blocks/by_height/{block_height}) and Cosmos SDK (/cosmos/base/tendermint/v1beta1/blocks/{height}) pattern. RestMessage.GetParams() exposes path parameters as a map keyed by the placeholder name, so a numeric value resolves to that block height.
  • A Cardano block hash is 64 hex chars without a 0x prefix, which ParseDefaultBlockParameter rejects. default_value: "latest" makes hash requests keep exactly today's behaviour (latest, UsedDefaultValue = true) instead of degrading to NOT_APPLICABLE.
  • The BLOCK_HASH generic parser surfaces the hash as a requested block hash. The consumer already ships it in BlocksHashesToHeights and resolves it through the relay cache (resolveRequestedBlock picks the cached height when the block was parsed from the default value), so hash requests can be routed to archive once the height is known. For a numeric value the hash parser fails the minimum-length check and falls through to the legacy parser, as in TestParseBlockFromParamsHash.

Routes deliberately left on DEFAULT latest: /epochs/{number}/... (epoch numbers, ~500 on mainnet) and /blocks/slot/{slot_number} / /blocks/epoch/{epoch_number}/slot/{slot_number} (slot numbers, far above the block height). Both would be mis-classified against the archive boundary if parsed as block heights. A hash-to-height decision for those is a separate routing policy question.

Spec gaps — diffed the API list against the current Blockfrost OpenAPI (blockfrost/openapi, master). Added the chain-data endpoints that were missing, with the same CU convention as their siblings (10 for a single object, 20 for a list):

Endpoint CU
/assets/{asset}/utxos 20
/scripts/{script_hash}/utxos 20
/governance/committee 10
/governance/committee/votes 20
/governance/committee/{cc_id}/votes 20

Not added, on purpose: /addresses/{address}/txs and /assets/{asset}/txs (marked deprecated in the OpenAPI, superseded by /transactions), /, /health, /health/clock, /metrics, /metrics/endpoints (Blockfrost account/service status, not chain data), and /ipfs/* (separate Blockfrost add-on product). Every endpoint currently in the spec exists in the OpenAPI and none is deprecated.

Everything else in the spec (verifications, pruning at latest_distance: 8193 vs the 8192 archive rule, finalization and block-time values, min stake, CARDANOT chain-id override) was reviewed and left unchanged.

Verification

New tests in protocol/chainlib/rest_test.go load the real spec through CreateChainLibMocks("CARDANO", rest, ...):

  • TestCardanoSpec_HistoricalBlockRequestsAreNotLatest — /blocks/1, /blocks/11500000 and the five sub-routes resolve to the numeric height; a 64-hex hash resolves to latest with the hash captured in GetRequestedBlocksHashes(); /blocks/latest, /epochs/1 and /blocks/slot/1 stay latest.
  • TestCardanoSpec_HistoricalBlockActivatesArchive — with the archive extension enabled by policy and LatestBlock = 12,000,000: latest and latest-1 get no extension and 10 CU; latest-8193 and /blocks/1 get the archive extension and 50 CU.

Against the original spec the first test fails on every numeric route with expected: 1, actual: -2 (LATEST_BLOCK), which reproduces the report.

go test ./x/spec/keeper -run 'TestSpecs$' -count=1        ok   (both spec copies load, expand and validate)
go test ./protocol/chainlib -run TestCardanoSpec -count=1  ok
go test ./protocol/chainlib -count=1                      ok   (9.9s)
go test ./protocol/parser -count=1                        ok
go vet ./protocol/chainlib                                ok

CI on 8e92aae: all 30 checks green (lint, CodeQL, test-protocol, test-consensus, protocol e2e, payment e2e, codecov patch, binaries, docker images).


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • read the contribution guide
  • included the correct type prefix in the PR title, you can find examples of the prefixes below:
  • confirmed ! in the type prefix if API or client breaking change — not breaking
  • targeted the main branch
  • provided a link to the relevant issue or specification — Blockfrost OpenAPI: https://github.com/blockfrost/openapi
  • reviewed "Files changed" and left comments if necessary
  • included the necessary unit and integration tests
  • updated the relevant documentation or specification, including comments for documenting Go code — the spec files are the specification
  • confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

I have...

  • confirmed the correct type prefix in the PR title
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage

https://claude.ai/code/session_015Zn1TrLFU2xyth1vqGmz7Q

… routes

The six Blockfrost `/blocks/{hash_or_number}` routes in the CARDANO spec
(inherited by CARDANOT) declared `DEFAULT latest` block parsing, so a
request such as `GET /blocks/1` was classified as a latest-block request.
The archive rule (8192 blocks) never fired for historical blocks, archive
providers were not selected, and the archive CU multiplier was skipped.

Switch those routes to `PARSE_DICTIONARY_OR_ORDERED` on the
`hash_or_number` path parameter, following the Aptos and Cosmos SDK
pattern, with `default_value: latest` so a block hash keeps today's
behaviour, and add a `BLOCK_HASH` generic parser so hash requests are
surfaced as requested hashes and can be resolved to heights by the
consumer cache.

Also add the Blockfrost endpoints missing from the spec:
`/assets/{asset}/utxos`, `/scripts/{script_hash}/utxos`,
`/governance/committee`, `/governance/committee/votes` and
`/governance/committee/{cc_id}/votes`.

Add chainlib tests that load the real CARDANO spec and pin both the
requested-block parsing and the archive extension activation.

Claude-Session: https://claude.ai/code/session_015Zn1TrLFU2xyth1vqGmz7Q
@codecov

codecov Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
consensus 8.97% <ø> (ø)
protocol 38.85% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
7 files   ±0   0 ❌ ±0 

Results for commit 8e92aae. ± Comparison against base commit 9ef694a.

@EliasiOfir
EliasiOfir marked this pull request as ready for review September 10, 2026 04:29
@EliasiOfir
EliasiOfir merged commit 148d09d into main Sep 10, 2026
31 checks passed
@EliasiOfir
EliasiOfir deleted the ofir/tsk-44-cardano-spec branch September 10, 2026 04:30
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix Cardano block parsing and complete Blockfrost routes

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Parse numeric Cardano block paths as requested heights across six Blockfrost routes.
• Capture block hashes and activate archive routing with the correct compute-unit multiplier.
• Add five missing Blockfrost endpoints and regression coverage using the real CARDANO spec.
Diagram

graph TD
  A["Cardano Request"] --> B["Route Spec"] --> C{"Path Value?"}
  C -->|Numeric| D["Requested Height"] --> F{"Archive Required?"}
  C -->|Hash| E["Hash Cache"] --> F
  F -->|Historical| G["Archive Routing"]
  F -->|Recent or latest| H["Standard Routing"]
Loading
High-Level Assessment

The PR uses the existing dictionary-or-ordered and generic block-hash parsers, matching established Aptos and Cosmos patterns while avoiding chain-specific parser code. Keeping hash fallback behavior and validating the real specification end to end is preferable to changing shared parser semantics or duplicating logic in the consumer.

Files changed (3) +450 / -36

Tests (1) +126 / -0
rest_test.goAdd Cardano block parsing and archive-routing regression tests +126/-0

Add Cardano block parsing and archive-routing regression tests

• Loads the real CARDANO specification to verify numeric heights, hashes, latest blocks, and intentionally excluded epoch and slot routes. Also confirms historical requests activate the archive extension and CU multiplier at the configured boundary.

protocol/chainlib/rest_test.go

Other (2) +324 / -36
cardano.jsonCorrect mainnet Cardano block parsing and add missing routes +162/-18

Correct mainnet Cardano block parsing and add missing routes

• Configures six hash-or-number block routes to parse numeric heights and capture block hashes while retaining latest as the hash fallback. Adds asset UTXO, script UTXO, and three governance committee endpoints.

specs/mainnet-1/specs/cardano.json

cardano.jsonMirror Cardano parsing and route additions for testnet +162/-18

Mirror Cardano parsing and route additions for testnet

• Keeps the testnet CARDANO specification aligned with mainnet by applying identical block parsing, hash extraction, and missing Blockfrost endpoint definitions.

specs/testnet-2/specs/cardano.json

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📜 Skill insights (0)

Grey Divider


Action required

1. Broken Cardano routes can pass tests 📘 Rule violation ☼ Reliability
Description
The five new Cardano route definitions are never exercised by rest_test.go, whose added cases only
send block, epoch, and slot paths. Invalid route names, compute units, or parser metadata on the
asset, script, and governance endpoints therefore reach the specifications without a targeted
parsing regression failing.
Code

specs/mainnet-1/specs/cardano.json[R446-449]

+                            {
+                                "name": "/assets/{asset}/utxos",
+                                "block_parsing": {
+                                    "parser_arg": [
Evidence
Compliance rule 4 requires suitable tests for changed behavior. Repository searches find the five
new paths only in the two Cardano specification files, while the added route-parsing table covers
only block, epoch, and slot paths.

AGENTS.md: Maintain Adequate Unit and Integration Test Coverage
specs/mainnet-1/specs/cardano.json[446-463]
specs/mainnet-1/specs/cardano.json[1022-1075]
specs/mainnet-1/specs/cardano.json[1742-1759]
protocol/chainlib/rest_test.go[349-367]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds five Cardano routes without tests that parse requests against those route definitions and verify their metadata.

## Fix Focus Areas
- protocol/chainlib/rest_test.go[349-367]
- specs/mainnet-1/specs/cardano.json[446-463]
- specs/mainnet-1/specs/cardano.json[1022-1075]
- specs/mainnet-1/specs/cardano.json[1742-1759]

## Recommended Fix
Add table-driven chainlib cases for every new asset, script, and governance route. Parse representative paths from the real Cardano spec and assert successful matching, requested-block behavior, and expected compute units.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Cardano behavior has no test narrative 📘 Rule violation ⚙ Maintainability
Description
The changed hash_or_number parsing and archive-selection scenarios are covered in Go tests but
have no corresponding specs/*_test.md document. Readers evaluating numeric heights, hashes, and
the archive boundary therefore cannot recover the expected behavior from the required human-readable
specification.
Code

specs/mainnet-1/specs/cardano.json[R540-543]

+                                        "hash_or_number",
+                                        "=",
+                                        "0"
                                    ],
-                                    "parser_func": "DEFAULT"
Evidence
Compliance rule 5 requires behavioral changes to be reflected in a specs/*_test.md file using
GIVEN–WHEN–THEN narratives. The PR introduces explicit Cardano parsing and archive scenarios, but
the repository contains no matching test-specification Markdown file.

AGENTS.md: Document Test Specifications with GIVEN–WHEN–THEN Narratives
specs/mainnet-1/specs/cardano.json[540-545]
protocol/chainlib/rest_test.go[324-332]
protocol/chainlib/rest_test.go[384-386]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Cardano requested-block and archive behavior changed without the required GIVEN–WHEN–THEN test specification under `specs/`.

## Fix Focus Areas
- specs/mainnet-1/specs/cardano.json[540-545]
- protocol/chainlib/rest_test.go[324-332]
- protocol/chainlib/rest_test.go[384-386]

## Recommended Fix
Create or update the relevant `specs/*_test.md` document with GIVEN–WHEN–THEN narratives for numeric block heights, block hashes, latest blocks, excluded epoch and slot parameters, and archive-extension activation at the configured boundary.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Genesis block requests use latest 🐞 Bug ≡ Correctness
Description
PARSE_DICTIONARY_OR_ORDERED passes the extracted string "0" to
appendInterfaceToInterfaceArrayWithError, which treats it as an absent value and substitutes the
configured latest default. Requests for /blocks/0 and its five subroutes therefore bypass
historical-block tracking and archive selection in both Cardano specs.
Code

specs/mainnet-1/specs/cardano.json[R540-542]

+                                        "hash_or_number",
+                                        "=",
+                                        "0"
Evidence
The new spec configuration extracts hash_or_number through the dictionary-or-ordered parser and
supplies latest as its fallback. That parser sends map values through a helper that explicitly
returns ValueNotSetError for "0"; legacyParse catches that error and substitutes the default
while marking it as used, even though the general block parser accepts nonnegative numeric values
including zero.

specs/mainnet-1/specs/cardano.json[537-545]
specs/testnet-2/specs/cardano.json[537-545]
protocol/parser/parser.go[832-838]
protocol/parser/parser.go[268-273]
protocol/parser/parser.go[902-907]
protocol/parser/parser.go[68-75]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Cardano requests whose `hash_or_number` path parameter is `0` are parsed as `latest` because the shared dictionary parser treats the string `"0"` as an unset value. This affects all six changed block routes in both Cardano specs.

## Fix Focus Areas
- specs/mainnet-1/specs/cardano.json[539-545]
- specs/testnet-2/specs/cardano.json[539-545]
- protocol/parser/parser.go[832-838]
- protocol/parser/parser.go[902-907]
- protocol/chainlib/rest_test.go[349-367]

## Recommended Fix
Ensure a present dictionary value of `"0"` is returned as a valid block value rather than converted to `ValueNotSetError`, without changing genuinely absent or empty-parameter handling. Add Cardano spec tests for `/blocks/0` and representative `/blocks/0/...` routes, asserting requested block zero and archive activation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This changes runtime block parsing, archive-provider selection, CU accounting, and requested-hash tracking across duplicated Cardano specs with substantial test and configuration edits, creating meaningful behavioral risk but not enough independent logic density to require extended review.

Grey Divider

Tip of the day
💡 Did you know, you can turn these tips off under Display preferences

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +446 to +449
{
"name": "/assets/{asset}/utxos",
"block_parsing": {
"parser_arg": [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Broken cardano routes can pass tests 📘 Rule violation ☼ Reliability

The five new Cardano route definitions are never exercised by rest_test.go, whose added cases only
send block, epoch, and slot paths. Invalid route names, compute units, or parser metadata on the
asset, script, and governance endpoints therefore reach the specifications without a targeted
parsing regression failing.
Agent Prompt
## Issue description
The PR adds five Cardano routes without tests that parse requests against those route definitions and verify their metadata.

## Fix Focus Areas
- protocol/chainlib/rest_test.go[349-367]
- specs/mainnet-1/specs/cardano.json[446-463]
- specs/mainnet-1/specs/cardano.json[1022-1075]
- specs/mainnet-1/specs/cardano.json[1742-1759]

## Recommended Fix
Add table-driven chainlib cases for every new asset, script, and governance route. Parse representative paths from the real Cardano spec and assert successful matching, requested-block behavior, and expected compute units.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +540 to 543
"hash_or_number",
"=",
"0"
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Cardano behavior has no test narrative 📘 Rule violation ⚙ Maintainability

The changed hash_or_number parsing and archive-selection scenarios are covered in Go tests but
have no corresponding specs/*_test.md document. Readers evaluating numeric heights, hashes, and
the archive boundary therefore cannot recover the expected behavior from the required human-readable
specification.
Agent Prompt
## Issue description
The Cardano requested-block and archive behavior changed without the required GIVEN–WHEN–THEN test specification under `specs/`.

## Fix Focus Areas
- specs/mainnet-1/specs/cardano.json[540-545]
- protocol/chainlib/rest_test.go[324-332]
- protocol/chainlib/rest_test.go[384-386]

## Recommended Fix
Create or update the relevant `specs/*_test.md` document with GIVEN–WHEN–THEN narratives for numeric block heights, block hashes, latest blocks, excluded epoch and slot parameters, and archive-extension activation at the configured boundary.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +540 to +542
"hash_or_number",
"=",
"0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Genesis block requests use latest 🐞 Bug ≡ Correctness

PARSE_DICTIONARY_OR_ORDERED passes the extracted string "0" to
appendInterfaceToInterfaceArrayWithError, which treats it as an absent value and substitutes the
configured latest default. Requests for /blocks/0 and its five subroutes therefore bypass
historical-block tracking and archive selection in both Cardano specs.
Agent Prompt
## Issue description
Cardano requests whose `hash_or_number` path parameter is `0` are parsed as `latest` because the shared dictionary parser treats the string `"0"` as an unset value. This affects all six changed block routes in both Cardano specs.

## Fix Focus Areas
- specs/mainnet-1/specs/cardano.json[539-545]
- specs/testnet-2/specs/cardano.json[539-545]
- protocol/parser/parser.go[832-838]
- protocol/parser/parser.go[902-907]
- protocol/chainlib/rest_test.go[349-367]

## Recommended Fix
Ensure a present dictionary value of `"0"` is returned as a valid block value rather than converted to `ValueNotSetError`, without changing genuinely absent or empty-parameter handling. Add Cardano spec tests for `/blocks/0` and representative `/blocks/0/...` routes, asserting requested block zero and archive activation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: CARDANO. Historical block requests are parsed as latest.

2 participants