Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b189c37
Additional debug
shd Sep 22, 2025
7802061
Merge branch 'main' of github.com:input-output-hk/acropolis
shd Sep 22, 2025
ba40986
Merge branch 'main' of github.com:input-output-hk/acropolis
shd Sep 29, 2025
b7ac171
Better debug for verification: first version
shd Sep 29, 2025
1ca1edd
Moved conway_voting, implementing unit tests
shd Sep 30, 2025
1ecc585
Tests fixed
shd Sep 30, 2025
572a196
Better logging; almost correct voting
shd Oct 1, 2025
2d73f88
Replayer fixed, bugs found (added to NOTES)
shd Oct 3, 2025
9522462
Hardfork voting corrected
shd Oct 6, 2025
6a75771
Added tests
shd Oct 6, 2025
d2d388a
Conway governance tests
shd Oct 6, 2025
3e47201
Merged
shd Oct 6, 2025
6d4c9b8
Notes updated
shd Oct 6, 2025
a548537
Added logs for voting stats
shd Oct 6, 2025
9d6bbc7
Formatter applied
shd Oct 7, 2025
e77214d
Simplified code, updated tests
shd Oct 7, 2025
1b0de1d
Formatted
shd Oct 7, 2025
a1d371a
Added info about voting in Haskell node
shd Oct 7, 2025
3c53b0d
Structures reading-writing
shd Oct 16, 2025
6b4cc4b
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Oct 16, 2025
4b82dfc
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Oct 17, 2025
29a48ba
Minimal requirement for tests
shd Oct 24, 2025
2e67eed
Merge with main
shd Oct 25, 2025
8b6dbf3
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Oct 25, 2025
9004ff5
Added unit test for mainnet governance
shd Oct 26, 2025
5b8e703
After formatter
shd Oct 26, 2025
2716b97
Merged, updated comments in PR
shd Oct 27, 2025
06d1e65
Applied formatter to code
shd Oct 27, 2025
c797b45
Merged changes from PR 231
shd Oct 27, 2025
6742d12
Types changed
shd Oct 29, 2025
2c1757f
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Oct 29, 2025
8261dcc
Review fixes, merge with main
shd Oct 29, 2025
27929b8
Formatter applied
shd Oct 29, 2025
bf4821d
Fixed format; test is to be done
shd Oct 29, 2025
e868ddd
Applied formatter once more
shd Oct 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 131 additions & 134 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion codec/src/map_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ fn map_governance_action(action: &conway::GovAction) -> Result<GovernanceAction>
conway::GovAction::HardForkInitiation(id, version) => Ok(
GovernanceAction::HardForkInitiation(HardForkInitiationAction {
previous_action_id: map_nullable_gov_action_id(id)?,
protocol_version: *version,
protocol_version: protocol_params::ProtocolVersion::new(version.0, version.1),
}),
),

Expand Down
1 change: 1 addition & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ hex = { workspace = true }
lf-queue = "0.1.0"
memmap2 = "0.9"
num-rational = { version = "0.4.2", features = ["serde"] }
regex = "1"
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true, features = ["base64"] }
Expand Down
65 changes: 62 additions & 3 deletions common/src/protocol_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
ExUnitPrices, ExUnits, GenesisDelegate, HeavyDelegate, NetworkId, PoolVotingThresholds,
ProtocolConsts,
};
use anyhow::Result;
use anyhow::{bail, Result};
use blake2::{digest::consts::U32, Blake2b, Digest};
use chrono::{DateTime, Utc};
use serde_with::{hex::Hex, serde_as};
Expand All @@ -25,12 +25,15 @@ pub struct ProtocolParams {
// Byron protocol parameters
//

#[serde_as]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ByronParams {
pub block_version_data: BlockVersionData,
pub fts_seed: Option<Vec<u8>>,
pub protocol_consts: ProtocolConsts,
pub start_time: u64,

#[serde_as(as = "Vec<(_, _)>")]
pub heavy_delegation: HashMap<Vec<u8>, HeavyDelegate>,
}

Expand Down Expand Up @@ -227,11 +230,27 @@ pub struct ConwayParams {
pub committee: Committee,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProtocolVersion {
pub minor: u64,
pub major: u64,
pub minor: u64,
}

impl ProtocolVersion {
pub fn new(major: u64, minor: u64) -> Self {
Self { major, minor }
}

pub fn is_chang(&self) -> Result<bool> {
if self.major == 9 {
if self.minor != 0 {
bail!("Chang version 9.xx with nonzero xx is not supported")
}
return Ok(true);
}
Ok(false)
}
}

#[derive(
Expand Down Expand Up @@ -370,3 +389,43 @@ impl Nonces {
slot + window < next_epoch_first_slot
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_protocol_version_order() {
let v9_0 = ProtocolVersion::new(9, 0);
let v9_1 = ProtocolVersion::new(9, 1);
let v9_10 = ProtocolVersion::new(9, 10);
let v10_0 = ProtocolVersion::new(10, 0);
let v10_9 = ProtocolVersion::new(10, 9);
let v10_10 = ProtocolVersion::new(10, 10);
let v10_11 = ProtocolVersion::new(10, 11);

assert!(v10_9 > v9_10);

let from = vec![v9_0, v9_1, v9_10, v10_0, v10_9, v10_10, v10_11];
let mut upd = from.clone();
upd.sort();

assert_eq!(from, upd);
}

#[test]
fn test_protocol_version_parsing() -> Result<()> {
let v9_0 = serde_json::from_slice::<ProtocolVersion>(b"{\"minor\": 0, \"major\": 9}")?;
let v9_0a = serde_json::from_slice::<ProtocolVersion>(b"{\"major\": 9, \"minor\": 0}")?;
let v0_9 = serde_json::from_slice::<ProtocolVersion>(b"{\"minor\": 9, \"major\": 0}")?;
let v0_9a = serde_json::from_slice::<ProtocolVersion>(b"{\"major\": 0, \"minor\": 9}")?;

assert_eq!(v9_0, v9_0a);
assert_eq!(v0_9, v0_9a);
assert_eq!(v9_0, ProtocolVersion::new(9, 0));
assert_eq!(v9_0.major, 9);
assert_eq!(v0_9, ProtocolVersion::new(0, 9));

Ok(())
}
}
194 changes: 174 additions & 20 deletions common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ use anyhow::{anyhow, bail, Error, Result};
use bech32::{Bech32, Hrp};
use bitmask_enum::bitmask;
use hex::decode;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_with::{hex::Hex, serde_as};
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::ops::{AddAssign, Neg};
use std::{cmp::Ordering, fmt};
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
fmt,
fmt::{Display, Formatter},
ops::{AddAssign, Neg},
str::FromStr,
};

/// Network identifier
#[derive(
Expand Down Expand Up @@ -1565,7 +1570,7 @@ pub struct ParameterChangeAction {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct HardForkInitiationAction {
pub previous_action_id: Option<GovActionId>,
pub protocol_version: (u64, u64),
pub protocol_version: protocol_params::ProtocolVersion,
}

#[serde_as]
Expand Down Expand Up @@ -1608,6 +1613,44 @@ pub enum GovernanceAction {
Information,
}

impl GovernanceAction {
pub fn get_previous_action_id(&self) -> Option<GovActionId> {
match &self {
Self::ParameterChange(ParameterChangeAction {
previous_action_id: prev,
..
}) => prev.clone(),
Self::HardForkInitiation(HardForkInitiationAction {
previous_action_id: prev,
..
}) => prev.clone(),
Self::TreasuryWithdrawals(_) => None,
Self::NoConfidence(prev) => prev.clone(),
Self::UpdateCommittee(UpdateCommitteeAction {
previous_action_id: prev,
..
}) => prev.clone(),
Self::NewConstitution(NewConstitutionAction {
previous_action_id: prev,
..
}) => prev.clone(),
Self::Information => None,
}
}

pub fn get_action_name(&self) -> &str {
match &self {
GovernanceAction::ParameterChange(_) => "ParameterChange",
GovernanceAction::HardForkInitiation(_) => "HardForkInitiation",
GovernanceAction::TreasuryWithdrawals(_) => "TreasuryWithdrawals",
GovernanceAction::NoConfidence(_) => "NoConfidence",
GovernanceAction::UpdateCommittee(_) => "UpdateCommittee",
GovernanceAction::NewConstitution(_) => "NewConstitution",
GovernanceAction::Information => "Information",
}
}
}

#[derive(
serde::Serialize, serde::Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Hash,
)]
Expand Down Expand Up @@ -1669,38 +1712,104 @@ pub struct VotingProcedures {
pub votes: HashMap<Voter, SingleVoterVotes>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct VotesCount {
pub committee: u64,
pub drep: u64,
pub pool: u64,
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct VoteCount {
pub yes: u64,
pub no: u64,
pub abstain: u64,
}

impl VotesCount {
impl VoteCount {
pub fn zero() -> Self {
Self {
committee: 0,
drep: 0,
pool: 0,
yes: 0,
no: 0,
abstain: 0,
}
}

pub fn majorizes(&self, v: &VotesCount) -> bool {
self.committee >= v.committee && self.drep >= v.drep && self.pool >= v.pool
pub fn total(&self) -> u64 {
self.yes + self.no + self.abstain
}
}

impl Display for VoteCount {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "y{}/n{}/a{}", self.yes, self.no, self.abstain)
}
}

impl FromStr for VoteCount {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
let re = Regex::new(r"y(\d+)/n(\d+)/a(\d+)$").unwrap();
let caps = re.captures(s).ok_or_else(|| anyhow!("Invalid VoteCount string: '{s}'"))?;

let yes = u64::from_str(&caps[1])?;
let no = u64::from_str(&caps[2])?;
let abstain = u64::from_str(&caps[3])?;

Ok(VoteCount { yes, no, abstain })
}
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct VoteResult<E: FromStr + Display> {
pub committee: E,
pub drep: E,
pub pool: E,
}

impl<E: FromStr + Display> VoteResult<E> {
pub fn new(committee: E, drep: E, pool: E) -> Self {
Self {
committee,
drep,
pool,
}
}
}

impl Display for VotesCount {
impl<E: FromStr + Display> Display for VoteResult<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "c{}:d{}:p{}", self.committee, self.drep, self.pool)
write!(f, "c{}:d{}:s{}", self.committee, self.drep, self.pool)
}
}

impl<E: FromStr + Display> FromStr for VoteResult<E> {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
// Regex for capturing each section
let Ok(re) = Regex::new(r"^c([^:]+):d([^:]+):s([^:]+)$") else {
bail!("Cannot parse redex");
};
let caps = re.captures(s).ok_or_else(|| anyhow!("Invalid VoteResult string: '{s}'"))?;

let Ok(committee) = E::from_str(&caps[1]) else {
bail!("Incorrect committee value {}", &caps[1]);
};
let Ok(drep) = E::from_str(&caps[2]) else {
bail!("Incorrect DRep value {}", &caps[2]);
};
let Ok(pool) = E::from_str(&caps[3]) else {
bail!("Incorrect SPO value {}", &caps[3]);
};

Ok(VoteResult {
committee,
drep,
pool,
})
}
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct VotingOutcome {
pub procedure: ProposalProcedure,
pub votes_cast: VotesCount,
pub votes_threshold: VotesCount,
pub votes_cast: VoteResult<VoteCount>,
pub votes_threshold: VoteResult<RationalNumber>,
pub accepted: bool,
}

Expand All @@ -1726,6 +1835,7 @@ pub enum EnactStateElem {
Params(Box<ProtocolParamUpdate>),
Constitution(Constitution),
Committee(CommitteeChange),
ProtVer(protocol_params::ProtocolVersion),
NoConfidence,
}

Expand Down Expand Up @@ -2012,4 +2122,48 @@ mod tests {

Ok(())
}

#[test]
fn parse_voting_values() -> Result<()> {
let count = VoteCount::from_str("y0/n5/a1")?;
assert_eq!(count.yes, 0);
assert_eq!(count.no, 5);
assert_eq!(count.abstain, 1);

let counts: VoteResult<VoteCount> =
VoteResult::from_str("cy0/n5/a1:dy0/n1/a2:sy123/n456/a0788890")?;
assert_eq!(counts.committee, count);
assert_eq!(counts.drep.yes, 0);
assert_eq!(counts.drep.no, 1);
assert_eq!(counts.drep.abstain, 2);
assert_eq!(counts.pool.yes, 123);
assert_eq!(counts.pool.no, 456);
assert_eq!(counts.pool.abstain, 788890);
Ok(())
}

#[test]
fn serialize_stake_addres() -> Result<()> {
let serialized = "{\
\"network\":\"Mainnet\",\
\"credential\":{\
\"AddrKeyHash\":\"45dee6ee5d7f631b6226d45f29da411c42fa7e816dc0948d31e0dba7\"\
}\
}";

let addr = serde_json::from_str::<StakeAddress>(serialized)?;
assert_eq!(addr.network, NetworkId::Mainnet);
assert_eq!(
addr.credential,
StakeCredential::AddrKeyHash(KeyHash::from([
0x45, 0xde, 0xe6, 0xee, 0x5d, 0x7f, 0x63, 0x1b, 0x62, 0x26, 0xd4, 0x5f, 0x29, 0xda,
0x41, 0x1c, 0x42, 0xfa, 0x7e, 0x81, 0x6d, 0xc0, 0x94, 0x8d, 0x31, 0xe0, 0xdb, 0xa7,
]))
);

let serialized_back = serde_json::to_string(&addr)?;
assert_eq!(serialized_back, serialized);

Ok(())
}
}
2 changes: 2 additions & 0 deletions modules/governance_state/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ caryatid_sdk = { workspace = true }
anyhow = { workspace = true }
async-trait = "0.1"
config = { workspace = true }
csv = "1"
hex = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true, features = ["base64"] }
tokio = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.20", features = ["registry", "env-filter"] }

[lib]
path = "src/governance_state.rs"
Loading
Loading