linera_client/
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 std::iter::IntoIterator;
6
7use linera_base::{
8    crypto::{AccountPublicKey, BcsSignable, CryptoHash, ValidatorPublicKey, ValidatorSecretKey},
9    data_types::{
10        Amount, Blob, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, NetworkDescription,
11        Timestamp,
12    },
13    identifiers::ChainId,
14    ownership::ChainOwnership,
15};
16use linera_execution::{
17    committee::{Committee, ValidatorState},
18    ResourceControlPolicy,
19};
20use linera_rpc::config::{ValidatorInternalNetworkConfig, ValidatorPublicNetworkConfig};
21use linera_storage::Storage;
22use serde::{Deserialize, Serialize};
23
24#[derive(Debug, thiserror::Error)]
25pub enum Error {
26    #[error("I/O error: {0}")]
27    IoError(#[from] std::io::Error),
28    #[error("chain error: {0}")]
29    Chain(#[from] linera_chain::ChainError),
30    #[error("storage is already initialized: {0:?}")]
31    StorageIsAlreadyInitialized(Box<NetworkDescription>),
32    #[error("no admin chain configured")]
33    NoAdminChain,
34}
35
36/// The public configuration of a validator.
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct ValidatorConfig {
39    /// The public key of the validator.
40    pub public_key: ValidatorPublicKey,
41    /// The account key of the validator.
42    pub account_key: AccountPublicKey,
43    /// The network configuration for the validator.
44    pub network: ValidatorPublicNetworkConfig,
45}
46
47/// The private configuration of a validator service.
48#[derive(Serialize, Deserialize)]
49pub struct ValidatorServerConfig {
50    pub validator: ValidatorConfig,
51    pub validator_secret: ValidatorSecretKey,
52    pub internal_network: ValidatorInternalNetworkConfig,
53}
54
55/// The (public) configuration for all validators.
56#[derive(Debug, Default, Clone, Deserialize, Serialize)]
57pub struct CommitteeConfig {
58    pub validators: Vec<ValidatorConfig>,
59}
60
61impl CommitteeConfig {
62    pub fn into_committee(self, policy: ResourceControlPolicy) -> Committee {
63        let validators = self
64            .validators
65            .into_iter()
66            .map(|v| {
67                (
68                    v.public_key,
69                    ValidatorState {
70                        network_address: v.network.to_string(),
71                        votes: 100,
72                        account_public_key: v.account_key,
73                    },
74                )
75            })
76            .collect();
77        Committee::new(validators, policy)
78    }
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize)]
82pub struct GenesisConfig {
83    pub committee: Committee,
84    pub timestamp: Timestamp,
85    pub chains: Vec<ChainDescription>,
86    pub network_name: String,
87}
88
89impl BcsSignable<'_> for GenesisConfig {}
90
91fn make_chain(
92    index: u32,
93    public_key: AccountPublicKey,
94    balance: Amount,
95    timestamp: Timestamp,
96) -> ChainDescription {
97    let origin = ChainOrigin::Root(index);
98    let config = InitialChainConfig {
99        application_permissions: Default::default(),
100        balance,
101        min_active_epoch: Epoch::ZERO,
102        max_active_epoch: Epoch::ZERO,
103        epoch: Epoch::ZERO,
104        ownership: ChainOwnership::single(public_key.into()),
105    };
106    ChainDescription::new(origin, config, timestamp)
107}
108
109impl GenesisConfig {
110    /// Creates a `GenesisConfig` with the first chain being the admin chain.
111    pub fn new(
112        committee: CommitteeConfig,
113        timestamp: Timestamp,
114        policy: ResourceControlPolicy,
115        network_name: String,
116        admin_public_key: AccountPublicKey,
117        admin_balance: Amount,
118    ) -> Self {
119        let committee = committee.into_committee(policy);
120        let admin_chain = make_chain(0, admin_public_key, admin_balance, timestamp);
121        Self {
122            committee,
123            timestamp,
124            chains: vec![admin_chain],
125            network_name,
126        }
127    }
128
129    pub fn add_root_chain(
130        &mut self,
131        public_key: AccountPublicKey,
132        balance: Amount,
133    ) -> ChainDescription {
134        let description = make_chain(
135            self.chains.len() as u32,
136            public_key,
137            balance,
138            self.timestamp,
139        );
140        self.chains.push(description.clone());
141        description
142    }
143
144    pub fn admin_chain_description(&self) -> &ChainDescription {
145        &self.chains[0]
146    }
147
148    pub fn admin_id(&self) -> ChainId {
149        self.admin_chain_description().id()
150    }
151
152    pub async fn initialize_storage<S>(&self, storage: &mut S) -> Result<(), Error>
153    where
154        S: Storage + Clone + 'static,
155    {
156        if let Some(description) = storage
157            .read_network_description()
158            .await
159            .map_err(linera_chain::ChainError::from)?
160        {
161            if description != self.network_description() {
162                // We can't initialize storage with a different network description.
163                tracing::error!(
164                    current_network=?description,
165                    new_network=?self.network_description(),
166                    "storage already initialized"
167                );
168                return Err(Error::StorageIsAlreadyInitialized(Box::new(description)));
169            }
170            tracing::debug!(?description, "storage already initialized");
171            return Ok(());
172        }
173        let network_description = self.network_description();
174        storage
175            .write_blob(&self.committee_blob())
176            .await
177            .map_err(linera_chain::ChainError::from)?;
178        storage
179            .write_network_description(&network_description)
180            .await
181            .map_err(linera_chain::ChainError::from)?;
182        for description in &self.chains {
183            storage.create_chain(description.clone()).await?;
184        }
185        Ok(())
186    }
187
188    pub fn hash(&self) -> CryptoHash {
189        CryptoHash::new(self)
190    }
191
192    pub fn committee_blob(&self) -> Blob {
193        Blob::new_committee(
194            bcs::to_bytes(&self.committee).expect("serializing a committee should succeed"),
195        )
196    }
197
198    pub fn network_description(&self) -> NetworkDescription {
199        NetworkDescription {
200            name: self.network_name.clone(),
201            genesis_config_hash: CryptoHash::new(self),
202            genesis_timestamp: self.timestamp,
203            genesis_committee_blob_hash: self.committee_blob().id().hash,
204            admin_chain_id: self.admin_id(),
205        }
206    }
207}
208
209#[cfg(with_testing)]
210mod test {
211    use linera_base::data_types::Timestamp;
212    use linera_core::test_utils::{MemoryStorageBuilder, TestBuilder};
213    use linera_rpc::{
214        config::{NetworkProtocol, ValidatorPublicNetworkPreConfig},
215        simple::TransportProtocol,
216    };
217
218    use super::*;
219    use crate::config::{CommitteeConfig, GenesisConfig, ValidatorConfig};
220
221    impl GenesisConfig {
222        /// Create a new local `GenesisConfig` for testing.
223        pub fn new_testing(builder: &TestBuilder<MemoryStorageBuilder>) -> Self {
224            let network = ValidatorPublicNetworkPreConfig {
225                protocol: NetworkProtocol::Simple(TransportProtocol::Tcp),
226                host: "localhost".to_string(),
227                port: 8080,
228            };
229            let validators = builder
230                .initial_committee
231                .validators()
232                .iter()
233                .map(|(public_key, state)| ValidatorConfig {
234                    public_key: *public_key,
235                    network: network.clone(),
236                    account_key: state.account_public_key,
237                })
238                .collect();
239            let mut genesis_chains = builder.genesis_chains().into_iter();
240            let (admin_public_key, admin_balance) = genesis_chains
241                .next()
242                .expect("should have at least one chain");
243            let mut genesis_config = Self::new(
244                CommitteeConfig { validators },
245                Timestamp::from(0),
246                builder.initial_committee.policy().clone(),
247                "test network".to_string(),
248                admin_public_key,
249                admin_balance,
250            );
251            for (public_key, amount) in genesis_chains {
252                genesis_config.add_root_chain(public_key, amount);
253            }
254            genesis_config
255        }
256    }
257}