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 ark_ff::fields::arithmetic::InvalidBigInt;
11use ledger::{
12    proofs::caching::{ensure_path_exists, openmina_cache_path},
13    scan_state::currency::Balance,
14    BaseLedger,
15};
16use mina_curves::pasta::Fp;
17use mina_p2p_messages::{
18    binprot::{
19        self,
20        macros::{BinProtRead, BinProtWrite},
21        BinProtRead, BinProtWrite,
22    },
23    v2::{self, PROTOCOL_CONSTANTS},
24};
25use openmina_core::constants::{constraint_constants, DEFAULT_GENESIS_TIMESTAMP_MILLISECONDS};
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                let mut masks = Vec::new();
265                let constants = config
266                    .genesis
267                    .as_ref()
268                    .map_or(PROTOCOL_CONSTANTS, |genesis| genesis.protocol_constants());
269                let ledger = config.ledger.as_ref().ok_or(GenesisConfigError::NoLedger)?;
270                let accounts = ledger
271                    .accounts_with_genesis_winner()
272                    .iter()
273                    .map(daemon_json::Account::to_account)
274                    .collect::<Result<Vec<_>, _>>()?;
275                let (mask, total_currency, genesis_ledger_hash) =
276                    Self::build_or_load_ledger(ledger.ledger_name(), accounts.into_iter())?;
277
278                masks.push(mask.clone());
279                if let Some(expected_hash) = config.ledger.as_ref().and_then(|l| l.hash.as_ref()) {
280                    if expected_hash != &genesis_ledger_hash.to_string() {
281                        return Err(GenesisConfigError::LedgerHashMismatch {
282                            expected: expected_hash.parse().unwrap(),
283                            computed: genesis_ledger_hash.clone(),
284                        });
285                    }
286                }
287
288                let staking_epoch_ledger_hash;
289                let staking_epoch_total_currency;
290                let staking_epoch_seed: v2::EpochSeed;
291                let next_epoch_ledger_hash;
292                let next_epoch_total_currency;
293                let next_epoch_seed: v2::EpochSeed;
294                let genesis_producer_stake_proof: v2::MinaBaseSparseLedgerBaseStableV2;
295                // TODO(devnet): handle other cases here, right now this works
296                // only for the post-fork genesis
297                if let Some(data) = &config.epoch_data {
298                    let accounts = data
299                        .staking
300                        .accounts
301                        .as_ref()
302                        .unwrap()
303                        .iter()
304                        .map(daemon_json::Account::to_account)
305                        .collect::<Result<Vec<_>, _>>()?;
306                    let (staking_ledger_mask, total_currency, hash) = Self::build_or_load_ledger(
307                        data.staking.ledger_name(),
308                        accounts.into_iter(),
309                    )?;
310                    staking_epoch_ledger_hash = hash;
311                    staking_epoch_total_currency = total_currency;
312                    staking_epoch_seed = v2::EpochSeed::from_str(&data.staking.seed).unwrap();
313                    genesis_producer_stake_proof =
314                        create_genesis_producer_stake_proof(&staking_ledger_mask);
315                    masks.push(staking_ledger_mask);
316
317                    let next = data.next.as_ref().unwrap();
318                    let accounts = next
319                        .accounts
320                        .as_ref()
321                        .unwrap()
322                        .iter()
323                        .map(daemon_json::Account::to_account)
324                        .collect::<Result<Vec<_>, _>>()?;
325                    let (mut _mask, total_currency, hash) =
326                        Self::build_or_load_ledger(next.ledger_name(), accounts.into_iter())?;
327                    next_epoch_ledger_hash = hash;
328                    next_epoch_total_currency = total_currency;
329                    next_epoch_seed =
330                        v2::EpochSeed::from_str(&data.next.as_ref().unwrap().seed).unwrap();
331                } else {
332                    staking_epoch_ledger_hash = genesis_ledger_hash.clone();
333                    staking_epoch_total_currency = total_currency.clone();
334                    staking_epoch_seed = v2::EpochSeed::zero();
335                    next_epoch_ledger_hash = genesis_ledger_hash.clone();
336                    next_epoch_total_currency = total_currency.clone();
337                    next_epoch_seed = v2::EpochSeed::zero();
338                    genesis_producer_stake_proof = create_genesis_producer_stake_proof(&mask);
339                }
340
341                let result = GenesisConfigLoaded {
342                    constants,
343                    genesis_ledger_hash,
344                    genesis_total_currency: total_currency,
345                    genesis_producer_stake_proof,
346                    staking_epoch_ledger_hash,
347                    staking_epoch_total_currency,
348                    next_epoch_ledger_hash,
349                    next_epoch_total_currency,
350                    staking_epoch_seed,
351                    next_epoch_seed,
352                };
353                (masks, result)
354            }
355            Self::DaemonJsonFile(path) => {
356                let reader = File::open(path)?;
357                let c = serde_json::from_reader(reader)?;
358                Self::DaemonJson(c).load()?
359            }
360        })
361    }
362
363    fn build_or_load_ledger(
364        ledger_name: String,
365        accounts: impl Iterator<Item = ledger::Account>,
366    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1, LedgerHash), GenesisConfigError> {
367        openmina_core::info!(
368            openmina_core::log::system_time();
369            kind = "ledger loading",
370            message = "loading the ledger",
371            ledger_name = ledger_name,
372        );
373        match LedgerAccountsWithHash::load(ledger_name)? {
374            Some(accounts_with_hash) => {
375                let (mask, total_currency) = Self::build_ledger_from_accounts_and_hashes(
376                    accounts_with_hash
377                        .accounts
378                        .iter()
379                        .map(ledger::Account::try_from),
380                    accounts_with_hash
381                        .hashes
382                        .into_iter()
383                        .map(|(n, h)| Ok((n, h.to_field()?)))
384                        .collect::<Result<Vec<_>, InvalidBigInt>>()?,
385                )?;
386                openmina_core::info!(
387                    openmina_core::log::system_time();
388                    kind = "ledger loaded",
389                    message = "loaded from cache",
390                    ledger_hash = accounts_with_hash.ledger_hash.to_string(),
391                );
392                Ok((mask, total_currency, accounts_with_hash.ledger_hash))
393            }
394            None => {
395                let (mut mask, total_currency) =
396                    Self::build_ledger_from_accounts(accounts.into_iter().map(Ok))?;
397                let hash = ledger_hash(&mut mask);
398                let ledger_accounts = LedgerAccountsWithHash {
399                    accounts: mask.fold(Vec::new(), |mut acc, a| {
400                        acc.push(a.into());
401                        acc
402                    }),
403                    ledger_hash: hash.clone(),
404                    hashes: mask
405                        .get_raw_inner_hashes()
406                        .into_iter()
407                        .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
408                        .collect(),
409                };
410                ledger_accounts.cache()?;
411                openmina_core::info!(
412                    openmina_core::log::system_time();
413                    kind = "ledger loaded",
414                    message = "built from config and cached",
415                    ledger_hash = hash.to_string(),
416                );
417                Ok((mask, total_currency, hash))
418            }
419        }
420    }
421
422    fn build_ledger_from_balances_delegator_table(
423        block_producers: impl IntoIterator<Item = (u64, impl IntoIterator<Item = u64>)>,
424        non_stakers: &NonStakers,
425    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1), InvalidBigInt> {
426        let mut counter = 0;
427        let mut total_balance = 0;
428
429        fn create_account(
430            counter: &mut u64,
431            balance: u64,
432            delegate: Option<mina_signer::CompressedPubKey>,
433            total_balance: &mut u64,
434        ) -> ledger::Account {
435            let sec_key = AccountSecretKey::deterministic(*counter);
436            let pub_key: mina_signer::CompressedPubKey = sec_key.public_key().try_into().unwrap(); // unwrap: `sec_key` was generated
437            let account_id = ledger::AccountId::new(pub_key.clone(), Default::default());
438            let mut account =
439                ledger::Account::create_with(account_id, Balance::from_mina(balance).unwrap());
440            account.delegate = delegate;
441            *total_balance = total_balance.checked_add(balance).expect("overflow");
442            *counter = counter.checked_add(1).expect("overflow");
443            // println!("Created account with balance: {}, total_balance: {}", balance, *total_balance); // Debug print
444            account
445        }
446
447        let mut accounts = genesis_account_iter().map(Ok).collect::<Vec<_>>();
448
449        // Process block producers and their delegators
450        for (bp_balance, delegators) in block_producers {
451            let bp_account = create_account(&mut counter, bp_balance, None, &mut total_balance);
452            let bp_pub_key = bp_account.public_key.clone();
453            accounts.push(Ok(bp_account));
454
455            for balance in delegators {
456                let delegator_account = create_account(
457                    &mut counter,
458                    balance,
459                    Some(bp_pub_key.clone()),
460                    &mut total_balance,
461                );
462                accounts.push(Ok(delegator_account));
463            }
464        }
465
466        let remaining_accounts = AccountSecretKey::max_deterministic_count()
467            .checked_sub(counter as usize)
468            .unwrap_or_default();
469
470        let non_staker_count = match non_stakers {
471            NonStakers::Fill => remaining_accounts,
472            NonStakers::None => 0,
473            NonStakers::Count(count) => *std::cmp::min(count, &remaining_accounts),
474        };
475
476        let non_staker_total = total_balance.checked_mul(20).expect("overflow") / 80;
477        let non_staker_balance = non_staker_total
478            .checked_div(non_staker_count as u64)
479            .unwrap_or(0);
480
481        println!("Non staker total balance: {}", non_staker_total);
482
483        // Process non-stakers
484        if matches!(non_stakers, NonStakers::Fill | NonStakers::Count(_)) {
485            for _ in 0..non_staker_count {
486                let non_staker_account =
487                    create_account(&mut counter, non_staker_balance, None, &mut total_balance);
488                accounts.push(Ok(non_staker_account));
489            }
490        }
491
492        Self::build_ledger_from_accounts(accounts)
493    }
494
495    fn build_ledger_from_accounts(
496        accounts: impl IntoIterator<Item = Result<ledger::Account, InvalidBigInt>>,
497    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1), InvalidBigInt> {
498        let db =
499            ledger::Database::create_with_token_owners(constraint_constants().ledger_depth as u8);
500        let mask = ledger::Mask::new_root(db);
501        let (mask, total_currency) = accounts.into_iter().try_fold(
502            (mask, 0),
503            |(mut mask, mut total_currency): (ledger::Mask, u64), account| {
504                let account = account?;
505                let account_id = account.id();
506                total_currency = total_currency
507                    .checked_add(account.balance.as_u64())
508                    .expect("overflow");
509                mask.get_or_create_account(account_id, account).unwrap();
510                Ok((mask, total_currency))
511            },
512        )?;
513
514        Ok((mask, v2::CurrencyAmountStableV1(total_currency.into())))
515    }
516
517    fn build_ledger_from_accounts_and_hashes(
518        accounts: impl IntoIterator<Item = Result<ledger::Account, InvalidBigInt>>,
519        hashes: Vec<(u64, Fp)>,
520    ) -> Result<(ledger::Mask, v2::CurrencyAmountStableV1), InvalidBigInt> {
521        let (mask, total_currency) = Self::build_ledger_from_accounts(accounts)?;
522
523        // Must happen after the accounts have been set to avoid
524        // cache invalidations.
525        mask.set_raw_inner_hashes(hashes);
526
527        Ok((mask, total_currency))
528    }
529}
530
531fn ledger_hash(mask: &mut ledger::Mask) -> v2::LedgerHash {
532    v2::MinaBaseLedgerHash0StableV1(mask.merkle_root().into()).into()
533}
534
535fn genesis_account_iter() -> impl Iterator<Item = ledger::Account> {
536    std::iter::once({
537        // add genesis producer as the first account.
538        let pub_key = AccountSecretKey::genesis_producer().public_key();
539        let account_id = ledger::AccountId::new(pub_key.try_into().unwrap(), Default::default()); // unwrap: `producer` is hardcoded
540        ledger::Account::create_with(account_id, Balance::from_u64(0))
541    })
542}
543
544fn create_genesis_producer_stake_proof(
545    mask: &ledger::Mask,
546) -> v2::MinaBaseSparseLedgerBaseStableV2 {
547    let producer = AccountSecretKey::genesis_producer().public_key();
548    let producer_id =
549        ledger::AccountId::new(producer.try_into().unwrap(), ledger::TokenId::default()); // unwrap: `producer` is hardcoded
550    let sparse_ledger =
551        ledger::sparse_ledger::SparseLedger::of_ledger_subset_exn(mask.clone(), &[producer_id]);
552    (&sparse_ledger).into()
553}
554
555#[derive(Debug, BinProtRead, BinProtWrite)]
556struct LedgerAccountsWithHash {
557    ledger_hash: LedgerHash,
558    accounts: Vec<MinaBaseAccountBinableArgStableV2>,
559    hashes: Vec<(u64, LedgerHash)>,
560}
561
562impl LedgerAccountsWithHash {
563    fn cache(&self) -> Result<(), std::io::Error> {
564        let cache_dir = openmina_cache_path("ledgers").unwrap();
565        let cache_file = cache_dir.join(format!("{}.bin", self.ledger_hash));
566        ensure_path_exists(cache_dir)?;
567        let mut file = File::create(cache_file)?;
568        self.binprot_write(&mut file)
569    }
570
571    fn load(ledger_name: String) -> Result<Option<Self>, binprot::Error> {
572        let cache_filename = openmina_cache_path(format!("ledgers/{}.bin", ledger_name)).unwrap();
573        if cache_filename.is_file() {
574            let mut file = File::open(cache_filename)?;
575            LedgerAccountsWithHash::binprot_read(&mut file).map(Some)
576        } else {
577            Ok(None)
578        }
579    }
580}
581
582use mina_p2p_messages::v2::{LedgerHash, MinaBaseAccountBinableArgStableV2};
583
584/// Precalculated genesis configuration.
585#[derive(Debug, Serialize, Deserialize, BinProtRead, BinProtWrite)]
586pub struct PrebuiltGenesisConfig {
587    constants: ProtocolConstants,
588    accounts: Vec<MinaBaseAccountBinableArgStableV2>,
589    ledger_hash: LedgerHash,
590    hashes: Vec<(u64, LedgerHash)>,
591    staking_epoch_data: PrebuiltGenesisEpochData,
592    next_epoch_data: PrebuiltGenesisEpochData,
593}
594
595impl PrebuiltGenesisConfig {
596    pub fn read<R: Read>(mut reader: R) -> Result<Self, binprot::Error> {
597        PrebuiltGenesisConfig::binprot_read(&mut reader)
598    }
599
600    pub fn store<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
601        self.binprot_write(&mut writer)
602    }
603
604    pub fn load(self) -> Result<(Vec<ledger::Mask>, GenesisConfigLoaded), GenesisConfigError> {
605        let mut masks = Vec::new();
606        let (mask, genesis_total_currency) = GenesisConfig::build_ledger_from_accounts_and_hashes(
607            self.accounts.into_iter().map(|acc| (&acc).try_into()),
608            self.hashes
609                .into_iter()
610                .map(|(n, h)| Ok((n, h.to_field()?)))
611                .collect::<Result<Vec<_>, InvalidBigInt>>()?,
612        )?;
613        masks.push(mask);
614        let (staking_ledger_mask, staking_epoch_total_currency) =
615            GenesisConfig::build_ledger_from_accounts_and_hashes(
616                self.staking_epoch_data
617                    .accounts
618                    .into_iter()
619                    .map(|acc| (&acc).try_into()),
620                self.staking_epoch_data
621                    .hashes
622                    .into_iter()
623                    .map(|(n, h)| Ok((n, h.to_field()?)))
624                    .collect::<Result<Vec<_>, InvalidBigInt>>()?,
625            )?;
626        let (_mask, next_epoch_total_currency) =
627            GenesisConfig::build_ledger_from_accounts_and_hashes(
628                self.next_epoch_data
629                    .accounts
630                    .into_iter()
631                    .map(|acc| (&acc).try_into()),
632                self.next_epoch_data
633                    .hashes
634                    .into_iter()
635                    .map(|(n, h)| Ok((n, h.to_field()?)))
636                    .collect::<Result<Vec<_>, InvalidBigInt>>()?,
637            )?;
638
639        let load_result = GenesisConfigLoaded {
640            constants: self.constants,
641            genesis_ledger_hash: self.ledger_hash,
642            genesis_total_currency,
643            genesis_producer_stake_proof: create_genesis_producer_stake_proof(&staking_ledger_mask),
644            staking_epoch_ledger_hash: self.staking_epoch_data.ledger_hash.clone(),
645            staking_epoch_total_currency,
646            next_epoch_ledger_hash: self.next_epoch_data.ledger_hash.clone(),
647            next_epoch_total_currency,
648            staking_epoch_seed: self.staking_epoch_data.seed,
649            next_epoch_seed: self.next_epoch_data.seed,
650        };
651        masks.push(staking_ledger_mask);
652        Ok((masks, load_result))
653    }
654
655    #[allow(clippy::result_unit_err)]
656    pub fn from_loaded(
657        (masks, data): (Vec<ledger::Mask>, GenesisConfigLoaded),
658    ) -> Result<Self, ()> {
659        let find_mask_by_hash = |hash: &v2::LedgerHash| {
660            masks
661                .iter()
662                .find(|&m| m.clone().merkle_root() == hash.to_field().unwrap())
663                .ok_or(())
664        };
665        let inner_hashes = |mask: &ledger::Mask| {
666            mask.get_raw_inner_hashes()
667                .into_iter()
668                .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
669                .collect()
670        };
671        let genesis_mask = find_mask_by_hash(&data.genesis_ledger_hash)?;
672        let epoch_data = |hash: v2::LedgerHash, seed: v2::EpochSeed| {
673            find_mask_by_hash(&hash).map(|mask| PrebuiltGenesisEpochData {
674                accounts: mask.to_list().into_iter().map(Into::into).collect(),
675                ledger_hash: hash,
676                hashes: inner_hashes(mask),
677                seed,
678            })
679        };
680        Ok(Self {
681            constants: data.constants,
682            accounts: genesis_mask.to_list().into_iter().map(Into::into).collect(),
683            ledger_hash: data.genesis_ledger_hash,
684            hashes: inner_hashes(genesis_mask),
685            staking_epoch_data: epoch_data(
686                data.staking_epoch_ledger_hash,
687                data.staking_epoch_seed,
688            )?,
689            next_epoch_data: epoch_data(data.next_epoch_ledger_hash, data.next_epoch_seed)?,
690        })
691    }
692}
693
694#[derive(Debug, Serialize, Deserialize, BinProtRead, BinProtWrite)]
695pub struct PrebuiltGenesisEpochData {
696    accounts: Vec<MinaBaseAccountBinableArgStableV2>,
697    ledger_hash: LedgerHash,
698    hashes: Vec<(u64, LedgerHash)>,
699    seed: v2::EpochSeed,
700}
701
702impl TryFrom<DaemonJson> for PrebuiltGenesisConfig {
703    type Error = GenesisConfigError;
704
705    fn try_from(config: DaemonJson) -> Result<Self, Self::Error> {
706        let constants = config
707            .genesis
708            .as_ref()
709            .map_or(PROTOCOL_CONSTANTS, |genesis| genesis.protocol_constants());
710        let ledger = config.ledger.as_ref().ok_or(GenesisConfigError::NoLedger)?;
711        let ledger_accounts = ledger
712            .accounts_with_genesis_winner()
713            .iter()
714            .map(daemon_json::Account::to_account)
715            .collect::<Result<Vec<_>, _>>()?;
716        let accounts = ledger_accounts.iter().map(Into::into).collect();
717        let (mut mask, _total_currency) =
718            GenesisConfig::build_ledger_from_accounts(ledger_accounts.into_iter().map(Ok))?;
719        let ledger_hash = ledger_hash(&mut mask);
720        let hashes = mask
721            .get_raw_inner_hashes()
722            .into_iter()
723            .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
724            .collect();
725        let daemon_json::Epochs { staking, next } = config.epoch_data.unwrap();
726        let staking_epoch_data = staking.try_into().unwrap();
727        let next_epoch_data = next.unwrap().try_into().unwrap();
728        let result = PrebuiltGenesisConfig {
729            constants,
730            accounts,
731            ledger_hash,
732            hashes,
733            staking_epoch_data,
734            next_epoch_data,
735        };
736        Ok(result)
737    }
738}
739
740impl TryFrom<EpochData> for PrebuiltGenesisEpochData {
741    type Error = GenesisConfigError;
742
743    fn try_from(value: EpochData) -> Result<Self, Self::Error> {
744        let EpochData {
745            accounts,
746            hash,
747            s3_data_hash: _,
748            seed,
749        } = value;
750
751        let expected_ledger_hash = hash.unwrap().parse().unwrap();
752        let seed = seed.parse().unwrap();
753        let ledger_accounts = accounts
754            .unwrap()
755            .iter()
756            .map(daemon_json::Account::to_account)
757            .collect::<Result<Vec<_>, _>>()?;
758        let accounts = ledger_accounts.iter().map(Into::into).collect();
759        let (mut mask, _total_currency) =
760            GenesisConfig::build_ledger_from_accounts(ledger_accounts.into_iter().map(Ok))?;
761        let ledger_hash = ledger_hash(&mut mask);
762
763        if ledger_hash != expected_ledger_hash {
764            return Err(Self::Error::LedgerHashMismatch {
765                expected: expected_ledger_hash,
766                computed: ledger_hash,
767            });
768        }
769
770        let hashes = mask
771            .get_raw_inner_hashes()
772            .into_iter()
773            .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash)))
774            .collect();
775
776        Ok(Self {
777            accounts,
778            ledger_hash,
779            hashes,
780            seed,
781        })
782    }
783}