linera_core/
genesis_config.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use linera_base::{
6    crypto::{AccountPublicKey, BcsSignable, CryptoHash},
7    data_types::{
8        Amount, Blob, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, NetworkDescription,
9        Timestamp,
10    },
11    identifiers::ChainId,
12    ownership::ChainOwnership,
13};
14use linera_execution::committee::Committee;
15use linera_storage::Storage;
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, thiserror::Error)]
19pub enum Error {
20    #[error("I/O error: {0}")]
21    IoError(#[from] std::io::Error),
22    #[error("chain error: {0}")]
23    Chain(#[from] linera_chain::ChainError),
24    #[error("storage is already initialized: {0:?}")]
25    StorageIsAlreadyInitialized(Box<NetworkDescription>),
26    #[error("no admin chain configured")]
27    NoAdminChain,
28}
29
30fn make_chain(
31    index: u32,
32    public_key: AccountPublicKey,
33    balance: Amount,
34    timestamp: Timestamp,
35) -> ChainDescription {
36    let origin = ChainOrigin::Root(index);
37    let config = InitialChainConfig {
38        application_permissions: Default::default(),
39        balance,
40        min_active_epoch: Epoch::ZERO,
41        max_active_epoch: Epoch::ZERO,
42        epoch: Epoch::ZERO,
43        ownership: ChainOwnership::single(public_key.into()),
44    };
45    ChainDescription::new(origin, config, timestamp)
46}
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct GenesisConfig {
50    pub committee: Committee,
51    pub timestamp: Timestamp,
52    pub chains: Vec<ChainDescription>,
53    pub network_name: String,
54}
55
56impl BcsSignable<'_> for GenesisConfig {}
57
58impl GenesisConfig {
59    /// Creates a `GenesisConfig` with the first chain being the admin chain.
60    pub fn new(
61        committee: Committee,
62        timestamp: Timestamp,
63        network_name: String,
64        admin_public_key: AccountPublicKey,
65        admin_balance: Amount,
66    ) -> Self {
67        let admin_chain = make_chain(0, admin_public_key, admin_balance, timestamp);
68        Self {
69            committee,
70            timestamp,
71            chains: vec![admin_chain],
72            network_name,
73        }
74    }
75
76    pub fn add_root_chain(
77        &mut self,
78        public_key: AccountPublicKey,
79        balance: Amount,
80    ) -> ChainDescription {
81        let description = make_chain(
82            self.chains.len() as u32,
83            public_key,
84            balance,
85            self.timestamp,
86        );
87        self.chains.push(description.clone());
88        description
89    }
90
91    pub fn admin_chain_description(&self) -> &ChainDescription {
92        &self.chains[0]
93    }
94
95    pub fn admin_chain_id(&self) -> ChainId {
96        self.admin_chain_description().id()
97    }
98
99    pub async fn initialize_storage<S>(&self, storage: &mut S) -> Result<(), Error>
100    where
101        S: Storage + Clone + 'static,
102    {
103        if let Some(description) = storage
104            .read_network_description()
105            .await
106            .map_err(linera_chain::ChainError::from)?
107        {
108            if description != self.network_description() {
109                tracing::error!(
110                    current_network=?description,
111                    new_network=?self.network_description(),
112                    "storage already initialized"
113                );
114                return Err(Error::StorageIsAlreadyInitialized(Box::new(description)));
115            }
116            tracing::debug!(?description, "storage already initialized");
117            return Ok(());
118        }
119        let network_description = self.network_description();
120        storage
121            .write_blob(&self.committee_blob())
122            .await
123            .map_err(linera_chain::ChainError::from)?;
124        storage
125            .write_network_description(&network_description)
126            .await
127            .map_err(linera_chain::ChainError::from)?;
128        for description in &self.chains {
129            storage.create_chain(description.clone()).await?;
130        }
131        Ok(())
132    }
133
134    pub fn hash(&self) -> CryptoHash {
135        CryptoHash::new(self)
136    }
137
138    pub fn committee_blob(&self) -> Blob {
139        Blob::new_committee(
140            bcs::to_bytes(&self.committee).expect("serializing a committee should succeed"),
141        )
142    }
143
144    pub fn network_description(&self) -> NetworkDescription {
145        NetworkDescription {
146            name: self.network_name.clone(),
147            genesis_config_hash: CryptoHash::new(self),
148            genesis_timestamp: self.timestamp,
149            genesis_committee_blob_hash: self.committee_blob().id().hash,
150            admin_chain_id: self.admin_chain_id(),
151        }
152    }
153
154    /// Creates a `GenesisConfig` for testing from a `TestBuilder`.
155    #[cfg(with_testing)]
156    pub fn new_for_testing<B: crate::test_utils::StorageBuilder>(
157        builder: &crate::test_utils::TestBuilder<B>,
158    ) -> Self {
159        let mut genesis_chains = builder.genesis_chains().into_iter();
160        let (admin_public_key, admin_balance) = genesis_chains
161            .next()
162            .expect("should have at least one chain");
163        let mut genesis_config = Self::new(
164            builder.initial_committee.clone(),
165            Timestamp::from(0),
166            "test network".to_string(),
167            admin_public_key,
168            admin_balance,
169        );
170        for (public_key, amount) in genesis_chains {
171            genesis_config.add_root_chain(public_key, amount);
172        }
173        genesis_config
174    }
175}