Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
418 changes: 214 additions & 204 deletions Cargo.lock

Large diffs are not rendered by default.

62 changes: 59 additions & 3 deletions common/src/protocol_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
BlockVersionData, Committee, Constitution, CostModel, DRepVotingThresholds, Era, ExUnitPrices,
ExUnits, NetworkId, PoolVotingThresholds, ProtocolConsts,
};
use anyhow::Result;
use anyhow::{bail, Result};
use blake2::{digest::consts::U32, Blake2b, Digest};
use chrono::{DateTime, Utc};
use serde_with::serde_as;
Expand Down Expand Up @@ -220,11 +220,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 @@ -356,3 +372,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(())
}
}
37 changes: 36 additions & 1 deletion common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,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 @@ -1427,6 +1427,32 @@ 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,
}
}
}

#[derive(
serde::Serialize, serde::Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Hash,
)]
Expand Down Expand Up @@ -1504,6 +1530,14 @@ impl VotesCount {
}
}

pub fn infinity() -> Self {
Self {
committee: u64::MAX,
drep: u64::MAX,
pool: u64::MAX,
}
}

pub fn majorizes(&self, v: &VotesCount) -> bool {
self.committee >= v.committee && self.drep >= v.drep && self.pool >= v.pool
}
Expand Down Expand Up @@ -1545,6 +1579,7 @@ pub enum EnactStateElem {
Params(Box<ProtocolParamUpdate>),
Constitution(Constitution),
Committee(CommitteeChange),
ProtVer(protocol_params::ProtocolVersion),
NoConfidence,
}

Expand Down
35 changes: 32 additions & 3 deletions modules/governance_state/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,46 @@ This is ok, mainnet Cardano scanners detect this and show latest vote.
Other names: 'Gov Action Validity', 'govActionLifetime',
updated in governance_action_validity_period parameter (measured in epochs).
That is, if proposal is published in epoch E, then voting is finished at
the end of epoch E+governance_action_validity_period.
the end of epoch E+governance_action_validity_period+1.

In another words, each action is granted 'govActionLifetime' full
epochs for voting (P is proposal, E is expiration, govActionLifetime is 6):

'---P--|-----|-----|-----|-----|-----|-----|E'

For +1 origin see e.g. `cardano-node/cardano-testnet/src/Testnet/Components/Query.hs`:

```
-- | Obtains the @govActionLifetime@ from the protocol parameters.
-- The @govActionLifetime@ or governance action maximum lifetime in epochs is
-- the number of epochs such that a governance action submitted during an epoch @e@
-- expires if it is still not ratified as of the end of epoch: @e + govActionLifetime + 1@.
```

Default value (6) is taken from Conway Genesis, which is (in turn) taken from
Cardano book:
https://book.world.dev.cardano.org/environments/mainnet/conway-genesis.json

So, if voting starts at 529, gov_action_lifetime is 6, then the last epoch
when votes have meaning is 535. At the 535/536 transition all votes expire.

### Ratification process.
Ratification checked at epoch boundary.
If ratified, deposits returned immediately, actions take place at E+1/E+2
boundary.
If ratified at E/E+1 transition, actions take place at E+1/E+2 boundary.
Rewards are paid at E+1/E+2 transition (?)
Deposits transferred to reward account.

### Bootstrap period (Chang sub-era of Conway)

Conway era is split into two parts: Chang (9.0 protocol version) and
Plomin (10.0 protocol version). The first ("Chang") epoch has limited
governance ("bootstrap governance"):

* DReps may vote only for Info actions, they don't count for other actions.
* Only Info, ParameterChange and HardFork actions are allowed.

https://docs.cardano.org/about-cardano/evolution/upgrades/chang

### Genesis blocks
* Conway genesis: committee key hashes have prefix 'scriptHash-' (I believe,
'keyHash-'), followed by hex hash. To be researched....
Expand Down Expand Up @@ -74,6 +102,7 @@ epoch. Special message?
total. Need info about voting registration.
* DRep::epoch -- it's written that it's epoch, which has ended. But I receive
messages with this epoch in its beginning. Need to sort out.
* Implement bootstrap period (resolved).
* What if voting length changes during voting process? E.g.:
(a) epoch 100 voting starts, voting length is 10 epochs
(b) epoch 107 voting length change to 3 enacts (e.g., because some earlier successful vote)
Expand Down
89 changes: 89 additions & 0 deletions modules/governance_state/data/check_conway_syncdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Standalone test for Conway governance correctness checking
# Use: check_conway_syncdb.py <dbsync-csv-output> <acropolis-conway-output>

import sys

def screen_strings(header):
inside = False
prev = " "
out = ""
for ptr in range(0,len(header)):
if prev == '\"' and header[ptr] != '\"':
inside = not inside

if header[ptr] == ',' and inside:
out += ';'
else:
out += header[ptr]
prev = header[ptr]

return out

def strip_some(s):
return s.removeprefix('Some(').removesuffix(')')

if len(sys.argv) <= 2:
print("\nUsage: %s <dbsync-csv-file> <acropolis-conway-output>\n" % sys.argv[0])
exit(1)

dbsync = open(sys.argv[1],'rt')
acropolis = open(sys.argv[2],'rt')

dbsync_dict = {}

# 0 7 13 23
header = """id,tx_id,index,prev_gov_action_proposal,deposit,return_address,expiration,
voting_anchor_id,type,description,param_proposal,ratified_epoch,enacted_epoch,
dropped_epoch,expired_epoch,id,hash,block_id,block_index,out_sum,fee,deposit,size,
invalid_before,invalid_hereafter,valid_contract,script_size,treasury_donation"""
header = screen_strings(header)
hl = header.split(',')

for s in dbsync:
sh = screen_strings(s)
sl = sh.strip().split(',')
if len(sl) != len(hl):
continue
if sl == hl:
continue

hhash = sl[16].strip("\\x")

idx = (hhash,sl[2])
if idx not in dbsync_dict:
dbsync_dict[idx] = []
dbsync_dict[idx].append((sl[8],sl[11],sl[12],sl[13],sl[14]))

header_a = """id,start,tx_id,index,prev_gov_action_proposal,deposit,return_address,expiration,
voting_anchor_id,type,description,param_proposal,ratified_epoch,enacted_epoch,
dropped_epoch,expired_epoch"""
al = header_a.split(',')

found = 0
for s in acropolis:
sh = screen_strings(s)
sl = sh.strip().split(',')
if len(sl) < 14:
print("Rejecting:",s)
continue
if sl == al:
continue

idx = (sl[1],sl[2])
if idx not in dbsync_dict:
print(idx," not found\n")
else:
rec = dbsync_dict[idx]
if len(rec) > 1:
print(idx, "too many records: ",rec,"\n")

found += 1
(ty,re,en,de,ex) = rec[0]
if ty == "InfoAction":
ty = "Information"
if ty != sl[8]: print (idx, "types do not match: `",ty,"` `",sl[8],"`\n")
if re != strip_some(sl[11]): print (idx, "re do not match: `",re,"` `",sl[11],"`\n")
if en != strip_some(sl[12]): print (idx, "en do not match: `",en,"` `",sl[12],"`\n")
if ex != strip_some(sl[14]): print (idx, "ex do not match: `",ex,"` `",sl[14],"`\n")

print("Total found records: ",found,"\n")
Loading