Skip to content

Commit 15560c0

Browse files
committed
feat: implement epoch nonce calculation on epochs state
1 parent b99db91 commit 15560c0

File tree

18 files changed

+483
-59
lines changed

18 files changed

+483
-59
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/src/calculations.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ pub fn slot_to_epoch_with_shelley_params(
2020
}
2121
}
2222

23+
pub fn epoch_to_first_slot_with_shelley_params(
24+
epoch: u64,
25+
shelley_epoch: u64,
26+
shelley_epoch_len: u64,
27+
) -> u64 {
28+
if epoch >= shelley_epoch {
29+
shelley_epoch * BYRON_SLOTS_PER_EPOCH + (epoch - shelley_epoch) * shelley_epoch_len
30+
} else {
31+
epoch * BYRON_SLOTS_PER_EPOCH
32+
}
33+
}
34+
2335
pub fn slot_to_timestamp_with_params(slot: u64, byron_timestamp: u64, shelley_epoch: u64) -> u64 {
2436
let shelley_start_slot = shelley_epoch * BYRON_SLOTS_PER_EPOCH;
2537
if slot < shelley_start_slot {
@@ -42,6 +54,10 @@ mod tests {
4254
slot_to_epoch_with_shelley_params(slot, SHELLEY_START_EPOCH, SHELLEY_SLOTS_PER_EPOCH)
4355
}
4456

57+
fn epoch_to_first_slot(epoch: u64) -> u64 {
58+
epoch_to_first_slot_with_shelley_params(epoch, SHELLEY_START_EPOCH, SHELLEY_SLOTS_PER_EPOCH)
59+
}
60+
4561
fn slot_to_timestamp(slot: u64) -> u64 {
4662
slot_to_timestamp_with_params(slot, BYRON_START_TIMESTAMP, SHELLEY_START_EPOCH)
4763
}
@@ -83,4 +99,12 @@ mod tests {
8399
assert_eq!(slot_to_epoch(98_272_003), (425, 35_203));
84100
assert_eq!(slot_to_timestamp(98_272_003), 1689838294);
85101
}
102+
103+
#[test]
104+
fn epoch_to_first_slot_test() {
105+
assert_eq!(slot_to_epoch(epoch_to_first_slot(208)), (208, 0));
106+
assert_eq!(slot_to_epoch(epoch_to_first_slot(0)), (0, 0));
107+
assert_eq!(slot_to_epoch(epoch_to_first_slot(209)), (209, 0));
108+
assert_eq!(slot_to_epoch(epoch_to_first_slot(150)), (150, 0));
109+
}
86110
}

common/src/genesis_values.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
use crate::calculations::{slot_to_epoch_with_shelley_params, slot_to_timestamp_with_params};
1+
use crate::calculations::{
2+
epoch_to_first_slot_with_shelley_params, slot_to_epoch_with_shelley_params,
3+
slot_to_timestamp_with_params,
4+
};
5+
const MAINNET_SHELLEY_GENESIS_HASH: &str =
6+
"1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81";
27

38
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
49
pub struct GenesisValues {
510
pub byron_timestamp: u64,
611
pub shelley_epoch: u64,
712
pub shelley_epoch_len: u64,
13+
pub shelley_genesis_hash: [u8; 32],
814
}
915

1016
impl GenesisValues {
@@ -13,6 +19,7 @@ impl GenesisValues {
1319
byron_timestamp: 1506203091,
1420
shelley_epoch: 208,
1521
shelley_epoch_len: 432000,
22+
shelley_genesis_hash: MAINNET_SHELLEY_GENESIS_HASH.as_bytes().try_into().unwrap(),
1623
}
1724
}
1825

@@ -22,4 +29,8 @@ impl GenesisValues {
2229
pub fn slot_to_timestamp(&self, slot: u64) -> u64 {
2330
slot_to_timestamp_with_params(slot, self.byron_timestamp, self.shelley_epoch)
2431
}
32+
33+
pub fn epoch_to_first_slot(&self, epoch: u64) -> u64 {
34+
epoch_to_first_slot_with_shelley_params(epoch, self.shelley_epoch, self.shelley_epoch_len)
35+
}
2536
}

common/src/messages.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use crate::genesis_values::GenesisValues;
77
use crate::ledger_state::SPOState;
8-
use crate::protocol_params::ProtocolParams;
8+
use crate::protocol_params::{Nonce, ProtocolParams};
99
use crate::queries::parameters::{ParametersStateQuery, ParametersStateQueryResponse};
1010
use crate::queries::{
1111
accounts::{AccountsStateQuery, AccountsStateQueryResponse},
@@ -143,6 +143,9 @@ pub struct EpochActivityMessage {
143143
/// List of all VRF vkey hashes used on blocks (SPO indicator) and
144144
/// number of blocks produced
145145
pub vrf_vkey_hashes: Vec<(KeyHash, usize)>,
146+
147+
/// Nonce
148+
pub nonce: Option<Nonce>,
146149
}
147150

148151
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]

common/src/protocol_params.rs

Lines changed: 174 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use crate::{
2+
genesis_values::GenesisValues,
23
rational_number::{ChameleonFraction, RationalNumber},
3-
BlockVersionData, Committee, Constitution, CostModel, DRepVotingThresholds, ExUnitPrices,
4-
ExUnits, PoolVotingThresholds, ProtocolConsts,
4+
BlockVersionData, Committee, Constitution, CostModel, DRepVotingThresholds, Era, ExUnitPrices,
5+
ExUnits, NetworkId, PoolVotingThresholds, ProtocolConsts,
56
};
7+
use anyhow::Result;
8+
use blake2::{digest::consts::U32, Blake2b, Digest};
69
use chrono::{DateTime, Utc};
710
use serde_with::serde_as;
811

@@ -95,12 +98,6 @@ pub struct ShelleyProtocolParams {
9598
pub pool_pledge_influence: RationalNumber,
9699
}
97100

98-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
99-
pub enum NetworkId {
100-
Testnet,
101-
Mainnet,
102-
}
103-
104101
#[serde_as]
105102
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
106103
#[serde(rename_all = "camelCase")]
@@ -127,6 +124,55 @@ pub struct ShelleyParams {
127124
pub update_quorum: u32,
128125
}
129126

127+
#[serde_as]
128+
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
129+
#[serde(rename_all = "camelCase")]
130+
pub struct PraosParams {
131+
pub security_param: u32,
132+
#[serde_as(as = "ChameleonFraction")]
133+
pub active_slots_coeff: RationalNumber,
134+
pub epoch_length: u32,
135+
pub max_kes_evolutions: u32,
136+
pub max_lovelace_supply: u64,
137+
pub network_id: NetworkId,
138+
pub slot_length: u32,
139+
pub slots_per_kes_period: u32,
140+
141+
/// Relative slot from which data of the previous epoch can be considered stable.
142+
/// This value is used for all TPraos eras AND Babbage Era from Praos
143+
pub stability_window: u64,
144+
145+
/// Number of slots at the end of each epoch which do NOT contribute randomness to the candidate
146+
/// nonce of the following epoch.
147+
/// This value is used for all Praos eras except Babbage
148+
pub randomness_stabilization_window: u64,
149+
}
150+
151+
impl From<&ShelleyParams> for PraosParams {
152+
fn from(params: &ShelleyParams) -> Self {
153+
let active_slots_coeff = params.active_slots_coeff;
154+
let security_param = params.security_param;
155+
let stability_window =
156+
(security_param as u64) * active_slots_coeff.denom() / active_slots_coeff.numer() * 3;
157+
let randomness_stabilization_window =
158+
(security_param as u64) * active_slots_coeff.denom() / active_slots_coeff.numer() * 4;
159+
160+
Self {
161+
security_param: security_param,
162+
active_slots_coeff: active_slots_coeff,
163+
epoch_length: params.epoch_length,
164+
max_kes_evolutions: params.max_kes_evolutions,
165+
max_lovelace_supply: params.max_lovelace_supply,
166+
network_id: params.network_id.clone(),
167+
slot_length: params.slot_length,
168+
slots_per_kes_period: params.slots_per_kes_period,
169+
170+
stability_window: stability_window,
171+
randomness_stabilization_window: randomness_stabilization_window,
172+
}
173+
}
174+
}
175+
130176
//
131177
// Babbage protocol parameters
132178
//
@@ -164,16 +210,133 @@ pub struct ProtocolVersion {
164210
pub major: u64,
165211
}
166212

167-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
213+
#[derive(
214+
Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
215+
)]
168216
#[serde(rename_all = "PascalCase")]
169217
pub enum NonceVariant {
218+
#[default]
170219
NeutralNonce,
171220
Nonce,
172221
}
173222

174-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
223+
pub type NonceHash = [u8; 32];
224+
225+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
175226
#[serde(rename_all = "camelCase")]
176227
pub struct Nonce {
177228
pub tag: NonceVariant,
178-
pub hash: Option<Vec<u8>>,
229+
pub hash: Option<NonceHash>,
230+
}
231+
232+
impl Default for Nonce {
233+
fn default() -> Self {
234+
Self {
235+
tag: NonceVariant::NeutralNonce,
236+
hash: None,
237+
}
238+
}
239+
}
240+
241+
impl From<NonceHash> for Nonce {
242+
fn from(hash: NonceHash) -> Self {
243+
Self {
244+
tag: NonceVariant::Nonce,
245+
hash: Some(hash),
246+
}
247+
}
248+
}
249+
250+
#[derive(
251+
Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize,
252+
)]
253+
pub struct Nonces {
254+
pub epoch: u64,
255+
pub active: Nonce,
256+
pub evolving: Nonce,
257+
pub candidate: Nonce,
258+
// Nonce constructed from the hash of the Last Applied Block
259+
pub lab: Nonce,
260+
// Nonce corresponding to the LAB nonce of the last block of the previous epoch
261+
pub prev_lab: Nonce,
262+
}
263+
264+
impl Nonces {
265+
pub fn shelley_genesis_nonces(genesis: &GenesisValues) -> Nonces {
266+
Nonces {
267+
epoch: genesis.shelley_epoch,
268+
active: genesis.shelley_genesis_hash.into(),
269+
evolving: genesis.shelley_genesis_hash.into(),
270+
candidate: genesis.shelley_genesis_hash.into(),
271+
lab: Nonce::default(),
272+
prev_lab: Nonce::default(),
273+
}
274+
}
275+
276+
pub fn from_candidate(candidate: &Nonce, prev_lab: &Nonce) -> Result<Nonce> {
277+
let Some(candidate_hash) = candidate.hash.as_ref() else {
278+
return Err(anyhow::anyhow!("Candidate hash is not set"));
279+
};
280+
281+
// if prev_lab is Neutral then just return candidate
282+
// this is for second shelley epoch boundary (from 208 to 209 in mainnet)
283+
match prev_lab.tag {
284+
NonceVariant::NeutralNonce => {
285+
return Ok(candidate.clone());
286+
}
287+
NonceVariant::Nonce => {
288+
let Some(prev_lab_hash) = prev_lab.hash.as_ref() else {
289+
return Err(anyhow::anyhow!("Prev lab hash is not set"));
290+
};
291+
let mut hasher = Blake2b::<U32>::new();
292+
hasher.update(&[&candidate_hash.clone()[..], &prev_lab_hash.clone()[..]].concat());
293+
let hash: NonceHash = hasher.finalize().into();
294+
Ok(Nonce::from(hash))
295+
}
296+
}
297+
}
298+
299+
/// Evolve the current nonce by combining it with the current rolling nonce and the
300+
/// range-extended tagged leader VRF output.
301+
///
302+
/// Specifically, we combine it with `η` (a.k.a eta), which is a blake2b-256 hash of the
303+
/// tagged leader VRF output after a range extension. The range extension is, yet another
304+
/// blake2b-256 hash.
305+
pub fn evolve(current: &Nonce, nonce_vrf_output: &Vec<u8>,) -> Result<Nonce> {
306+
// first hash nonce_vrf_output
307+
let mut hasher = Blake2b::<U32>::new();
308+
hasher.update(nonce_vrf_output.as_slice());
309+
let nonce_vrf_output_hash: [u8; 32] = hasher.finalize().into();
310+
311+
match current.hash.as_ref() {
312+
Some(nonce) => {
313+
let mut hasher = Blake2b::<U32>::new();
314+
hasher.update(&[&nonce.clone()[..], &nonce_vrf_output_hash[..]].concat());
315+
let hash: NonceHash = hasher.finalize().into();
316+
Ok(Nonce::from(hash))
317+
}
318+
_ => Err(anyhow::anyhow!("Current nonce is not set")),
319+
}
320+
}
321+
322+
pub fn randomness_stability_window(
323+
era: Era,
324+
slot: u64,
325+
genesis: &GenesisValues,
326+
params: &PraosParams,
327+
) -> bool {
328+
let (epoch, _) = genesis.slot_to_epoch(slot);
329+
let next_epoch_first_slot = genesis.epoch_to_first_slot(epoch + 1);
330+
331+
// For Praos in Babbage (just as in all TPraos eras) we use the
332+
// smaller (3k/f vs 4k/f slots) stability window here for
333+
// backwards-compatibility. See erratum 17.3 in the Shelley ledger
334+
// specs for context
335+
let window = match era {
336+
Era::Conway => params.randomness_stabilization_window,
337+
_ => params.stability_window,
338+
};
339+
340+
slot + window < next_epoch_first_slot
341+
}
179342
}

common/src/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ use std::collections::{HashMap, HashSet};
1818
use std::fmt::{Display, Formatter};
1919
use std::ops::Neg;
2020

21+
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22+
pub enum NetworkId {
23+
Testnet,
24+
#[default]
25+
Mainnet,
26+
}
27+
2128
/// Protocol era
2229
#[derive(
2330
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,

modules/accounts_state/src/monetary.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ fn calculate_monetary_expansion(
107107
mod tests {
108108
use super::*;
109109
use acropolis_common::protocol_params::{
110-
NetworkId, Nonce, NonceVariant, ProtocolVersion, ShelleyProtocolParams,
110+
Nonce, NonceVariant, ProtocolVersion, ShelleyProtocolParams,
111111
};
112112
use acropolis_common::rational_number::rational_number_from_f32;
113+
use acropolis_common::NetworkId;
113114
use chrono::{DateTime, Utc};
114115

115116
// Known values at start of Shelley - from Java reference and DBSync

modules/epochs_state/src/epochs_history.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ mod tests {
9494
total_blocks: 1,
9595
total_fees: 50,
9696
vrf_vkey_hashes: vec![],
97+
nonce: None,
9798
},
9899
);
99100

0 commit comments

Comments
 (0)