unc_chain_configs/
genesis_config.rs

1//! Genesis Configuration
2//!
3//! NOTE: chain-configs is not the best place for `GenesisConfig` since it
4//! contains `RuntimeConfig`, but we keep it here for now until we figure
5//! out the better place.
6use crate::genesis_validate::validate_genesis;
7use anyhow::Context;
8use chrono::{DateTime, Utc};
9use num_rational::Rational32;
10use serde::de::{self, DeserializeSeed, IgnoredAny, MapAccess, SeqAccess, Visitor};
11use serde::{Deserialize, Deserializer, Serialize};
12use serde_json::Serializer;
13use sha2::digest::Digest;
14use smart_default::SmartDefault;
15use std::collections::HashSet;
16use std::fmt;
17use std::fs::File;
18use std::io::{BufReader, Read};
19use std::path::{Path, PathBuf};
20use tracing::warn;
21use unc_config_utils::ValidationError;
22use unc_parameters::{RuntimeConfig, RuntimeConfigView};
23use unc_primitives::epoch_manager::EpochConfig;
24use unc_primitives::shard_layout::ShardLayout;
25use unc_primitives::types::validator_power_and_pledge::ValidatorPowerAndPledge;
26use unc_primitives::types::StateRoot;
27use unc_primitives::{
28    hash::CryptoHash,
29    serialize::dec_format,
30    state_record::StateRecord,
31    types::{
32        AccountId, AccountInfo, Balance, BlockHeight, BlockHeightDelta, Gas, NumBlocks, NumSeats,
33    },
34    version::ProtocolVersion,
35};
36
37const MAX_GAS_PRICE: Balance = 10_000_000_000_000_000_000_000;
38
39fn default_online_min_threshold() -> Rational32 {
40    Rational32::new(90, 100)
41}
42
43fn default_online_max_threshold() -> Rational32 {
44    Rational32::new(99, 100)
45}
46
47fn default_minimum_pledge_divisor() -> u64 {
48    10
49}
50
51fn default_protocol_upgrade_pledge_threshold() -> Rational32 {
52    Rational32::new(8, 10)
53}
54
55fn default_shard_layout() -> ShardLayout {
56    ShardLayout::v0_single_shard()
57}
58
59fn default_minimum_pledge_ratio() -> Rational32 {
60    Rational32::new(160, 1_000_000)
61}
62
63fn default_minimum_validators_per_shard() -> u64 {
64    1
65}
66
67fn default_num_chunk_only_producer_seats() -> u64 {
68    300
69}
70
71fn default_use_production_config() -> bool {
72    false
73}
74
75fn default_max_kickout_pledge_threshold() -> u8 {
76    100
77}
78
79#[derive(Debug, Clone, SmartDefault, serde::Serialize, serde::Deserialize)]
80pub struct GenesisConfig {
81    /// Protocol version that this genesis works with.
82    pub protocol_version: ProtocolVersion,
83    /// Official time of blockchain start.
84    #[default(Utc::now())]
85    pub genesis_time: DateTime<Utc>,
86    /// ID of the blockchain. This must be unique for every blockchain.
87    /// If your testnet blockchains do not have unique chain IDs, you will have a bad time.
88    pub chain_id: String,
89    /// Height of genesis block.
90    pub genesis_height: BlockHeight,
91    /// Number of block producer seats at genesis.
92    pub num_block_producer_seats: NumSeats,
93    /// Defines number of shards and number of block producer seats per each shard at genesis.
94    /// Note: not used with protocol_feature_chunk_only_producers -- replaced by minimum_validators_per_shard
95    /// Note: not used before as all block producers produce chunks for all shards
96    pub num_block_producer_seats_per_shard: Vec<NumSeats>,
97    /// Expected number of hidden validators per shard.
98    pub avg_hidden_validator_seats_per_shard: Vec<NumSeats>,
99    /// Threshold of pledge that needs to indicate that they ready for upgrade.
100    #[serde(default = "default_protocol_upgrade_pledge_threshold")]
101    #[default(Rational32::new(8, 10))]
102    pub protocol_upgrade_pledge_threshold: Rational32,
103    /// Epoch length counted in block heights.
104    pub epoch_length: BlockHeightDelta,
105    /// Initial gas limit.
106    pub gas_limit: Gas,
107    /// Minimum gas price. It is also the initial gas price.
108    #[serde(with = "dec_format")]
109    pub min_gas_price: Balance,
110    #[serde(with = "dec_format")]
111    #[default(MAX_GAS_PRICE)]
112    pub max_gas_price: Balance,
113    /// Criterion for kicking out block producers (this is a number between 0 and 100)
114    pub block_producer_kickout_threshold: u8,
115    /// Criterion for kicking out chunk producers (this is a number between 0 and 100)
116    pub chunk_producer_kickout_threshold: u8,
117    /// Online minimum threshold below which validator doesn't receive reward.
118    #[serde(default = "default_online_min_threshold")]
119    #[default(Rational32::new(90, 100))]
120    pub online_min_threshold: Rational32,
121    /// Online maximum threshold above which validator gets full reward.
122    #[serde(default = "default_online_max_threshold")]
123    #[default(Rational32::new(99, 100))]
124    pub online_max_threshold: Rational32,
125    /// Gas price adjustment rate
126    #[default(Rational32::from_integer(0))]
127    pub gas_price_adjustment_rate: Rational32,
128    /// List of initial validators.
129    pub validators: Vec<AccountInfo>,
130    /// Number of blocks for which a given transaction is valid
131    pub transaction_validity_period: NumBlocks,
132    /// Protocol treasury rate
133    #[default(Rational32::from_integer(0))]
134    pub protocol_reward_rate: Rational32,
135    /// Maximum inflation on the total supply every epoch.
136    #[default(Rational32::from_integer(0))]
137    pub max_inflation_rate: Rational32,
138    /// Total supply of tokens at genesis.
139    #[serde(with = "dec_format")]
140    pub total_supply: Balance,
141    /// Expected number of blocks per year
142    #[default(31536000)]
143    pub num_blocks_per_year: NumBlocks,
144    /// Protocol treasury account
145    #[default("unc".parse().unwrap())]
146    pub protocol_treasury_account: AccountId,
147    /// Fishermen pledge threshold.
148    #[serde(with = "dec_format")]
149    pub fishermen_threshold: Balance,
150    /// The minimum pledge required for staking is last seat price divided by this number.
151    #[serde(default = "default_minimum_pledge_divisor")]
152    #[default(10)]
153    pub minimum_pledge_divisor: u64,
154    /// Layout information regarding how to split accounts to shards
155    #[serde(default = "default_shard_layout")]
156    #[default(ShardLayout::v0_single_shard())]
157    pub shard_layout: ShardLayout,
158    #[serde(default = "default_num_chunk_only_producer_seats")]
159    #[default(300)]
160    pub num_chunk_only_producer_seats: NumSeats,
161    /// The minimum number of validators each shard must have
162    #[serde(default = "default_minimum_validators_per_shard")]
163    #[default(1)]
164    pub minimum_validators_per_shard: NumSeats,
165    #[serde(default = "default_max_kickout_pledge_threshold")]
166    #[default(100)]
167    /// Max pledge percentage of the validators we will kick out.
168    pub max_kickout_pledge_perc: u8,
169    /// The lowest ratio s/s_total any block producer can have.
170    #[serde(default = "default_minimum_pledge_ratio")]
171    #[default(Rational32::new(160, 1_000_000))]
172    pub minimum_pledge_ratio: Rational32,
173    #[serde(default = "default_use_production_config")]
174    #[default(false)]
175    /// This is only for test purposes. We hard code some configs for mainnet and testnet
176    /// in AllEpochConfig, and we want to have a way to test that code path. This flag is for that.
177    /// If set to true, the node will use the same config override path as mainnet and testnet.
178    pub use_production_config: bool,
179}
180
181impl GenesisConfig {
182    pub fn use_production_config(&self) -> bool {
183        self.use_production_config
184            || self.chain_id == unc_primitives::chains::TESTNET
185            || self.chain_id == unc_primitives::chains::MAINNET
186    }
187}
188
189impl From<&GenesisConfig> for EpochConfig {
190    fn from(config: &GenesisConfig) -> Self {
191        EpochConfig {
192            epoch_length: config.epoch_length,
193            num_block_producer_seats: config.num_block_producer_seats,
194            num_block_producer_seats_per_shard: config.num_block_producer_seats_per_shard.clone(),
195            avg_hidden_validator_seats_per_shard: config
196                .avg_hidden_validator_seats_per_shard
197                .clone(),
198            block_producer_kickout_threshold: config.block_producer_kickout_threshold,
199            chunk_producer_kickout_threshold: config.chunk_producer_kickout_threshold,
200            fishermen_threshold: config.fishermen_threshold,
201            online_min_threshold: config.online_min_threshold,
202            online_max_threshold: config.online_max_threshold,
203            protocol_upgrade_pledge_threshold: config.protocol_upgrade_pledge_threshold,
204            minimum_pledge_divisor: config.minimum_pledge_divisor,
205            shard_layout: ShardLayout::v0_single_shard(),
206            validator_selection_config: unc_primitives::epoch_manager::ValidatorSelectionConfig {
207                num_chunk_only_producer_seats: config.num_chunk_only_producer_seats,
208                minimum_validators_per_shard: config.minimum_validators_per_shard,
209                minimum_pledge_ratio: config.minimum_pledge_ratio,
210            },
211            validator_max_kickout_pledge_perc: config.max_kickout_pledge_perc,
212        }
213    }
214}
215
216/// Records in storage at genesis (get split into shards at genesis creation).
217#[derive(
218    Debug,
219    Clone,
220    SmartDefault,
221    derive_more::AsRef,
222    derive_more::AsMut,
223    derive_more::From,
224    serde::Serialize,
225    serde::Deserialize,
226)]
227pub struct GenesisRecords(pub Vec<StateRecord>);
228
229/// custom deserializer that does *almost the same thing that #[serde(default)] would do --
230/// if no value is provided in json, returns default value.
231/// * Here if `null` is provided as value in JSON, default value will be returned,
232/// while in serde default implementation that scenario wouldn't parse.
233fn no_value_and_null_as_default<'de, D, T>(de: D) -> Result<T, D::Error>
234where
235    D: Deserializer<'de>,
236    T: Deserialize<'de> + Default,
237{
238    let opt = Option::<T>::deserialize(de)?;
239    match opt {
240        None => Ok(T::default()),
241        Some(value) => Ok(value),
242    }
243}
244
245#[derive(Debug, Clone, SmartDefault, serde::Serialize, serde::Deserialize)]
246#[serde(untagged)]
247pub enum GenesisContents {
248    /// Records in storage at genesis (get split into shards at genesis creation).
249    #[default]
250    Records { records: GenesisRecords },
251    /// Genesis object may not contain records.
252    /// In this case records can be found in records_file.
253    /// The idea is that all records consume too much memory,
254    /// so they should be processed in streaming fashion with for_each_record.
255    RecordsFile { records_file: PathBuf },
256    /// Use records already in storage, represented by these state roots.
257    /// Used only for mock network forking for testing purposes.
258    /// WARNING: THIS IS USED FOR TESTING ONLY. IT IS **NOT CORRECT**, because
259    /// it is impossible to compute the corresponding genesis hash in this form,
260    /// such that the genesis hash is consistent with that of an equivalent
261    /// genesis that spells out the records.
262    StateRoots { state_roots: Vec<StateRoot> },
263}
264
265fn contents_are_from_record_file(contents: &GenesisContents) -> bool {
266    match contents {
267        GenesisContents::RecordsFile { .. } => true,
268        _ => false,
269    }
270}
271
272/// `Genesis` has an invariant that `total_supply` is equal to the supply seen in the records.
273/// However, we can't enforce that invariant. All fields are public, but the clients are expected to
274/// use the provided methods for instantiation, serialization and deserialization.
275/// Refer to `test_loading_localnet_genesis` to see an example of serialized Genesis JSON.
276#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
277pub struct Genesis {
278    #[serde(flatten)]
279    pub config: GenesisConfig,
280    /// Custom deserializer used instead of serde(default),
281    /// because serde(flatten) doesn't work with default for some reason
282    /// The corresponding issue has been open since 2019, so any day now.
283    #[serde(
284        flatten,
285        deserialize_with = "no_value_and_null_as_default",
286        skip_serializing_if = "contents_are_from_record_file"
287    )]
288    pub contents: GenesisContents,
289}
290
291impl GenesisConfig {
292    /// Parses GenesisConfig from a JSON string.
293    /// The string can be a JSON with comments.
294    /// It panics if the contents cannot be parsed from JSON to the GenesisConfig structure.
295    pub fn from_json(value: &str) -> Self {
296        let json_str_without_comments: String =
297            unc_config_utils::strip_comments_from_json_str(&value.to_string())
298                .expect("Failed to strip comments from genesis config.");
299        serde_json::from_str(&json_str_without_comments)
300            .expect("Failed to deserialize the genesis config.")
301    }
302
303    /// Reads GenesisConfig from a JSON file.
304    /// The file can be a JSON with comments.
305    /// It panics if file cannot be open or read, or the contents cannot be parsed from JSON to the
306    /// GenesisConfig structure.
307    pub fn from_file<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
308        let mut file = File::open(path).with_context(|| "Could not open genesis config file.")?;
309        let mut json_str = String::new();
310        file.read_to_string(&mut json_str)?;
311        let json_str_without_comments: String =
312            unc_config_utils::strip_comments_from_json_str(&json_str)?;
313        let genesis_config: GenesisConfig = serde_json::from_str(&json_str_without_comments)
314            .with_context(|| "Failed to deserialize the genesis config.")?;
315        Ok(genesis_config)
316    }
317
318    /// Writes GenesisConfig to the file.
319    pub fn to_file<P: AsRef<Path>>(&self, path: P) {
320        std::fs::write(
321            path,
322            serde_json::to_vec_pretty(self).expect("Error serializing the genesis config."),
323        )
324        .expect("Failed to create / write a genesis config file.");
325    }
326
327    /// Get validators from genesis config
328    pub fn validators(&self) -> Vec<ValidatorPowerAndPledge> {
329        self.validators
330            .iter()
331            .map(|account_info| {
332                ValidatorPowerAndPledge::new(
333                    account_info.account_id.clone(),
334                    account_info.public_key.clone(),
335                    account_info.power,
336                    account_info.pledging,
337                )
338            })
339            .collect()
340    }
341}
342
343impl GenesisRecords {
344    /// Parses GenesisRecords from a JSON string.
345    ///
346    /// It panics if the contents cannot be parsed from JSON to the GenesisConfig structure.
347    pub fn from_json(value: &str) -> Self {
348        serde_json::from_str(value).expect("Failed to deserialize the genesis records.")
349    }
350
351    /// Reads GenesisRecords from a JSON file.
352    /// The file can be a JSON with comments.
353    /// It panics if file cannot be open or read, or the contents cannot be parsed from JSON to the
354    /// GenesisConfig structure.
355    pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
356        let mut file = File::open(path).expect("Failed to open genesis config file.");
357        let mut json_str = String::new();
358        file.read_to_string(&mut json_str)
359            .expect("Failed to read the genesis config file to string. ");
360        let json_str_without_comments: String =
361            unc_config_utils::strip_comments_from_json_str(&json_str)
362                .expect("Failed to strip comments from Genesis config file.");
363        serde_json::from_str(&json_str_without_comments)
364            .expect("Failed to deserialize the genesis records.")
365    }
366
367    /// Writes GenesisRecords to the file.
368    pub fn to_file<P: AsRef<Path>>(&self, path: P) {
369        std::fs::write(
370            path,
371            serde_json::to_vec_pretty(self).expect("Error serializing the genesis records."),
372        )
373        .expect("Failed to create / write a genesis records file.");
374    }
375}
376
377/// Visitor for records.
378/// Reads records one by one and passes them to sink.
379/// If full genesis file is passed, reads records from "records" field and
380/// IGNORES OTHER FIELDS.
381struct RecordsProcessor<F> {
382    sink: F,
383}
384
385impl<'de, F: FnMut(StateRecord)> Visitor<'de> for RecordsProcessor<&'_ mut F> {
386    type Value = ();
387
388    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
389        formatter.write_str(
390            "either:\
391        1. array of StateRecord\
392        2. map with records field which is array of StateRecord",
393        )
394    }
395
396    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
397    where
398        A: SeqAccess<'de>,
399    {
400        while let Some(record) = seq.next_element::<StateRecord>()? {
401            (self.sink)(record)
402        }
403        Ok(())
404    }
405
406    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
407    where
408        A: MapAccess<'de>,
409    {
410        let mut me = Some(self);
411        let mut has_records_field = false;
412        while let Some(key) = map.next_key::<String>()? {
413            match key.as_str() {
414                "records" => {
415                    let me =
416                        me.take().ok_or_else(|| de::Error::custom("duplicate field: records"))?;
417                    map.next_value_seed(me)?;
418                    has_records_field = true;
419                }
420                _ => {
421                    map.next_value::<IgnoredAny>()?;
422                }
423            }
424        }
425        if has_records_field {
426            Ok(())
427        } else {
428            Err(de::Error::custom("missing field: records"))
429        }
430    }
431}
432
433impl<'de, F: FnMut(StateRecord)> DeserializeSeed<'de> for RecordsProcessor<&'_ mut F> {
434    type Value = ();
435
436    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
437    where
438        D: Deserializer<'de>,
439    {
440        deserializer.deserialize_seq(self)
441    }
442}
443
444/// The file can be a JSON with comments
445pub fn stream_records_from_file(
446    reader: impl Read,
447    mut callback: impl FnMut(StateRecord),
448) -> serde_json::Result<()> {
449    let reader_without_comments = unc_config_utils::strip_comments_from_json_reader(reader);
450    let mut deserializer = serde_json::Deserializer::from_reader(reader_without_comments);
451    let records_processor = RecordsProcessor { sink: &mut callback };
452    deserializer.deserialize_any(records_processor)
453}
454
455pub struct GenesisJsonHasher {
456    digest: sha2::Sha256,
457}
458
459impl GenesisJsonHasher {
460    pub fn new() -> Self {
461        Self { digest: sha2::Sha256::new() }
462    }
463
464    pub fn process_config(&mut self, config: &GenesisConfig) {
465        let mut ser = Serializer::pretty(&mut self.digest);
466        config.serialize(&mut ser).expect("Error serializing the genesis config.");
467    }
468
469    pub fn process_record(&mut self, record: &StateRecord) {
470        let mut ser = Serializer::pretty(&mut self.digest);
471        record.serialize(&mut ser).expect("Error serializing the genesis record.");
472    }
473
474    pub fn process_state_roots(&mut self, state_roots: &[StateRoot]) {
475        // WARNING: THIS IS INCORRECT, because it is impossible to compute the
476        // genesis hash from the state root in a way that is consistent to that
477        // of a genesis that spells out all the records.
478        // THEREFORE, THIS IS ONLY USED FOR TESTING, and this logic is only
479        // present at all because a genesis hash always has to at least exist.
480        let mut ser = Serializer::pretty(&mut self.digest);
481        state_roots.serialize(&mut ser).expect("Error serializing the state roots.");
482    }
483
484    pub fn process_genesis(&mut self, genesis: &Genesis) {
485        self.process_config(&genesis.config);
486        match &genesis.contents {
487            GenesisContents::StateRoots { state_roots } => {
488                self.process_state_roots(state_roots);
489            }
490            _ => {
491                genesis.for_each_record(|record: &StateRecord| {
492                    self.process_record(record);
493                });
494            }
495        }
496    }
497
498    pub fn finalize(self) -> CryptoHash {
499        CryptoHash(self.digest.finalize().into())
500    }
501}
502
503pub enum GenesisValidationMode {
504    Full,
505    UnsafeFast,
506}
507
508impl Genesis {
509    pub fn new(config: GenesisConfig, records: GenesisRecords) -> Result<Self, ValidationError> {
510        Self::new_validated(config, records, GenesisValidationMode::Full)
511    }
512
513    pub fn new_with_path<P: AsRef<Path>>(
514        config: GenesisConfig,
515        records_file: P,
516    ) -> Result<Self, ValidationError> {
517        Self::new_with_path_validated(config, records_file, GenesisValidationMode::Full)
518    }
519
520    /// Reads Genesis from a single JSON file, the file can be JSON with comments
521    /// This function will collect all errors regarding genesis.json and push them to validation_errors
522    pub fn from_file<P: AsRef<Path>>(
523        path: P,
524        genesis_validation: GenesisValidationMode,
525    ) -> Result<Self, ValidationError> {
526        let mut file = File::open(&path).map_err(|e| ValidationError::GenesisFileError {
527            error_message: format!(
528                "Could not open genesis config file at path {}: {:?}",
529                &path.as_ref().display(),
530                e,
531            ),
532        })?;
533
534        let mut json_str = String::new();
535        file.read_to_string(&mut json_str).map_err(|e| ValidationError::GenesisFileError {
536            error_message: format!("Failed to read genesis config file to string: {:?}", e),
537        })?;
538
539        let json_str_without_comments = unc_config_utils::strip_comments_from_json_str(&json_str)
540            .map_err(|e| ValidationError::GenesisFileError {
541            error_message: format!("Failed to strip comments from genesis config file: {:?}", e),
542        })?;
543
544        let genesis = serde_json::from_str::<Genesis>(&json_str_without_comments).map_err(|e| {
545            ValidationError::GenesisFileError {
546                error_message: format!("Failed to deserialize the genesis records: {:?}", e),
547            }
548        })?;
549
550        genesis.validate(genesis_validation)?;
551        Ok(genesis)
552    }
553
554    /// Reads Genesis from config and records files.
555    pub fn from_files<P1, P2>(
556        config_path: P1,
557        records_path: P2,
558        genesis_validation: GenesisValidationMode,
559    ) -> Result<Self, ValidationError>
560    where
561        P1: AsRef<Path>,
562        P2: AsRef<Path>,
563    {
564        let genesis_config = GenesisConfig::from_file(config_path).map_err(|error| {
565            let error_message = error.to_string();
566            ValidationError::GenesisFileError { error_message: error_message }
567        })?;
568        Self::new_with_path_validated(genesis_config, records_path, genesis_validation)
569    }
570
571    pub fn new_from_state_roots(config: GenesisConfig, state_roots: Vec<StateRoot>) -> Self {
572        Self { config, contents: GenesisContents::StateRoots { state_roots } }
573    }
574
575    fn new_validated(
576        config: GenesisConfig,
577        records: GenesisRecords,
578        genesis_validation: GenesisValidationMode,
579    ) -> Result<Self, ValidationError> {
580        let genesis = Self { config, contents: GenesisContents::Records { records } };
581        genesis.validate(genesis_validation)?;
582        Ok(genesis)
583    }
584
585    fn new_with_path_validated<P: AsRef<Path>>(
586        config: GenesisConfig,
587        records_file: P,
588        genesis_validation: GenesisValidationMode,
589    ) -> Result<Self, ValidationError> {
590        let genesis = Self {
591            config,
592            contents: GenesisContents::RecordsFile {
593                records_file: records_file.as_ref().to_path_buf(),
594            },
595        };
596        genesis.validate(genesis_validation)?;
597        Ok(genesis)
598    }
599
600    pub fn validate(
601        &self,
602        genesis_validation: GenesisValidationMode,
603    ) -> Result<(), ValidationError> {
604        match genesis_validation {
605            GenesisValidationMode::Full => validate_genesis(self),
606            GenesisValidationMode::UnsafeFast => {
607                warn!(target: "genesis", "Skipped genesis validation");
608                Ok(())
609            }
610        }
611    }
612
613    /// Writes Genesis to the file.
614    pub fn to_file<P: AsRef<Path>>(&self, path: P) {
615        std::fs::write(
616            path,
617            serde_json::to_vec_pretty(self).expect("Error serializing the genesis config."),
618        )
619        .expect("Failed to create / write a genesis config file.");
620    }
621
622    /// Hash of the json-serialized input.
623    /// DEVNOTE: the representation is not unique, and could change on upgrade.
624    pub fn json_hash(&self) -> CryptoHash {
625        let mut hasher = GenesisJsonHasher::new();
626        hasher.process_genesis(self);
627        hasher.finalize()
628    }
629
630    /// If records vector is empty processes records stream from records_file.
631    /// May panic if records_file is removed or is in wrong format.
632    pub fn for_each_record(&self, mut callback: impl FnMut(&StateRecord)) {
633        match &self.contents {
634            GenesisContents::Records { records } => {
635                for record in &records.0 {
636                    callback(record);
637                }
638            }
639            GenesisContents::RecordsFile { records_file } => {
640                let callback_move = |record: StateRecord| {
641                    callback(&record);
642                };
643                let reader = BufReader::new(
644                    File::open(&records_file).expect("error while opening records file"),
645                );
646                stream_records_from_file(reader, callback_move)
647                    .expect("error while streaming records");
648            }
649            GenesisContents::StateRoots { .. } => {
650                unreachable!("Cannot iterate through records when genesis uses state roots");
651            }
652        }
653    }
654
655    /// Forces loading genesis records into memory.
656    ///
657    /// This is meant for **tests only**.  In production code you should be
658    /// using [`Self::for_each_record`] instead to iterate over records.
659    ///
660    /// If the records are already loaded, simply returns mutable reference to
661    /// them.  Otherwise, reads them from `records_file`, stores them in memory
662    /// and then returns mutable reference to them.
663    pub fn force_read_records(&mut self) -> &mut GenesisRecords {
664        match &self.contents {
665            GenesisContents::RecordsFile { records_file } => {
666                self.contents =
667                    GenesisContents::Records { records: GenesisRecords::from_file(records_file) };
668            }
669            GenesisContents::Records { .. } => {}
670            GenesisContents::StateRoots { .. } => {
671                unreachable!("Cannot iterate through records when genesis uses state roots");
672            }
673        }
674        match &mut self.contents {
675            GenesisContents::Records { records } => records,
676            _ => {
677                unreachable!("Records should have been set previously");
678            }
679        }
680    }
681}
682
683/// Config for changes applied to state dump.
684#[derive(Debug, Default)]
685pub struct GenesisChangeConfig {
686    pub select_account_ids: Option<Vec<AccountId>>,
687    pub whitelist_validators: Option<HashSet<AccountId>>,
688}
689
690impl GenesisChangeConfig {
691    pub fn with_select_account_ids(mut self, select_account_ids: Option<Vec<AccountId>>) -> Self {
692        self.select_account_ids = select_account_ids;
693        self
694    }
695
696    pub fn with_whitelist_validators(
697        mut self,
698        whitelist_validators: Option<Vec<AccountId>>,
699    ) -> Self {
700        self.whitelist_validators = match whitelist_validators {
701            None => None,
702            Some(whitelist) => Some(whitelist.into_iter().collect::<HashSet<AccountId>>()),
703        };
704        self
705    }
706}
707
708// Note: this type cannot be placed in primitives/src/view.rs because of `RuntimeConfig` dependency issues.
709// Ideally we should create `RuntimeConfigView`, but given the deeply nested nature and the number of fields inside
710// `RuntimeConfig`, it should be its own endeavor.
711// TODO: This has changed, there is now `RuntimeConfigView`. Reconsider if moving this is possible now.
712// TODO: Consider replacing tens of fields with a combination of `GenesisConfig`
713// and `EpochConfig` fields, similar to how `RuntimeConfig` is represented as a
714// separate struct and not inlined.
715#[derive(serde::Serialize, serde::Deserialize, Debug)]
716pub struct ProtocolConfigView {
717    /// Current Protocol Version
718    pub protocol_version: ProtocolVersion,
719    /// Official time of blockchain start.
720    pub genesis_time: DateTime<Utc>,
721    /// ID of the blockchain. This must be unique for every blockchain.
722    /// If your testnet blockchains do not have unique chain IDs, you will have a bad time.
723    pub chain_id: String,
724    /// Height of genesis block.
725    pub genesis_height: BlockHeight,
726    /// Number of block producer seats at genesis.
727    pub num_block_producer_seats: NumSeats,
728    /// Defines number of shards and number of block producer seats per each shard at genesis.
729    pub num_block_producer_seats_per_shard: Vec<NumSeats>,
730    /// Expected number of hidden validators per shard.
731    pub avg_hidden_validator_seats_per_shard: Vec<NumSeats>,
732    /// Threshold of pledge that needs to indicate that they ready for upgrade.
733    pub protocol_upgrade_pledge_threshold: Rational32,
734    /// Epoch length counted in block heights.
735    pub epoch_length: BlockHeightDelta,
736    /// Initial gas limit.
737    pub gas_limit: Gas,
738    /// Minimum gas price. It is also the initial gas price.
739    #[serde(with = "dec_format")]
740    pub min_gas_price: Balance,
741    /// Maximum gas price.
742    #[serde(with = "dec_format")]
743    pub max_gas_price: Balance,
744    /// Criterion for kicking out block producers (this is a number between 0 and 100)
745    pub block_producer_kickout_threshold: u8,
746    /// Criterion for kicking out chunk producers (this is a number between 0 and 100)
747    pub chunk_producer_kickout_threshold: u8,
748    /// Online minimum threshold below which validator doesn't receive reward.
749    pub online_min_threshold: Rational32,
750    /// Online maximum threshold above which validator gets full reward.
751    pub online_max_threshold: Rational32,
752    /// Gas price adjustment rate
753    pub gas_price_adjustment_rate: Rational32,
754    /// Runtime configuration (mostly economics constants).
755    pub runtime_config: RuntimeConfigView,
756    /// Number of blocks for which a given transaction is valid
757    pub transaction_validity_period: NumBlocks,
758    /// Protocol treasury rate
759    pub protocol_reward_rate: Rational32,
760    /// Maximum inflation on the total supply every epoch.
761    pub max_inflation_rate: Rational32,
762    /// Expected number of blocks per year
763    pub num_blocks_per_year: NumBlocks,
764    /// Protocol treasury account
765    pub protocol_treasury_account: AccountId,
766    /// Fishermen pledge threshold.
767    #[serde(with = "dec_format")]
768    pub fishermen_threshold: Balance,
769    /// The minimum pledge required for staking is last seat price divided by this number.
770    pub minimum_pledge_divisor: u64,
771    /// Max pledge percentage of the validators we will kick out.
772    pub max_kickout_pledge_perc: u8,
773    /// The lowest ratio s/s_total any block producer can have.
774    pub minimum_pledge_ratio: Rational32,
775    /// The minimum number of validators each shard must have
776    pub minimum_validators_per_shard: NumSeats,
777    /// Number of validator seats for chunk only producers.
778    pub num_chunk_only_producer_seats: NumSeats,
779    /// Layout information regarding how to split accounts to shards
780    pub shard_layout: ShardLayout,
781}
782
783pub struct ProtocolConfig {
784    pub genesis_config: GenesisConfig,
785    pub runtime_config: RuntimeConfig,
786}
787
788impl From<ProtocolConfig> for ProtocolConfigView {
789    fn from(protocol_config: ProtocolConfig) -> Self {
790        let ProtocolConfig { genesis_config, runtime_config } = protocol_config;
791
792        ProtocolConfigView {
793            protocol_version: genesis_config.protocol_version,
794            genesis_time: genesis_config.genesis_time,
795            chain_id: genesis_config.chain_id,
796            genesis_height: genesis_config.genesis_height,
797            num_block_producer_seats: genesis_config.num_block_producer_seats,
798            num_block_producer_seats_per_shard: genesis_config.num_block_producer_seats_per_shard,
799            avg_hidden_validator_seats_per_shard: genesis_config
800                .avg_hidden_validator_seats_per_shard,
801            protocol_upgrade_pledge_threshold: genesis_config.protocol_upgrade_pledge_threshold,
802            epoch_length: genesis_config.epoch_length,
803            gas_limit: genesis_config.gas_limit,
804            min_gas_price: genesis_config.min_gas_price,
805            max_gas_price: genesis_config.max_gas_price,
806            block_producer_kickout_threshold: genesis_config.block_producer_kickout_threshold,
807            chunk_producer_kickout_threshold: genesis_config.chunk_producer_kickout_threshold,
808            online_min_threshold: genesis_config.online_min_threshold,
809            online_max_threshold: genesis_config.online_max_threshold,
810            gas_price_adjustment_rate: genesis_config.gas_price_adjustment_rate,
811            runtime_config: RuntimeConfigView::from(runtime_config),
812            transaction_validity_period: genesis_config.transaction_validity_period,
813            protocol_reward_rate: genesis_config.protocol_reward_rate,
814            max_inflation_rate: genesis_config.max_inflation_rate,
815            num_blocks_per_year: genesis_config.num_blocks_per_year,
816            protocol_treasury_account: genesis_config.protocol_treasury_account,
817            fishermen_threshold: genesis_config.fishermen_threshold,
818            minimum_pledge_divisor: genesis_config.minimum_pledge_divisor,
819            max_kickout_pledge_perc: genesis_config.max_kickout_pledge_perc,
820            minimum_pledge_ratio: genesis_config.minimum_pledge_ratio,
821            minimum_validators_per_shard: genesis_config.minimum_validators_per_shard,
822            num_chunk_only_producer_seats: genesis_config.num_chunk_only_producer_seats,
823            shard_layout: genesis_config.shard_layout,
824        }
825    }
826}
827
828pub fn get_initial_supply(records: &[StateRecord]) -> Balance {
829    let mut total_supply = 0;
830    for record in records {
831        if let StateRecord::Account { account, .. } = record {
832            total_supply += account.amount() + account.pledging();
833        }
834    }
835    total_supply
836}
837
838#[cfg(test)]
839mod test {
840    use crate::genesis_config::RecordsProcessor;
841    use crate::{Genesis, GenesisValidationMode};
842    use serde::Deserializer;
843    use unc_primitives::state_record::StateRecord;
844
845    fn stream_records_from_json_str(genesis: &str) -> serde_json::Result<()> {
846        let mut deserializer = serde_json::Deserializer::from_reader(genesis.as_bytes());
847        let records_processor = RecordsProcessor { sink: &mut |_record: StateRecord| {} };
848        deserializer.deserialize_any(records_processor)
849    }
850
851    #[test]
852    fn test_genesis_with_empty_records() {
853        let genesis = r#"{
854            "a": [1, 2],
855            "b": "random",
856            "records": []
857        }"#;
858        stream_records_from_json_str(genesis).expect("error reading empty records");
859    }
860
861    #[test]
862    #[should_panic(expected = "missing field: records")]
863    fn test_genesis_with_no_records() {
864        let genesis = r#"{
865            "a": [1, 2],
866            "b": "random"
867        }"#;
868        stream_records_from_json_str(genesis).unwrap();
869    }
870
871    #[test]
872    #[should_panic(expected = "duplicate field: records")]
873    fn test_genesis_with_several_records_fields() {
874        let genesis = r#"{
875            "a": [1, 2],
876            "records": [{
877                    "Account": {
878                        "account_id": "01.unc",
879                        "account": {
880                              "amount": "49999999958035075000000000",
881                              "pledging": "0",
882                              "code_hash": "11111111111111111111111111111111",
883                              "storage_usage": 264
884                        }
885                    }
886                }],
887            "b": "random",
888            "records": [{
889                    "Account": {
890                        "account_id": "01.unc",
891                        "account": {
892                              "amount": "49999999958035075000000000",
893                              "pledging": "0",
894                              "code_hash": "11111111111111111111111111111111",
895                              "storage_usage": 264
896                        }
897                    }
898                }]
899        }"#;
900        stream_records_from_json_str(genesis).unwrap();
901    }
902
903    #[test]
904    fn test_genesis_with_fields_after_records() {
905        let genesis = r#"{
906            "a": [1, 2],
907            "b": "random",
908            "records": [
909                {
910                    "Account": {
911                        "account_id": "01.unc",
912                        "account": {
913                              "amount": "49999999958035075000000000",
914                              "pledging": "0",
915                              "code_hash": "11111111111111111111111111111111",
916                              "storage_usage": 264
917                        }
918                    }
919                }
920            ],
921            "c": {
922                "d": 1,
923                "e": []
924            }
925        }"#;
926        stream_records_from_json_str(genesis).expect("error reading records with a field after");
927    }
928
929    #[test]
930    fn test_genesis_with_fields_before_records() {
931        let genesis = r#"{
932            "a": [1, 2],
933            "b": "random",
934            "c": {
935                "d": 1,
936                "e": []
937            },
938            "records": [
939                {
940                    "Account": {
941                        "account_id": "01.unc",
942                        "account": {
943                              "amount": "49999999958035075000000000",
944                              "pledging": "0",
945                              "code_hash": "11111111111111111111111111111111",
946                              "storage_usage": 264
947                        }
948                    }
949                }
950            ]
951        }"#;
952        stream_records_from_json_str(genesis).expect("error reading records from genesis");
953    }
954
955    #[test]
956    fn test_genesis_with_several_records() {
957        let genesis = r#"{
958            "a": [1, 2],
959            "b": "random",
960            "c": {
961                "d": 1,
962                "e": []
963            },
964            "records": [
965                {
966                    "Account": {
967                        "account_id": "01.unc",
968                        "account": {
969                              "amount": "49999999958035075000000000",
970                              "pledging": "0",
971                              "code_hash": "11111111111111111111111111111111",
972                              "storage_usage": 264
973                        }
974                    }
975                },
976                {
977                    "Account": {
978                        "account_id": "01.unc",
979                        "account": {
980                              "amount": "49999999958035075000000000",
981                              "pledging": "0",
982                              "code_hash": "11111111111111111111111111111111",
983                              "storage_usage": 264
984                        }
985                    }
986                }
987            ]
988        }"#;
989        stream_records_from_json_str(genesis).expect("error reading records from genesis");
990    }
991
992    #[test]
993    fn test_loading_localnet_genesis() {
994        let genesis_str = r#"{
995              "protocol_version": 57,
996              "genesis_time": "2022-11-15T05:17:59.578706Z",
997              "chain_id": "localnet",
998              "genesis_height": 0,
999              "num_block_producer_seats": 50,
1000              "num_block_producer_seats_per_shard": [
1001                50
1002              ],
1003              "avg_hidden_validator_seats_per_shard": [
1004                0
1005              ],
1006              "protocol_upgrade_pledge_threshold": [
1007                4,
1008                5
1009              ],
1010              "protocol_upgrade_num_epochs": 2,
1011              "epoch_length": 60,
1012              "gas_limit": 1000000000000000,
1013              "min_gas_price": "100000000",
1014              "max_gas_price": "10000000000000000000000",
1015              "block_producer_kickout_threshold": 90,
1016              "chunk_producer_kickout_threshold": 90,
1017              "online_min_threshold": [
1018                9,
1019                10
1020              ],
1021              "online_max_threshold": [
1022                99,
1023                100
1024              ],
1025              "gas_price_adjustment_rate": [
1026                1,
1027                100
1028              ],
1029              "validators": [
1030                {
1031                  "account_id": "test.unc",
1032                  "public_key": "ed25519:Gc4yTakj3QVm5T9XpsFNooVKBxXcYnhnuQdGMXf5Hjcf",
1033                  "amount": "50000000000000000000000000000000"
1034                }
1035              ],
1036              "transaction_validity_period": 100,
1037              "protocol_reward_rate": [
1038                1,
1039                10
1040              ],
1041              "max_inflation_rate": [
1042                1,
1043                20
1044              ],
1045              "total_supply": "2050000000000000000000000000000000",
1046              "num_blocks_per_year": 31536000,
1047              "protocol_treasury_account": "test.unc",
1048              "fishermen_threshold": "10000000000000000000000000",
1049              "minimum_pledge_divisor": 10,
1050              "num_chunk_only_producer_seats": 300,
1051              "minimum_validators_per_shard": 1,
1052              "max_kickout_pledge_perc": 100,
1053              "minimum_pledge_ratio": [
1054                1,
1055                6250
1056              ],
1057              "use_production_config": false,
1058              "records": [
1059                {
1060                  "Account": {
1061                    "account_id": "test.unc",
1062                    "account": {
1063                      "amount": "1000000000000000000000000000000000",
1064                      "pledging": "50000000000000000000000000000000",
1065                      "code_hash": "11111111111111111111111111111111",
1066                      "storage_usage": 0,
1067                      "version": "V1"
1068                    }
1069                  }
1070                },
1071                {
1072                  "AccessKey": {
1073                    "account_id": "test.unc",
1074                    "public_key": "ed25519:Gc4yTakj3QVm5T9XpsFNooVKBxXcYnhnuQdGMXf5Hjcf",
1075                    "access_key": {
1076                      "nonce": 0,
1077                      "permission": "FullAccess"
1078                    }
1079                  }
1080                },
1081                {
1082                  "Account": {
1083                    "account_id": "unc",
1084                    "account": {
1085                      "amount": "1000000000000000000000000000000000",
1086                      "pledging": "0",
1087                      "code_hash": "11111111111111111111111111111111",
1088                      "storage_usage": 0,
1089                      "version": "V1"
1090                    }
1091                  }
1092                },
1093                {
1094                  "AccessKey": {
1095                    "account_id": "unc",
1096                    "public_key": "ed25519:546XB2oHhj7PzUKHiH9Xve3Ze5q1JiW2WTh6abXFED3c",
1097                    "access_key": {
1098                      "nonce": 0,
1099                      "permission": "FullAccess"
1100                    }
1101                  }
1102                }
1103              ]
1104        }"#;
1105        let genesis =
1106            serde_json::from_str::<Genesis>(&genesis_str).expect("Failed to deserialize Genesis");
1107        genesis.validate(GenesisValidationMode::Full).expect("Failed to validate Genesis");
1108    }
1109
1110    #[test]
1111    fn test_loading_localnet_genesis_without_records() {
1112        let genesis_str = r#"{
1113              "protocol_version": 57,
1114              "genesis_time": "2022-11-15T05:17:59.578706Z",
1115              "chain_id": "localnet",
1116              "genesis_height": 0,
1117              "num_block_producer_seats": 50,
1118              "num_block_producer_seats_per_shard": [
1119                50
1120              ],
1121              "avg_hidden_validator_seats_per_shard": [
1122                0
1123              ],
1124              "protocol_upgrade_pledge_threshold": [
1125                4,
1126                5
1127              ],
1128              "protocol_upgrade_num_epochs": 2,
1129              "epoch_length": 60,
1130              "gas_limit": 1000000000000000,
1131              "min_gas_price": "100000000",
1132              "max_gas_price": "10000000000000000000000",
1133              "block_producer_kickout_threshold": 90,
1134              "chunk_producer_kickout_threshold": 90,
1135              "online_min_threshold": [
1136                9,
1137                10
1138              ],
1139              "online_max_threshold": [
1140                99,
1141                100
1142              ],
1143              "gas_price_adjustment_rate": [
1144                1,
1145                100
1146              ],
1147              "validators": [
1148                {
1149                  "account_id": "test.unc",
1150                  "public_key": "ed25519:Gc4yTakj3QVm5T9XpsFNooVKBxXcYnhnuQdGMXf5Hjcf",
1151                  "amount": "50000000000000000000000000000000"
1152                }
1153              ],
1154              "transaction_validity_period": 100,
1155              "protocol_reward_rate": [
1156                1,
1157                10
1158              ],
1159              "max_inflation_rate": [
1160                1,
1161                20
1162              ],
1163              "total_supply": "2050000000000000000000000000000000",
1164              "num_blocks_per_year": 31536000,
1165              "protocol_treasury_account": "test.unc",
1166              "fishermen_threshold": "10000000000000000000000000",
1167              "minimum_pledge_divisor": 10,
1168              "num_chunk_only_producer_seats": 300,
1169              "minimum_validators_per_shard": 1,
1170              "max_kickout_pledge_perc": 100,
1171              "minimum_pledge_ratio": [
1172                1,
1173                6250
1174              ],
1175              "use_production_config": false
1176        }"#;
1177        let _genesis =
1178            serde_json::from_str::<Genesis>(&genesis_str).expect("Failed to deserialize Genesis");
1179    }
1180}