Skip to main content

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::{AccountOwner, ChainId},
12    ownership::ChainOwnership,
13};
14use linera_execution::committee::Committee;
15use linera_storage::Storage;
16use serde::{Deserialize, Serialize};
17
18/// An error that can occur while building or applying a [`GenesisConfig`].
19#[derive(Debug, thiserror::Error)]
20#[allow(missing_docs)]
21pub enum Error {
22    #[error("I/O error: {0}")]
23    IoError(#[from] std::io::Error),
24    #[error("chain error: {0}")]
25    Chain(#[from] linera_chain::ChainError),
26    #[error("storage is already initialized: {0:?}")]
27    StorageIsAlreadyInitialized(Box<NetworkDescription>),
28    #[error("no admin chain configured")]
29    NoAdminChain,
30}
31
32fn make_chain(
33    index: u32,
34    public_key: AccountPublicKey,
35    balance: Amount,
36    timestamp: Timestamp,
37) -> ChainDescription {
38    let origin = ChainOrigin::Root(index);
39    let config = InitialChainConfig {
40        application_permissions: Default::default(),
41        account: AccountOwner::CHAIN,
42        balance,
43        epoch: Epoch::ZERO,
44        ownership: ChainOwnership::single(public_key.into()),
45    };
46    ChainDescription::new(origin, config, timestamp)
47}
48
49/// The initial configuration of a Linera network, defining its genesis state.
50#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct GenesisConfig {
52    /// The initial committee of validators.
53    pub committee: Committee,
54    /// The timestamp of the genesis block.
55    pub timestamp: Timestamp,
56    /// The descriptions of the chains created at genesis, the first of which is the admin chain.
57    pub chains: Vec<ChainDescription>,
58    /// The name of the network.
59    pub network_name: String,
60}
61
62impl BcsSignable<'_> for GenesisConfig {}
63
64impl GenesisConfig {
65    /// Creates a `GenesisConfig` with the first chain being the admin chain.
66    pub fn new(
67        committee: Committee,
68        timestamp: Timestamp,
69        network_name: String,
70        admin_public_key: AccountPublicKey,
71        admin_balance: Amount,
72    ) -> Self {
73        let admin_chain = make_chain(0, admin_public_key, admin_balance, timestamp);
74        Self {
75            committee,
76            timestamp,
77            chains: vec![admin_chain],
78            network_name,
79        }
80    }
81
82    /// Adds a new root chain with the given public key and balance, and returns its description.
83    pub fn add_root_chain(
84        &mut self,
85        public_key: AccountPublicKey,
86        balance: Amount,
87    ) -> ChainDescription {
88        let description = make_chain(
89            u32::try_from(self.chains.len()).expect("more than u32::MAX genesis chains"),
90            public_key,
91            balance,
92            self.timestamp,
93        );
94        self.chains.push(description.clone());
95        description
96    }
97
98    /// Returns the description of the admin chain.
99    pub fn admin_chain_description(&self) -> &ChainDescription {
100        &self.chains[0]
101    }
102
103    /// Returns the ID of the admin chain.
104    pub fn admin_chain_id(&self) -> ChainId {
105        self.admin_chain_description().id()
106    }
107
108    /// Writes the committee, network description and genesis chains to storage.
109    pub async fn initialize_storage<S>(&self, storage: &mut S) -> Result<(), Error>
110    where
111        S: Storage + Clone + 'static,
112    {
113        if let Some(description) = storage
114            .read_network_description()
115            .await
116            .map_err(linera_chain::ChainError::from)?
117        {
118            if description != self.network_description() {
119                tracing::error!(
120                    current_network=?description,
121                    new_network=?self.network_description(),
122                    "storage already initialized"
123                );
124                return Err(Error::StorageIsAlreadyInitialized(Box::new(description)));
125            }
126            tracing::debug!(?description, "storage already initialized");
127            return Ok(());
128        }
129        let network_description = self.network_description();
130        storage
131            .write_blob(&self.committee_blob())
132            .await
133            .map_err(linera_chain::ChainError::from)?;
134        storage
135            .write_network_description(&network_description)
136            .await
137            .map_err(linera_chain::ChainError::from)?;
138        for description in &self.chains {
139            storage.create_chain(description.clone()).await?;
140        }
141        Ok(())
142    }
143
144    /// Returns the cryptographic hash of this genesis configuration.
145    pub fn hash(&self) -> CryptoHash {
146        CryptoHash::new(self)
147    }
148
149    /// Returns the committee serialized as a blob.
150    pub fn committee_blob(&self) -> Blob {
151        Blob::new_committee(
152            bcs::to_bytes(&self.committee).expect("serializing a committee should succeed"),
153        )
154    }
155
156    /// Returns the network description derived from this genesis configuration.
157    pub fn network_description(&self) -> NetworkDescription {
158        NetworkDescription {
159            name: self.network_name.clone(),
160            genesis_config_hash: CryptoHash::new(self),
161            genesis_timestamp: self.timestamp,
162            genesis_committee_blob_hash: self.committee_blob().id().hash,
163            admin_chain_id: self.admin_chain_id(),
164        }
165    }
166
167    /// Creates a `GenesisConfig` for testing from a `TestBuilder`.
168    #[cfg(with_testing)]
169    pub fn new_for_testing<B: crate::test_utils::StorageBuilder>(
170        builder: &crate::test_utils::TestBuilder<B>,
171    ) -> Self {
172        let mut genesis_chains = builder.genesis_chains().into_iter();
173        let (admin_public_key, admin_balance) = genesis_chains
174            .next()
175            .expect("should have at least one chain");
176        let mut genesis_config = Self::new(
177            builder.initial_committee.clone(),
178            Timestamp::from(0),
179            "test network".to_string(),
180            admin_public_key,
181            admin_balance,
182        );
183        for (public_key, amount) in genesis_chains {
184            genesis_config.add_root_chain(public_key, amount);
185        }
186        genesis_config
187    }
188}