Skip to content

Conversation

@hrmtm23
Copy link

@hrmtm23 hrmtm23 commented Aug 15, 2025

  • Update utxorpc to 0.17.0
  • Added mapping of genesis configuration

Summary by CodeRabbit

  • New Features

    • Added era slot transition tracking for Allegra, Mary, Alonzo, Babbage, and Conway eras.
    • Introduced methods to determine network era from slot numbers and query all available eras.
    • Enhanced genesis data configuration and mapping capabilities.
    • Improved timestamp-to-slot conversions using era slot data.
  • Chores

    • Updated dependencies for enhanced compatibility.

scarmuega
scarmuega previously approved these changes Oct 22, 2025
@scarmuega
Copy link
Member

@hrmtm23 looks good!
can you resolve the conflicts with main?
happy to merge right after.

@coderabbitai
Copy link

coderabbitai bot commented Oct 24, 2025

Walkthrough

The changes introduce multi-era genesis support to Pallas by adding era transition slot tracking to GenesisValues, implementing genesis-to-protocol-buffer mapping, and integrating era-aware timestamp handling into the mapper infrastructure. This enables dynamic era determination from slot numbers and comprehensive genesis data serialization.

Changes

Cohort / File(s) Change Summary
Era transition infrastructure
pallas-configs/src/byron.rs, pallas-traverse/src/wellknown.rs
Added omega: u32 field to HeavyDelegation struct; extended GenesisValues with five optional era slot fields (allegra_start_slot, mary_start_slot, alonzo_start_slot, babbage_start_slot, conway_start_slot) and six new public methods for era-slot conversion, era enumeration, and builder-style configuration.
UTxO RPC dependency & configuration
pallas-utxorpc/Cargo.toml
Added pallas-configs dependency (path="../pallas-configs", version="=1.0.0-alpha.2"); upgraded prost-types from 0.13.1 to 0.14.1.
Genesis mapping & mapper integration
pallas-utxorpc/src/genesis.rs, pallas-utxorpc/src/lib.rs
Implemented two new public mapping methods (map_genesis, map_era_summaries) for converting internal genesis representations to u5c protocol buffers; integrated GenesisValues into Mapper<C> struct with new constructor new_with_genesis(), custom Default impl, and refactored timestamp resolution from ledger.get_slot_timestamp() to block.wallclock(&self.genesis).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Mapper
    participant GenesisValues
    participant u5c Protocol

    Client->>Mapper: map_genesis(byron, shelley, alonzo, conway, params)
    Mapper->>GenesisValues: available_eras()
    GenesisValues-->>Mapper: [Byron, Shelley, Allegra, Mary, Alonzo, Babbage, Conway]
    
    loop For each era
        Mapper->>GenesisValues: era_start_slot(era)
        GenesisValues-->>Mapper: Option<u64>
        Mapper->>Mapper: aggregate era data
    end
    
    Mapper->>Mapper: map block version data<br/>map Conway governance<br/>map Alonzo parameters
    Mapper->>u5c Protocol: Genesis (aggregated)
    u5c Protocol-->>Client: Complete genesis object

    Client->>Mapper: map_era_summaries(current_params)
    loop For each available era
        Mapper->>GenesisValues: slot_to_era(slot)
        GenesisValues-->>Mapper: Era
        Mapper->>Mapper: compute start/end slots & epochs
    end
    Mapper->>u5c Protocol: EraSummaries[]
    u5c Protocol-->>Client: Era summaries with boundaries
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes introduce significant structural modifications across multiple modules: five new fields and six new methods in GenesisValues with non-trivial era-conversion logic, a new comprehensive genesis mapping module with extensive field-by-field protocol buffer translations, structural updates to Mapper<C>, and integration of a new dependency. While individual changes follow consistent patterns, the cross-module coordination, new public API surface, and complex nested data transformations require careful review.

Possibly related PRs

  • feat: update utxorpc spec #693: Related through timestamp handling refactoring; the retrieved PR adds LedgerContext::get_slot_timestamp() while this PR replaces per-block timestamp logic with block.wallclock(&self.genesis) using era-aware genesis data.

Suggested reviewers

  • scarmuega

Poem

🐰 Through epochs and slots we now can trace,
Five eras dance at their rightful place,
Genesis blooms with era-aware sight,
Byron to Conway, aligned just right! ✨
The mapper adapts, with wallclock in hand,
Multi-era Pallas, across all the land! 🌍

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat(utxorpc): implement mapping of genesis configuration to UTxO RPC Genesis type" accurately describes the main objective of the changeset. The core changes involve implementing new methods (map_genesis and map_era_summaries) in the pallas-utxorpc module to map genesis configuration data to UTxO RPC types, with supporting enhancements across related modules to provide the necessary data structures and methods. The title is specific, concise, and clearly communicates the primary change without vague terminology or unnecessary detail.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hrmtm23
Copy link
Author

hrmtm23 commented Oct 24, 2025

@hrmtm23 looks good! can you resolve the conflicts with main? happy to merge right after.

I have resolved the conflict @scarmuega! TY

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (2)
pallas-utxorpc/src/lib.rs (1)

46-64: Document the default network assumption or require explicit genesis.

Mapper::default/new now assume mainnet GenesisValues. This is easy to misuse on preview/preprod/testnet. Either:

  • Document prominently, or
  • Prefer an explicit constructor and consider deprecating new in favor of new_with_genesis.

I can add rustdoc and a builder-style ctor if desired.

pallas-utxorpc/src/genesis.rs (1)

55-60: Active slots coeff scaling may lose precision.

Scaling by 1000 truncates. Consider higher precision (e.g., denominator 1_000_000) or a simple fraction approximation to preserve accuracy.

Apply:

- active_slots_coeff: shelley.active_slots_coeff.map(|coeff| u5c::RationalNumber {
-     numerator: (coeff * 1000.0) as i32,
-     denominator: 1000,
- }),
+ active_slots_coeff: shelley.active_slots_coeff.map(|coeff| {
+     let denom: u32 = 1_000_000;
+     let numer: i32 = (coeff * denom as f64).round() as i32;
+     u5c::RationalNumber { numerator: numer, denominator: denom }
+ }),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df1cd7a and 8a3341c.

📒 Files selected for processing (5)
  • pallas-configs/src/byron.rs (1 hunks)
  • pallas-traverse/src/wellknown.rs (6 hunks)
  • pallas-utxorpc/Cargo.toml (1 hunks)
  • pallas-utxorpc/src/genesis.rs (1 hunks)
  • pallas-utxorpc/src/lib.rs (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pallas-traverse/src/wellknown.rs (1)
pallas-traverse/src/header.rs (1)
  • slot (57-70)
pallas-utxorpc/src/lib.rs (1)
pallas-traverse/src/wellknown.rs (1)
  • default (274-276)
🔇 Additional comments (3)
pallas-utxorpc/Cargo.toml (1)

21-22: Review comment is incorrect; no workspace version mismatch exists.

Only pallas-utxorpc depends on prost-types 0.14.1. No other workspace member uses prost or prost-types, so there is no version mixing to align. The concern about "mixing 0.13 and 0.14" within the workspace is unfounded and the recommendation is not actionable.

Likely an incorrect or invalid review comment.

pallas-traverse/src/wellknown.rs (1)

119-125: Confirm the preview Conway start slot value from official Cardano sources.

The code structure is actually correct—setting babbage_start_slot: None with conway_start_slot: Some(31424418) properly represents that preview started in Babbage era and transitioned to Conway at that slot. The era detection logic (lines 193-196) correctly treats None to mean "all earlier slots belong to this era." This matches the semantic intent of your comment "Preview likely started in Babbage era."

However, you should verify that 31424418 is the official preview testnet Conway era start slot from Cardano's official sources (e.g., cardano-node repository, IOG documentation, or the official genesis files at book.world.dev.cardano.org/environments/preview/). I couldn't locate this specific value in publicly available documentation to confirm its accuracy.

pallas-configs/src/byron.rs (1)

80-87: Verify omega field origin and backward-compatibility requirements.

The review comment raises a valid concern, but verification reveals conflicting signals:

  • Official Cardano Byron genesis specification lists heavyDelegation fields as: delegate hash, VRF key hash, and certificate—no omega mentioned
  • Test genesis files in the repo (mainnet-byron-genesis.json, preview-byron-genesis.json) both contain omega: 0
  • The struct requires omega: u32 without #[serde(default)] or Option

Without access to git history, it's unclear whether:

  1. omega is a pre-existing field already present in real genesis data, making the struct change safe
  2. omega is newly added, creating a backward-compatibility risk for existing genesis files lacking it
  3. The test data represents the authoritative format or a Pallas-specific variant

Required verification: Confirm whether omega has been present in Cardano's official Byron genesis format from chain inception, or if it's a recent addition/Pallas extension. If real-world genesis files lack omega, the #[serde(default)] suggestion is necessary.

Comment on lines +67 to 73
// Era transitions - source: official Cardano documentation
allegra_start_slot: Some(16588800),
mary_start_slot: Some(23068800),
alonzo_start_slot: Some(39916975),
babbage_start_slot: Some(72316896),
conway_start_slot: Some(133660855),
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify mainnet transition slots.

Hardcoded slots for Allegra/Mary/Alonzo/Babbage/Conway must match canonical sources.


🌐 Web query:

Confirm Cardano mainnet era transition absolute slot numbers for: Allegra, Mary, Alonzo, Babbage, Conway.
Return authoritative sources.

💡 Result:

Here are the mainnet era-transition absolute slot boundaries (last absolute slot of the previous era) with authoritative sources:

  • Allegra → Mary: last Allegra absolute slot = 23,068,793. [1]
  • Mary → Alonzo: last Mary absolute slot = 39,916,796. [1]
  • Alonzo → Babbage: last Alonzo absolute slot = 72,316,796. [1]
  • Babbage → Conway: last Babbage absolute slot = 133,660,799. [1]

(Therefore the next-era first absolute slot = last-slot + 1 for each transition.)

Sources:
[1] Ogmios — Chain synchronization / “Points of interest” (mainnet era-boundaries with absolute slot numbers). [2] Cardano Wallet — Eras (explanation of era-switching by on‑chain proposal). [1]: https://ogmios.dev/mini-protocols/local-chain-sync/ [2]: https://cardano-foundation.github.io/cardano-wallet/design/concepts/eras.html

If you want I can also return the exact first-slot-of-era values (last+1) and block hashes for those boundary blocks.


Fix era transition slots to match Cardano mainnet canonical values.

The hardcoded slot values do not match authoritative Ogmios and Cardano Wallet documentation. Correct values are:

  • mary_start_slot: 23068794 (currently 23068800)
  • alonzo_start_slot: 39916797 (currently 39916975)
  • babbage_start_slot: 72316797 (currently 72316896)
  • conway_start_slot: 133660800 (currently 133660855)
🤖 Prompt for AI Agents
In pallas-traverse/src/wellknown.rs around lines 67 to 73, the hardcoded era
transition slot constants are incorrect; update the values to match Cardano
mainnet canonical sources by changing mary_start_slot to Some(23068794),
alonzo_start_slot to Some(39916797), babbage_start_slot to Some(72316797), and
conway_start_slot to Some(133660800) so the file uses the authoritative
Ogmios/Cardano Wallet slot numbers.

Comment on lines +181 to +200
/// Get era from slot number using HFC data
pub fn slot_to_era(&self, slot: u64) -> crate::Era {
use crate::Era;

if slot < self.shelley_known_slot {
Era::Byron
} else if self.allegra_start_slot.map_or(true, |s| slot < s) {
Era::Shelley
} else if self.mary_start_slot.map_or(true, |s| slot < s) {
Era::Allegra
} else if self.alonzo_start_slot.map_or(true, |s| slot < s) {
Era::Mary
} else if self.babbage_start_slot.map_or(true, |s| slot < s) {
Era::Alonzo
} else if self.conway_start_slot.map_or(true, |s| slot < s) {
Era::Babbage
} else {
Era::Conway
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix slot_to_era when intermediate era start slots are None.

Current map_or(true, …) short‑circuits to earlier eras; e.g., preview classifies everything as Shelley. Implement skip‑if‑None with rolling last‑known era.

Apply:

-    pub fn slot_to_era(&self, slot: u64) -> crate::Era {
-        use crate::Era;
-
-        if slot < self.shelley_known_slot {
-            Era::Byron
-        } else if self.allegra_start_slot.map_or(true, |s| slot < s) {
-            Era::Shelley
-        } else if self.mary_start_slot.map_or(true, |s| slot < s) {
-            Era::Allegra
-        } else if self.alonzo_start_slot.map_or(true, |s| slot < s) {
-            Era::Mary
-        } else if self.babbage_start_slot.map_or(true, |s| slot < s) {
-            Era::Alonzo
-        } else if self.conway_start_slot.map_or(true, |s| slot < s) {
-            Era::Babbage
-        } else {
-            Era::Conway
-        }
-    }
+    pub fn slot_to_era(&self, slot: u64) -> crate::Era {
+        use crate::Era;
+        if slot < self.shelley_known_slot {
+            return Era::Byron;
+        }
+        // List known transitions in order, skipping unknowns.
+        let transitions: &[(Era, Option<u64>)] = &[
+            (Era::Allegra, self.allegra_start_slot),
+            (Era::Mary, self.mary_start_slot),
+            (Era::Alonzo, self.alonzo_start_slot),
+            (Era::Babbage, self.babbage_start_slot),
+            (Era::Conway, self.conway_start_slot),
+        ];
+        let mut current = Era::Shelley;
+        for (era, maybe_start) in transitions {
+            if let Some(start) = maybe_start {
+                if slot < *start {
+                    return current;
+                }
+                current = *era;
+            }
+        }
+        current
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Get era from slot number using HFC data
pub fn slot_to_era(&self, slot: u64) -> crate::Era {
use crate::Era;
if slot < self.shelley_known_slot {
Era::Byron
} else if self.allegra_start_slot.map_or(true, |s| slot < s) {
Era::Shelley
} else if self.mary_start_slot.map_or(true, |s| slot < s) {
Era::Allegra
} else if self.alonzo_start_slot.map_or(true, |s| slot < s) {
Era::Mary
} else if self.babbage_start_slot.map_or(true, |s| slot < s) {
Era::Alonzo
} else if self.conway_start_slot.map_or(true, |s| slot < s) {
Era::Babbage
} else {
Era::Conway
}
}
pub fn slot_to_era(&self, slot: u64) -> crate::Era {
use crate::Era;
if slot < self.shelley_known_slot {
return Era::Byron;
}
// List known transitions in order, skipping unknowns.
let transitions: &[(Era, Option<u64>)] = &[
(Era::Allegra, self.allegra_start_slot),
(Era::Mary, self.mary_start_slot),
(Era::Alonzo, self.alonzo_start_slot),
(Era::Babbage, self.babbage_start_slot),
(Era::Conway, self.conway_start_slot),
];
let mut current = Era::Shelley;
for (era, maybe_start) in transitions {
if let Some(start) = maybe_start {
if slot < *start {
return current;
}
current = *era;
}
}
current
}
🤖 Prompt for AI Agents
pallas-traverse/src/wellknown.rs around lines 181 to 200: slot_to_era currently
uses map_or(true, |s| slot < s) which treats missing (None) start slots as if
the era boundary is in the future and thus short‑circuits to earlier eras;
change the logic to skip None boundaries and only compare when a start slot is
Some. Implement a sequential check or a rolling approach: start with
Byron/Shelley decision using shelley_known_slot, then for each subsequent era
boundary (allegra, mary, alonzo, babbage, conway) only compare slot < start when
the Option is Some, otherwise skip that check and continue to the next boundary;
alternatively maintain a current_era and advance it only when a boundary is Some
and slot >= that boundary, then return the final current_era.

Comment on lines +110 to +121
constitution: Some(u5c::Constitution {
anchor: Some(u5c::Anchor {
url: conway.constitution.anchor.url.clone(),
content_hash: conway.constitution.anchor.data_hash.clone().into(),
}),
hash: conway
.constitution
.script
.clone()
.unwrap_or_default()
.into(),
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Confirm constitution.hash meaning.

Spec typically expects a guardrail script hash, not the script bytes. You’re passing the optional script content (or empty). Validate against UTxO RPC schema.


🌐 Web query:

In utxorpc v1alpha cardano Genesis.Constitution, does `hash` hold the guardrail script hash or script bytes?

💡 Result:

It holds the guardrails script hash (the 28-byte script hash), not the full script bytes. [1][2][3]

Sources:

  • Ogmios changelog (notes guardrails field contains a script hash, blake2b 28 bytes). [1]
  • Cardano Constitution / docs (describes a script hash being associated with the Constitution/Guardrails Script). [2]
  • UTxORPC Cardano module reference (module uses script/script_hash types; scripts are referenced by hash). [3]

🏁 Script executed:

#!/bin/bash
# Examine the genesis.rs file around lines 110-121 to understand context and types
sed -n '100,130p' pallas-utxorpc/src/genesis.rs

Length of output: 1354


🏁 Script executed:

#!/bin/bash
# Search for conway structure/type definition and constitution fields
rg -n -A5 "conway\." pallas-utxorpc/src/genesis.rs | head -40

Length of output: 1905


🏁 Script executed:

#!/bin/bash
# Look for imports and see what u5c::Constitution and related types are
rg -n "use.*u5c|struct Constitution|conway:" pallas-utxorpc/src/genesis.rs | head -20

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Find conway module and GenesisFile structure
rg -n "pub.*struct GenesisFile|pub.*constitution" --type rust | grep -i conway | head -20

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Search for conway module definition in the codebase
fd -name "*.rs" -exec rg -l "mod conway|pub mod conway" {} \;

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Look for constitution-related types in conway module
rg -n "pub.*constitution|pub struct.*Constitution" --type rust | head -20

Length of output: 870


🏁 Script executed:

#!/bin/bash
# Check if there's a script_hash field separate from script in any constitution struct
rg -n "script_hash|guardrail" --type rust | head -30

Length of output: 3450


🏁 Script executed:

#!/bin/bash
# Inspect the Constitution struct definition in pallas-configs/src/conway.rs
sed -n '47,80p' pallas-configs/src/conway.rs

Length of output: 777


🏁 Script executed:

#!/bin/bash
# Get more context around the Constitution struct definition
rg -n -A15 "pub struct Constitution" pallas-configs/src/conway.rs

Length of output: 403


🏁 Script executed:

#!/bin/bash
# Check the UTxORPC spec type definition for u5c::Constitution
rg -n "message Constitution|pub struct Constitution" --type rust | grep -i "utxorpc\|u5c"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for script vs script_hash in conway module
rg -n "\.script|\.script_hash" pallas-configs/src/conway.rs

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for script hashing or blake2b utilities in the codebase
rg -n "blake2b|script.*hash|compute.*hash" pallas-utxorpc/src/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check nearby code in genesis.rs to see how other script hashes are handled
sed -n '1,50p' pallas-utxorpc/src/genesis.rs

Length of output: 2282


🏁 Script executed:

#!/bin/bash
# Look at the full function context where this Constitution code is
rg -n -B20 "constitution: Some" pallas-utxorpc/src/genesis.rs | head -50

Length of output: 2113


🏁 Script executed:

#!/bin/bash
# Check if there are other similar issues with script handling in the file
rg -n "\.script\." pallas-utxorpc/src/genesis.rs

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find the UTxORPC spec types (likely in proto or generated rust)
find . -name "*.rs" -type f | xargs rg -l "u5c::|utxorpc.*cardano" | head -10

Length of output: 120


🏁 Script executed:

#!/bin/bash
# Look for blake2b or script hashing in pallas-validate or pallas-crypto
rg -n "blake2b|compute.*script.*hash" --type rust | grep -E "validate|crypto" | head -20

Length of output: 2104


🏁 Script executed:

#!/bin/bash
# Check what the .into() conversion produces for Option<String>
rg -n "impl.*From.*Option.*String|impl.*Into.*for.*Option" pallas-utxorpc/src/ --type rust

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for hash function usage elsewhere in the file
sed -n '1,300p' pallas-utxorpc/src/genesis.rs | rg -n "hash|Hash"

Length of output: 123


Hash must be computed from script, not passed as script bytes.

The code passes conway.constitution.script (the script content) directly to the hash field. Per UTxORPC spec, this field should contain the 28-byte guardrails script hash, not the script bytes. Use script hashing utilities from pallas-validate::utils (e.g., compute_native_script_hash, compute_plutus_v1_script_hash) to compute the hash before assignment. Currently, the hash field will contain incorrect data.

🤖 Prompt for AI Agents
In pallas-utxorpc/src/genesis.rs around lines 110 to 121, the constitution.hash
is being set from the raw script bytes (conway.constitution.script) but must
contain the 28-byte guardrails script hash; replace the direct use of script
bytes with a computed hash using the appropriate pallas-validate::utils helper
(e.g., compute_native_script_hash for native scripts or
compute_plutus_v1_script_hash for Plutus V1), handle the Option/script variant
safely (return default or None if no script), convert the resulting 28-byte hash
into the expected type for the u5c::Constitution.hash field, and add the
necessary use/import for the chosen compute_* function.

Comment on lines +127 to +171
// Conway voting thresholds
drep_voting_thresholds: Some(u5c::DRepVotingThresholds {
motion_no_confidence: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.motion_no_confidence * 100.0) as i32,
denominator: 100,
}),
committee_normal: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.committee_normal * 100.0) as i32,
denominator: 100,
}),
committee_no_confidence: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.committee_no_confidence * 100.0)
as i32,
denominator: 100,
}),
update_to_constitution: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.update_to_constitution * 100.0)
as i32,
denominator: 100,
}),
hard_fork_initiation: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.hard_fork_initiation * 100.0) as i32,
denominator: 100,
}),
pp_network_group: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.pp_network_group * 100.0) as i32,
denominator: 100,
}),
pp_economic_group: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.pp_economic_group * 100.0) as i32,
denominator: 100,
}),
pp_technical_group: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.pp_technical_group * 100.0) as i32,
denominator: 100,
}),
pp_gov_group: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.pp_gov_group * 100.0) as i32,
denominator: 100,
}),
treasury_withdrawal: Some(u5c::RationalNumber {
numerator: (conway.d_rep_voting_thresholds.treasury_withdrawal * 100.0) as i32,
denominator: 100,
}),
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Rational thresholds: confirm required precision.

Multiplying by 100 and casting truncates. If spec demands precise thresholds, increase denominator and round.

Also applies to: 172-195


🌐 Web query:

What numeric precision is required for DRep/Pool voting thresholds in utxorpc v1alpha? Are percentages encoded as exact rationals or is 2‑decimal fixed acceptable?

💡 Result:

Short answer: thresholds are encoded as exact rationals (RationalNumber — numerator/denominator), not a 2‑decimal fixed string. You can of course express common percentages as rationals (e.g. 0.50 → 1/2 or 50/100), but send them using the spec's RationalNumber/UnitInterval types rather than assuming 2‑decimal fixed-point. [1][2][3]

Sources:

  • UTxO RPC spec / docs (shows Cardano voting threshold types live in the v1alpha Cardano schema). [1]
  • docs.rs listing for the generated utxorpc-spec showing DRepVotingThresholds / PoolVotingThresholds and RationalNumber types. [2]
  • Example code (aiken) converting Pool/DRep thresholds to Plutus using rational/array-of-rationals representation. [3]

I'll verify the source of the threshold values and their precision to determine if truncation is a legitimate concern.



🏁 Script executed:

#!/bin/bash
# Find where conway struct is defined and how thresholds are sourced
rg -n "d_rep_voting_thresholds" --type=rs -A 2 -B 2

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for conway struct definition
rg -n "struct.*[Cc]onway" --type=rs -A 5 | head -100

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Look for genesis data loading/parsing to understand source precision
rg -n "genesis|from_json|parse.*conway" --type=rs -A 3 -B 3 | head -150

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Check the full context around the flagged lines in genesis.rs
fd "genesis.rs" --type=rs --exec cat {} \; | head -250

Length of output: 169


🏁 Script executed:

#!/bin/bash
# Search for similar rational conversions elsewhere
rg -n "RationalNumber|as i32.*denominator" --type=rs -B 3 -A 1 | head -100

Length of output: 26



---

🏁 Script executed:

```shell
#!/bin/bash
# Find where conway struct is defined and threshold values sourced
rg -n "d_rep_voting_thresholds" --type rust -A 2 -B 2

Length of output: 4150


🏁 Script executed:

#!/bin/bash
# Search for conway struct definition
rg -n "struct.*[Cc]onway" --type rust -A 5 | head -100

Length of output: 584


🏁 Script executed:

#!/bin/bash
# Look for genesis data loading
rg -n "genesis|from_json|parse" --type rust | grep -i conway | head -50

Length of output: 917


🏁 Script executed:

#!/bin/bash
# Get the full genesis.rs file to understand context
find . -name "genesis.rs" -exec cat {} \;

Length of output: 15654


🏁 Script executed:

#!/bin/bash
# Search for RationalNumber usage patterns
rg -n "RationalNumber" --type rust -B 3 -A 1 | head -80

Length of output: 5979



---

🏁 Script executed:

```shell
#!/bin/bash
# Find DRepVotingThresholds struct definition
rg -n "struct DRepVotingThresholds|pub struct DRepVotingThresholds" --type rust -A 12

Length of output: 2939


🏁 Script executed:

#!/bin/bash
# Check conway config types (look at pallas-configs/src/conway.rs)
cat pallas-configs/src/conway.rs | head -150

Length of output: 2871


🏁 Script executed:

#!/bin/bash
# Search for any test genesis data or examples with voting thresholds
rg -n "motion_no_confidence|committee_normal" --type rust -B 2 -A 2 | grep -A 2 -B 2 "0\."

Length of output: 1630


🏁 Script executed:

#!/bin/bash
# Check if similar rational conversions elsewhere suggest a pattern
rg -n "\* (100|1000)\.0.*as i32" --type rust

Length of output: 1671


Add rounding to preserve voting threshold precision.

Voting thresholds are loaded from f32 genesis values and converted to rationals. Current code truncates (e.g., 50.1 → 50) instead of rounding. Per spec, exact rationals are required. Add .round() before the as i32 cast on all voting threshold conversions, or use a higher denominator to preserve full f32 precision.

Lines affected: 130, 134, 138, 143, 148, 152, 156, 160, 164, 168 (DRep); 174, 178, 182, 187, 191 (Pool).

Suggested fix:

numerator: (conway.d_rep_voting_thresholds.motion_no_confidence * 100.0).round() as i32,
🤖 Prompt for AI Agents
In pallas-utxorpc/src/genesis.rs around lines 127 to 171 (DRep voting
thresholds) and similarly for the Pool lines noted in the review, the
conversions from f32 to integer numerator are truncating (e.g., 50.1 → 50);
change each numerator expression to round the multiplied value before casting
(e.g., apply .round() on the result of * 100.0) so the RationalNumber numerators
preserve correct precision, or alternatively increase the denominator to capture
full f32 precision consistently across all listed threshold fields.

Comment on lines +295 to +346
pub fn map_era_summaries(
&self,
current_params: Option<MultiEraProtocolParameters>,
) -> u5c::EraSummaries {
// Include all eras that exist, not just ones with available slot data
let all_eras = self.genesis.available_eras();

let summaries = all_eras
.iter()
.enumerate()
.map(|(i, era)| {
let start_slot = self.genesis.era_start_slot(*era);
let end_slot = if i < all_eras.len() - 1 {
self.genesis.era_start_slot(all_eras[i + 1])
} else {
None // Current era has no end
};

let is_current_era = i == all_eras.len() - 1;

u5c::EraSummary {
name: era.to_string().to_lowercase(),
start: start_slot.map(|slot| {
let (epoch, _) = self.genesis.absolute_slot_to_relative(slot);
u5c::EraBoundary {
time: self.genesis.slot_to_wallclock(slot),
slot,
epoch,
}
}),
end: end_slot.map(|slot| {
let (epoch, _) = self.genesis.absolute_slot_to_relative(slot);
u5c::EraBoundary {
time: self.genesis.slot_to_wallclock(slot),
slot,
epoch,
}
}),
protocol_params: if is_current_era {
current_params
.as_ref()
.map(|params| self.map_pparams(params.clone()))
} else {
None
},
}
})
.collect();

u5c::EraSummaries { summaries }
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pick the actual current era for protocol_params.

Using the last enum entry as “current” sets protocol_params on Conway even when earlier eras are current or when slots are unknown. Use the last era with a known start slot instead.

Apply:

-        let summaries = all_eras
+        // Determine the last era with a known start slot (at least Shelley is known).
+        let last_known_index = all_eras
+            .iter()
+            .enumerate()
+            .rfind(|(_, era)| self.genesis.era_start_slot(**era).is_some())
+            .map(|(i, _)| i)
+            .unwrap_or(1); // Byron(0) and Shelley(1) always known
+
+        let summaries = all_eras
             .iter()
             .enumerate()
             .map(|(i, era)| {
                 let start_slot = self.genesis.era_start_slot(*era);
                 let end_slot = if i < all_eras.len() - 1 {
                     self.genesis.era_start_slot(all_eras[i + 1])
                 } else {
                     None // Current era has no end
                 };
 
-                let is_current_era = i == all_eras.len() - 1;
+                let is_current_era = i == last_known_index;

Comment on lines +747 to 748
timestamp: block.wallclock(&self.genesis),
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve ledger-provided timestamps; fall back to genesis wallclock.

Previously, when available, LedgerContext::get_slot_timestamp(slot) provided authoritative timestamps. Now it’s ignored. Prefer ledger first, then compute.

Apply:

         u5c::Block {
             header: u5c::BlockHeader {
                 slot: block.slot(),
                 hash: block.hash().to_vec().into(),
                 height: block.number(),
             }
             .into(),
             body: u5c::BlockBody {
                 tx: block.txs().iter().map(|x| self.map_tx(x)).collect(),
             }
             .into(),
-            timestamp: block.wallclock(&self.genesis),
+            timestamp: self.ledger
+                .as_ref()
+                .and_then(|ctx| ctx.get_slot_timestamp(block.slot()))
+                .unwrap_or_else(|| block.wallclock(&self.genesis)),
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timestamp: block.wallclock(&self.genesis),
}
timestamp: self.ledger
.as_ref()
.and_then(|ctx| ctx.get_slot_timestamp(block.slot()))
.unwrap_or_else(|| block.wallclock(&self.genesis)),
}
🤖 Prompt for AI Agents
In pallas-utxorpc/src/lib.rs around lines 747 to 748, the code unconditionally
uses block.wallclock(&self.genesis) for the timestamp; update it to prefer the
ledger-provided timestamp first (call the ledger/ledger context method that
returns an Option<Result/Option-like> timestamp for the given slot) and only
fall back to computing the wallclock from genesis when the ledger has no
timestamp; ensure you handle the Option/Result correctly (map/and_then or
unwrap_or) so the final field remains the same type as before.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants