Skip to main content

mina_node/transition_frontier/genesis/
transition_frontier_genesis_config.rs

1use std::{
2    borrow::Cow,
3    fs::File,
4    io::{Read, Write},
5    path::PathBuf,
6    str::FromStr,
7};
8
9use crate::{account::AccountSecretKey, daemon_json::EpochData};
10use ledger::{
11    proofs::caching::{ensure_path_exists, mina_cache_path},
12    scan_state::currency::Balance,
13    BaseLedger,
14};
15use mina_core::constants::{constraint_constants, DEFAULT_GENESIS_TIMESTAMP_MILLISECONDS};
16use mina_curves::pasta::Fp;
17use mina_p2p_messages::{
18    bigint::InvalidBigInt,
19    binprot::{
20        self,
21        macros::{BinProtRead, BinProtWrite},
22        BinProtRead, BinProtWrite,
23    },
24    v2::{self, PROTOCOL_CONSTANTS},
25};
26use serde::{Deserialize, Serialize};
27
28use crate::{
29    daemon_json::{self, AccountConfigError, DaemonJson},
30    ProtocolConstants,
31};
32
33pub use GenesisConfig as TransitionFrontierGenesisConfig;
34
35#[derive(Serialize, Deserialize, Debug, Clone)]
36pub enum GenesisConfig {
37    Counts {
38        whales: usize,
39        fish: usize,
40        non_stakers: NonStakers,
41        constants: ProtocolConstants,
42    },
43    BalancesDelegateTable {
44        table: Vec<(u64, Vec<u64>)>,
45        constants: ProtocolConstants,
46    },
47    AccountsBinProt {
48        bytes: Cow<'static, [u8]>,
49        constants: ProtocolConstants,
50    },
51    Prebuilt(Cow<'static, [u8]>),
52    DaemonJson(Box<DaemonJson>),
53    DaemonJsonFile(PathBuf),
54}
55
56#[derive(Serialize, Deserialize, Debug, Clone)]
57pub enum NonStakers {
58    /// Adds all the accounts from the generated array to the ledger
59    Fill,
60    /// No non-staker accounts will be added to the ledger
61    None,
62    /// Add a precise amount of accounts, non greater than the amount of generated accounts
63    Count(usize),
64}
65
66#[derive(Serialize, Deserialize, Debug, Clone)]
67pub struct GenesisConfigLoaded {
68    pub constants: ProtocolConstants,
69    pub genesis_ledger_hash: v2::LedgerHash,
70    pub genesis_total_currency: v2::CurrencyAmountStableV1,
71    pub genesis_producer_stake_proof: v2::MinaBaseSparseLedgerBaseStableV2,
72    pub staking_epoch_ledger_hash: v2::LedgerHash,
73    pub staking_epoch_total_currency: v2::CurrencyAmountStableV1,
74    pub staking_epoch_seed: v2::EpochSeed,
75    pub next_epoch_ledger_hash: v2::LedgerHash,
76    pub next_epoch_total_currency: v2::CurrencyAmountStableV1,
77    pub next_epoch_seed: v2::EpochSeed,
78}
79
80fn bp_num_delegators(i: usize) -> usize {
81    (i.saturating_add(1))
82        .checked_mul(2)
83        .expect("overflow")
84        .min(32)
85}
86
87#[derive(Debug, thiserror::Error)]
88pub enum GenesisConfigError {
89    #[error("no ledger in configuration")]
90    NoLedger,
91    #[error("declared and computed ledger hashes don't match: {expected} != {computed}")]
92    LedgerHashMismatch {
93        expected: v2::LedgerHash,
94        computed: v2::LedgerHash,
95    },
96    #[error("account error: {0}")]
97    Account(#[from] AccountConfigError),
98    #[error("error loading genesis config from precomputed data: {0}")]
99    Prebuilt(#[from] binprot::Error),
100    #[error("error deserializing daemon.json: {0}")]
101    Json(#[from] serde_json::Error),
102    #[error("I/O error: {0}")]
103    Io(#[from] std::io::Error),
104    #[error("Invalid bigint")]
105    InvalidBigInt(#[from] InvalidBigInt),
106}
107
108impl GenesisConfig {
109    pub fn default_constants(timestamp_ms: u64) -> ProtocolConstants {
110        ProtocolConstants {
111            k: 290.into(),
112            slots_per_epoch: 7140.into(),
113            slots_per_sub_window: 7.into(),
114            grace_period_slots: 2160.into(),
115            delta: 0.into(),
116            genesis_state_timestamp: v2::BlockTimeTimeStableV1(
117                v2::UnsignedExtendedUInt64Int64ForVersionTagsStableV1(timestamp_ms.into()),
118            ),
119        }
120    }
121
122    // This is a stub for the moment until PR #420 is merged, which implements this for
123    // real. In case of conflict, delete this stub and put the real implementation here.
124    pub fn protocol_constants(&self) -> Result<ProtocolConstants, time::error::Parse> {
125        match self {
126            Self::Counts { constants, .. }
127            | Self::BalancesDelegateTable { constants, .. }
128            | Self::AccountsBinProt { constants, .. } => Ok(constants.clone()),
129            Self::Prebuilt { .. } => Ok(self.load().unwrap().1.constants),
130            Self::DaemonJson(config) => Ok(config
131                .genesis
132                .as_ref()
133                .map(|g: &daemon_json::Genesis| g.protocol_constants())
134                .unwrap_or(Self::default_constants(
135                    DEFAULT_GENESIS_TIMESTAMP_MILLISECONDS,
136                ))),
137            Self::DaemonJsonFile(_) => todo!(),
138        }
139    }
140
141    pub fn override_genesis_state_timestamp(&mut self, timestamp: v2::BlockTimeTimeStableV1) {
142        match self {
143            Self::Counts { constants, .. }
144            | Self::BalancesDelegateTable { constants, .. }
145            | Self::AccountsBinProt { constants, .. } => {
146                constants.genesis_state_timestamp = timestamp;
147            }
148            _ => todo!(),
149        }
150    }
151
152    pub fn load(
153        &self,
154    ) -> anyhow::Result<(Vec<ledger::Mask>, GenesisConfigLoaded), GenesisConfigError> {
155        Ok(match self {
156            Self::Counts {
157                whales,
158                fish,
159                non_stakers,
160                constants,
161            } => {
162                let (whales, fish) = (*whales, *fish);
163                let delegator_balance =
164                    |balance: u64| move |i| balance.checked_div(i as u64).expect("division by 0");
165                let whales = (0..whales).map(|i| {
166                    let balance = 8333_u64;
167                    let delegators = (1..=bp_num_delegators(i)).map(delegator_balance(50_000_000));
168                    (balance, delegators)
169                });
170                let fish = (0..fish).map(|i| {
171                    let balance = 6333_u64;
172                    let delegators = (1..=bp_num_delegators(i)).map(delegator_balance(5_000_000));
173                    (balance, delegators)
174                });
175                let delegator_table = whales.chain(fish);
176                let (mut mask, genesis_total_currency) =
177                    Self::build_ledger_from_balances_delegator_table(delegator_table, non_stakers)?;
178                let genesis_ledger_hash = ledger_hash(&mut mask);
179                let staking_epoch_total_currency = genesis_total_currency.clone();
180                let next_epoch_total_currency = genesis_total_currency.clone();
181                let staking_epoch_seed = v2::EpochSeed::zero();
182                let next_epoch_seed = v2::EpochSeed::zero();
183
184                let load_result = GenesisConfigLoaded {
185                    constants: constants.clone(),
186                    genesis_ledger_hash: genesis_ledger_hash.clone(),
187                    genesis_total_currency,
188                    genesis_producer_stake_proof: create_genesis_producer_stake_proof(&mask),
189                    staking_epoch_ledger_hash: genesis_ledger_hash.clone(),
190                    staking_epoch_total_currency,
191                    next_epoch_ledger_hash: genesis_ledger_hash,
192                    next_epoch_total_currency,
193                    staking_epoch_seed,
194                    next_epoch_seed,
195                };
196                let masks = vec![mask];
197                (masks, load_result)
198            }
199            Self::BalancesDelegateTable { table, constants } => {
200                let table = table.iter().map(|(bp_balance, delegators)| {
201                    let delegators = delegators.iter().copied();
202                    (*bp_balance, delegators)
203                });
204                let (mut mask, genesis_total_currency) =
205                    Self::build_ledger_from_balances_delegator_table(table, &NonStakers::None)?;
206                let genesis_ledger_hash = ledger_hash(&mut mask);
207                let staking_epoch_total_currency = genesis_total_currency.clone();
208                let next_epoch_total_currency = genesis_total_currency.clone();
209                let staking_epoch_seed = v2::EpochSeed::zero();
210                let next_epoch_seed = v2::EpochSeed::zero();
211
212                let load_result = GenesisConfigLoaded {
213                    constants: constants.clone(),
214                    genesis_ledger_hash: genesis_ledger_hash.clone(),
215                    genesis_total_currency,
216                    genesis_producer_stake_proof: create_genesis_producer_stake_proof(&mask),
217                    staking_epoch_ledger_hash: genesis_ledger_hash.clone(),
218                    staking_epoch_total_currency,
219                    next_epoch_ledger_hash: genesis_ledger_hash,
220                    next_epoch_total_currency,
221                    staking_epoch_seed,
222                    next_epoch_seed,
223                };
224                let masks = vec![mask];
225                (masks, load_result)
226            }
227            Self::Prebuilt(bytes) => {
228                let prebuilt = PrebuiltGenesisConfig::read(&mut bytes.as_ref())?;
229                prebuilt.load()?
230            }
231            Self::AccountsBinProt { bytes, .. } => {
232                let mut bytes = bytes.as_ref();
233                let _expected_hash = Option::<v2::LedgerHash>::binprot_read(&mut bytes)?;
234                let hashes = Vec::<(u64, v2::LedgerHash)>::binprot_read(&mut bytes)?
235                    .into_iter()
236                    .map(|(idx, hash)| Ok((idx, hash.0.to_field()?)))
237                    .collect::<Result<Vec<_>, InvalidBigInt>>()?;
238                let accounts = Vec::<ledger::Account>::binprot_read(&mut bytes)?;
239
240                let (mut mask, _total_currency) = Self::build_ledger_from_accounts_and_hashes(
241                    accounts.into_iter().map(Ok),
242                    hashes,
243                )?;
244                let _ledger_hash = ledger_hash(&mut mask);
245
246                todo!()
247
248                // // TODO(tizoc): currently this doesn't really do much, because now we load the hashes
249                // // from the bin_prot data too to speed up the loading. Maybe add some flag
250                // // to force the rehashing and validation of the loaded ledger hashes.
251                // if let Some(expected_hash) = expected_hash.filter(|h| h != &ledger_hash) {
252                //     anyhow::bail!("ledger hash mismatch after building the mask! expected: '{expected_hash}', got '{ledger_hash}'");
253                // }
254                //
255                // let load_result = GenesisConfigLoaded {
256                //     constants: constants.clone(),
257                //     ledger_hash,
258                //     total_currency,
259                //     genesis_producer_stake_proof: genesis_producer_stake_proof(&mask),
260                // };
261                // (mask, load_result)
262            }
263            Self::DaemonJson(config) => {
264                // Apply fork override from daemon.json proof section.
265                // This must happen before genesis block computation so
266                // that OCaml and Rust nodes use matching fork values.
267                if let Some(fork) = config.proof.as_ref().and_then(|p| p.fork.clone()) {
268                    mina_core::constants::set_fork_override(fork);
269                }
270
271                let mut masks = Vec::new();
272                let constants = config
273                    .genesis
274                    .as_ref()
275                    .map_or(PROTOCOL_CONSTANTS, |genesis| genesis.protocol_constants());
276                let ledger = config.ledger.as_ref().ok_or(GenesisConfigError::NoLedger)?;
277                let accounts = ledger
278                    .accounts_with_genesis_winner()
279                    .iter()
280                    .map(daemon_json::Account::to_account)
281                    .collect::<Result<Vec<_>, _>>()?;
282                let (mask, total_currency, genesis_ledger_hash) =
283                    Self::build_or_load_ledger(ledger.ledger_name(), accounts.into_iter())?;
284
285                masks.push(mask.clone());
286                if let Some(expected_hash) = config.ledger.as_ref().and_then(|l| l.hash.as_ref()) {
287                    if expected_hash != &genesis_ledger_hash.to_string() {
288                        return Err(GenesisConfigError::LedgerHashMismatch {
289                            expected: expected_hash.parse().unwrap(),
290                            computed: genesis_ledger_hash.clone(),
291                        });
292                    }
293                }
294
295                let staking_epoch_ledger_hash;
296                let staking_epoch_total_currency;
297                let staking_epoch_seed: v2::EpochSeed;
298                let next_epoch_ledger_hash;
299                let next_epoch_total_currency;
300                let next_epoch_seed: v2::EpochSeed;
301                let genesis_producer_stake_proof: v2::MinaBaseSparseLedgerBaseStableV2;
302                // TODO(devnet): handle other cases here, right now this works
303                // only for the post-fork genesis
304                if let Some(data) = &config.epoch_data {
305                    let accounts = data
306                        .staking
307                        .accounts
308                        .as_ref()
309                        .unwrap()
310                        .iter()
311                        .map(daemon_json::Account::to_account)
312                        .collect::<Result<Vec<_>, _>>()?;
313                    let (staking_ledger_mask, total_currency, hash) = Self::build_or_load_ledger(
314                        data.staking.ledger_name(),
315                        accounts.into_iter(),
316                    )?;
317                    staking_epoch_ledger_hash = hash;
318                    staking_epoch_total_currency = total_currency;
319                    staking_epoch_seed = v2::EpochSeed::from_str(&data.staking.seed).unwrap();
320                    genesis_producer_stake_proof =
321                        create_genesis_producer_stake_proof(&staking_ledger_mask);
322                    masks.push(staking_ledger_mask);
323
324                    let next = data.next.as_ref().unwrap();
325                    let accounts = next
326                        .accounts
327                        .as_ref()
328                        .unwrap()
329                        .iter()
330                        .map(daemon_json::Account::to_account)
331                        .collect::<Result<Vec<_>, _>>()?;
332                    let (mut _mask, total_currency, hash) =
333                        Self::build_or_load_ledger(next.ledger_name(), accounts.into_iter())?;
334                    next_epoch_ledger_hash = hash;
335                    next_epoch_total_currency = total_currency;
336                    next_epoch_seed =
337                        v2::EpochSeed::from_str(&data.next.as_ref().unwrap().seed).unwrap();
338                } else {
339                    staking_epoch_ledger_hash = genesis_ledger_hash.clone();
340                    staking_epoch_total_currency = total_currency.clone();
341                    staking_epoch_seed = v2::EpochSeed::zero();
342                    next_epoch_ledger_hash = genesis_ledger_hash.clone();
343                    next_epoch_total_currency = total_currency.clone();
344                    next_epoch_seed = v2::EpochSeed::zero();
345                    genesis_producer_stake_proof = create_genesis_producer_stake_proof(&mask);
346                }
347
348                let result = GenesisConfigLoaded {
349                    constants,
350                    genesis_ledger_hash,
351                    genesis_total_currency: total_currency,
352                    genesis_producer_stake_proof,
353                    staking_epoch_ledger_hash,
354                    staking_epoch_total_currency,
355                    next_epoch_ledger_hash,
356                    next_epoch_total_currency,
357                    staking_epoch_seed,
358                    next_epoch_seed,
359                };
360                (masks, result)
361            }
362            Self::DaemonJsonFile(path) => {
363                let reader = File::open(path)?;
364                let c = serde_json::from_reader(reader)?;
365                Self::DaemonJson(c).load()?
366            }
367        })
368    }
369
370    fn build_or_load_ledger(
371        ledger_name: String,
372        accounts: impl Iterator<Item = ledger::Account>,
373    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1, LedgerHash), GenesisConfigError> {
374        mina_core::info!(
375            mina_core::log::system_time();
376            kind = "ledger loading",
377            message = "loading the ledger",
378            ledger_name = ledger_name,
379        );
380        match LedgerAccountsWithHash::load(ledger_name)? {
381            Some(accounts_with_hash) => {
382                let (mask, total_currency) = Self::build_ledger_from_accounts_and_hashes(
383                    accounts_with_hash
384                        .accounts
385                        .iter()
386                        .map(ledger::Account::try_from),
387                    accounts_with_hash
388                        .hashes
389                        .into_iter()
390                        .map(|(n, h)| Ok((n, h.to_field()?)))
391                        .collect::<Result<Vec<_>, InvalidBigInt>>()?,
392                )?;
393                mina_core::info!(
394                    mina_core::log::system_time();
395                    kind = "ledger loaded",
396                    message = "loaded from cache",
397                    ledger_hash = accounts_with_hash.ledger_hash.to_string(),
398                );
399                Ok((mask, total_currency, accounts_with_hash.ledger_hash))
400            }
401            None => {
402                let (mut mask, total_currency) =
403                    Self::build_ledger_from_accounts(accounts.into_iter().map(Ok))?;
404                let hash = ledger_hash(&mut mask);
405                let ledger_accounts = LedgerAccountsWithHash {
406                    accounts: mask.fold(Vec::new(), |mut acc, a| {
407                        acc.push(a.into());
408                        acc
409                    }),
410                    ledger_hash: hash.clone(),
411                    hashes: mask
412                        .get_raw_inner_hashes()
413                        .into_iter()
414                        .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
415                        .collect(),
416                };
417                ledger_accounts.cache()?;
418                mina_core::info!(
419                    mina_core::log::system_time();
420                    kind = "ledger loaded",
421                    message = "built from config and cached",
422                    ledger_hash = hash.to_string(),
423                );
424                Ok((mask, total_currency, hash))
425            }
426        }
427    }
428
429    fn build_ledger_from_balances_delegator_table(
430        block_producers: impl IntoIterator<Item = (u64, impl IntoIterator<Item = u64>)>,
431        non_stakers: &NonStakers,
432    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1), InvalidBigInt> {
433        let mut counter = 0;
434        let mut total_balance = 0;
435
436        fn create_account(
437            counter: &mut u64,
438            balance: u64,
439            delegate: Option<mina_signer::CompressedPubKey>,
440            total_balance: &mut u64,
441        ) -> ledger::Account {
442            let sec_key = AccountSecretKey::deterministic(*counter);
443            let pub_key: mina_signer::CompressedPubKey = sec_key.public_key().try_into().unwrap(); // unwrap: `sec_key` was generated
444            let account_id = ledger::AccountId::new(pub_key.clone(), Default::default());
445            let mut account =
446                ledger::Account::create_with(account_id, Balance::from_mina(balance).unwrap());
447            account.delegate = delegate;
448            *total_balance = total_balance.checked_add(balance).expect("overflow");
449            *counter = counter.checked_add(1).expect("overflow");
450            // println!("Created account with balance: {}, total_balance: {}", balance, *total_balance); // Debug print
451            account
452        }
453
454        let mut accounts = genesis_account_iter().map(Ok).collect::<Vec<_>>();
455
456        // Process block producers and their delegators
457        for (bp_balance, delegators) in block_producers {
458            let bp_account = create_account(&mut counter, bp_balance, None, &mut total_balance);
459            let bp_pub_key = bp_account.public_key.clone();
460            accounts.push(Ok(bp_account));
461
462            for balance in delegators {
463                let delegator_account = create_account(
464                    &mut counter,
465                    balance,
466                    Some(bp_pub_key.clone()),
467                    &mut total_balance,
468                );
469                accounts.push(Ok(delegator_account));
470            }
471        }
472
473        let remaining_accounts =
474            AccountSecretKey::max_deterministic_count().saturating_sub(counter as usize);
475
476        let non_staker_count = match non_stakers {
477            NonStakers::Fill => remaining_accounts,
478            NonStakers::None => 0,
479            NonStakers::Count(count) => *std::cmp::min(count, &remaining_accounts),
480        };
481
482        let non_staker_total = total_balance.checked_mul(20).expect("overflow") / 80;
483        let non_staker_balance = non_staker_total
484            .checked_div(non_staker_count as u64)
485            .unwrap_or(0);
486
487        println!("Non staker total balance: {}", non_staker_total);
488
489        // Process non-stakers
490        if matches!(non_stakers, NonStakers::Fill | NonStakers::Count(_)) {
491            for _ in 0..non_staker_count {
492                let non_staker_account =
493                    create_account(&mut counter, non_staker_balance, None, &mut total_balance);
494                accounts.push(Ok(non_staker_account));
495            }
496        }
497
498        Self::build_ledger_from_accounts(accounts)
499    }
500
501    fn build_ledger_from_accounts(
502        accounts: impl IntoIterator<Item = Result<ledger::Account, InvalidBigInt>>,
503    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1), InvalidBigInt> {
504        let db =
505            ledger::Database::create_with_token_owners(constraint_constants().ledger_depth as u8);
506        let mask = ledger::Mask::new_root(db);
507        let (mask, total_currency) = accounts.into_iter().try_fold(
508            (mask, 0),
509            |(mut mask, mut total_currency): (ledger::Mask, u64), account| {
510                let account = account?;
511                let account_id = account.id();
512                total_currency = total_currency
513                    .checked_add(account.balance.as_u64())
514                    .expect("overflow");
515                mask.get_or_create_account(account_id, account).unwrap();
516                Ok((mask, total_currency))
517            },
518        )?;
519
520        Ok((mask, v2::CurrencyAmountStableV1(total_currency.into())))
521    }
522
523    fn build_ledger_from_accounts_and_hashes(
524        accounts: impl IntoIterator<Item = Result<ledger::Account, InvalidBigInt>>,
525        hashes: Vec<(u64, Fp)>,
526    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1), InvalidBigInt> {
527        let (mask, total_currency) = Self::build_ledger_from_accounts(accounts)?;
528
529        // Must happen after the accounts have been set to avoid
530        // cache invalidations.
531        mask.set_raw_inner_hashes(hashes);
532
533        Ok((mask, total_currency))
534    }
535}
536
537fn ledger_hash(mask: &mut ledger::Mask) -> v2::LedgerHash {
538    v2::MinaBaseLedgerHash0StableV1(mask.merkle_root().into()).into()
539}
540
541fn genesis_account_iter() -> impl Iterator<Item = ledger::Account> {
542    std::iter::once({
543        // add genesis producer as the first account.
544        let pub_key = AccountSecretKey::genesis_producer().public_key();
545        let account_id = ledger::AccountId::new(pub_key.try_into().unwrap(), Default::default()); // unwrap: `producer` is hardcoded
546        ledger::Account::create_with(account_id, Balance::from_u64(0))
547    })
548}
549
550fn create_genesis_producer_stake_proof(
551    mask: &ledger::Mask,
552) -> v2::MinaBaseSparseLedgerBaseStableV2 {
553    let producer = AccountSecretKey::genesis_producer().public_key();
554    let producer_id =
555        ledger::AccountId::new(producer.try_into().unwrap(), ledger::TokenId::default()); // unwrap: `producer` is hardcoded
556    let sparse_ledger =
557        ledger::sparse_ledger::SparseLedger::of_ledger_subset_exn(mask.clone(), &[producer_id]);
558    (&sparse_ledger).into()
559}
560
561#[derive(Debug, BinProtRead, BinProtWrite)]
562struct LedgerAccountsWithHash {
563    ledger_hash: LedgerHash,
564    accounts: Vec<MinaBaseAccountBinableArgStableV2>,
565    hashes: Vec<(u64, LedgerHash)>,
566}
567
568impl LedgerAccountsWithHash {
569    fn cache(&self) -> Result<(), std::io::Error> {
570        let cache_dir = mina_cache_path("ledgers").unwrap();
571        let cache_file = cache_dir.join(format!("{}.bin", self.ledger_hash));
572        ensure_path_exists(cache_dir)?;
573        let mut file = File::create(cache_file)?;
574        self.binprot_write(&mut file)
575    }
576
577    fn load(ledger_name: String) -> Result<Option<Self>, binprot::Error> {
578        let cache_filename = mina_cache_path(format!("ledgers/{}.bin", ledger_name)).unwrap();
579        if cache_filename.is_file() {
580            let mut file = File::open(cache_filename)?;
581            LedgerAccountsWithHash::binprot_read(&mut file).map(Some)
582        } else {
583            Ok(None)
584        }
585    }
586}
587
588use mina_p2p_messages::v2::{LedgerHash, MinaBaseAccountBinableArgStableV2};
589
590/// Precalculated genesis configuration.
591#[derive(Debug, Serialize, Deserialize, BinProtRead, BinProtWrite)]
592pub struct PrebuiltGenesisConfig {
593    constants: ProtocolConstants,
594    accounts: Vec<MinaBaseAccountBinableArgStableV2>,
595    ledger_hash: LedgerHash,
596    hashes: Vec<(u64, LedgerHash)>,
597    staking_epoch_data: PrebuiltGenesisEpochData,
598    next_epoch_data: PrebuiltGenesisEpochData,
599}
600
601impl PrebuiltGenesisConfig {
602    pub fn read<R: Read>(mut reader: R) -> Result<Self, binprot::Error> {
603        PrebuiltGenesisConfig::binprot_read(&mut reader)
604    }
605
606    pub fn store<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
607        self.binprot_write(&mut writer)
608    }
609
610    pub fn load(self) -> Result<(Vec<ledger::Mask>, GenesisConfigLoaded), GenesisConfigError> {
611        let mut masks = Vec::new();
612        let (mask, genesis_total_currency) = GenesisConfig::build_ledger_from_accounts_and_hashes(
613            self.accounts.into_iter().map(|acc| (&acc).try_into()),
614            self.hashes
615                .into_iter()
616                .map(|(n, h)| Ok((n, h.to_field()?)))
617                .collect::<Result<Vec<_>, InvalidBigInt>>()?,
618        )?;
619        masks.push(mask);
620        let (staking_ledger_mask, staking_epoch_total_currency) =
621            GenesisConfig::build_ledger_from_accounts_and_hashes(
622                self.staking_epoch_data
623                    .accounts
624                    .into_iter()
625                    .map(|acc| (&acc).try_into()),
626                self.staking_epoch_data
627                    .hashes
628                    .into_iter()
629                    .map(|(n, h)| Ok((n, h.to_field()?)))
630                    .collect::<Result<Vec<_>, InvalidBigInt>>()?,
631            )?;
632        let (_mask, next_epoch_total_currency) =
633            GenesisConfig::build_ledger_from_accounts_and_hashes(
634                self.next_epoch_data
635                    .accounts
636                    .into_iter()
637                    .map(|acc| (&acc).try_into()),
638                self.next_epoch_data
639                    .hashes
640                    .into_iter()
641                    .map(|(n, h)| Ok((n, h.to_field()?)))
642                    .collect::<Result<Vec<_>, InvalidBigInt>>()?,
643            )?;
644
645        let load_result = GenesisConfigLoaded {
646            constants: self.constants,
647            genesis_ledger_hash: self.ledger_hash,
648            genesis_total_currency,
649            genesis_producer_stake_proof: create_genesis_producer_stake_proof(&staking_ledger_mask),
650            staking_epoch_ledger_hash: self.staking_epoch_data.ledger_hash.clone(),
651            staking_epoch_total_currency,
652            next_epoch_ledger_hash: self.next_epoch_data.ledger_hash.clone(),
653            next_epoch_total_currency,
654            staking_epoch_seed: self.staking_epoch_data.seed,
655            next_epoch_seed: self.next_epoch_data.seed,
656        };
657        masks.push(staking_ledger_mask);
658        Ok((masks, load_result))
659    }
660
661    #[allow(clippy::result_unit_err)]
662    pub fn from_loaded(
663        (masks, data): (Vec<ledger::Mask>, GenesisConfigLoaded),
664    ) -> Result<Self, ()> {
665        let find_mask_by_hash = |hash: &v2::LedgerHash| {
666            masks
667                .iter()
668                .find(|&m| m.clone().merkle_root() == hash.to_field().unwrap())
669                .ok_or(())
670        };
671        let inner_hashes = |mask: &ledger::Mask| {
672            mask.get_raw_inner_hashes()
673                .into_iter()
674                .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
675                .collect()
676        };
677        let genesis_mask = find_mask_by_hash(&data.genesis_ledger_hash)?;
678        let epoch_data = |hash: v2::LedgerHash, seed: v2::EpochSeed| {
679            find_mask_by_hash(&hash).map(|mask| PrebuiltGenesisEpochData {
680                accounts: mask.to_list().into_iter().map(Into::into).collect(),
681                ledger_hash: hash,
682                hashes: inner_hashes(mask),
683                seed,
684            })
685        };
686        Ok(Self {
687            constants: data.constants,
688            accounts: genesis_mask.to_list().into_iter().map(Into::into).collect(),
689            ledger_hash: data.genesis_ledger_hash,
690            hashes: inner_hashes(genesis_mask),
691            staking_epoch_data: epoch_data(
692                data.staking_epoch_ledger_hash,
693                data.staking_epoch_seed,
694            )?,
695            next_epoch_data: epoch_data(data.next_epoch_ledger_hash, data.next_epoch_seed)?,
696        })
697    }
698}
699
700#[derive(Debug, Serialize, Deserialize, BinProtRead, BinProtWrite)]
701pub struct PrebuiltGenesisEpochData {
702    accounts: Vec<MinaBaseAccountBinableArgStableV2>,
703    ledger_hash: LedgerHash,
704    hashes: Vec<(u64, LedgerHash)>,
705    seed: v2::EpochSeed,
706}
707
708impl TryFrom<DaemonJson> for PrebuiltGenesisConfig {
709    type Error = GenesisConfigError;
710
711    fn try_from(config: DaemonJson) -> Result<Self, Self::Error> {
712        let constants = config
713            .genesis
714            .as_ref()
715            .map_or(PROTOCOL_CONSTANTS, |genesis| genesis.protocol_constants());
716        let ledger = config.ledger.as_ref().ok_or(GenesisConfigError::NoLedger)?;
717        let ledger_accounts = ledger
718            .accounts_with_genesis_winner()
719            .iter()
720            .map(daemon_json::Account::to_account)
721            .collect::<Result<Vec<_>, _>>()?;
722        let accounts = ledger_accounts.iter().map(Into::into).collect();
723        let (mut mask, _total_currency) =
724            GenesisConfig::build_ledger_from_accounts(ledger_accounts.into_iter().map(Ok))?;
725        let ledger_hash = ledger_hash(&mut mask);
726        let hashes = mask
727            .get_raw_inner_hashes()
728            .into_iter()
729            .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
730            .collect();
731        let daemon_json::Epochs { staking, next } = config.epoch_data.unwrap();
732        let staking_epoch_data = staking.try_into().unwrap();
733        let next_epoch_data = next.unwrap().try_into().unwrap();
734        let result = PrebuiltGenesisConfig {
735            constants,
736            accounts,
737            ledger_hash,
738            hashes,
739            staking_epoch_data,
740            next_epoch_data,
741        };
742        Ok(result)
743    }
744}
745
746impl TryFrom<EpochData> for PrebuiltGenesisEpochData {
747    type Error = GenesisConfigError;
748
749    fn try_from(value: EpochData) -> Result<Self, Self::Error> {
750        let EpochData {
751            accounts,
752            hash,
753            s3_data_hash: _,
754            seed,
755        } = value;
756
757        let expected_ledger_hash = hash.unwrap().parse().unwrap();
758        let seed = seed.parse().unwrap();
759        let ledger_accounts = accounts
760            .unwrap()
761            .iter()
762            .map(daemon_json::Account::to_account)
763            .collect::<Result<Vec<_>, _>>()?;
764        let accounts = ledger_accounts.iter().map(Into::into).collect();
765        let (mut mask, _total_currency) =
766            GenesisConfig::build_ledger_from_accounts(ledger_accounts.into_iter().map(Ok))?;
767        let ledger_hash = ledger_hash(&mut mask);
768
769        if ledger_hash != expected_ledger_hash {
770            return Err(Self::Error::LedgerHashMismatch {
771                expected: expected_ledger_hash,
772                computed: ledger_hash,
773            });
774        }
775
776        let hashes = mask
777            .get_raw_inner_hashes()
778            .into_iter()
779            .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
780            .collect();
781
782        Ok(Self {
783            accounts,
784            ledger_hash,
785            hashes,
786            seed,
787        })
788    }
789}