casper_node/utils/chain_specification/
parse_toml.rs

1//! Helper struct and function for parsing a chainspec configuration file into its respective domain
2//! object.
3//!
4//! The runtime representation defined by the chainspec object graph is all-inclusive.
5//! However, as an implementation detail, the reference implementation splits the data up into
6//! multiple topical files.
7//!
8//! In addition to the mandatory base chainspec file, there is a file containing genesis account
9//! definitions for a given network (produced at genesis). This file contains all accounts that will
10//! be (or were, historically) created at genesis, their initial balances, initial staking (both
11//! validators and delegators). The total initial supply of a new network is a consequence of the
12//! sum of the token issued to these accounts. For a test network or small sidechain, the contents
13//! of this file might be small but for a full sized network there is quite a lot of data.
14//!
15//!
16//! Further, when protocol version upgrades are put forth they are allowed to have a file containing
17//! proposed changes to global state that if accepted will be applied as of the upgrade's block
18//! height and onward. This file is optional (more clearly, on an as needed basis only), a given
19//! network might not ever have such a file over its lifetime, and the contents of the file can
20//! be arbitrarily large as it contains encoded bytes of data. Each such file is directly associated
21//! to the specific chainspec file the changes are proposed with; each one is essentially a one off.
22//!
23//! This capability can and has been used to allow the introduction of new capabilities to the
24//! system which require some introduction of value(s) to global state to enable; this is a purely
25//! additive / extension type upgrade. However, this capability can also be leveraged as part of a
26//! social consensus to make changes to the validator set and / or to assert new values for existing
27//! global state entries. In either case, the contents of the file are parseable and verifiable in
28//! advance of their acceptance and application to a given network.
29
30use std::{convert::TryFrom, path::Path};
31
32use serde::{Deserialize, Serialize};
33
34use casper_types::{
35    bytesrepr::Bytes, file_utils, AccountsConfig, ActivationPoint, Chainspec, ChainspecRawBytes,
36    CoreConfig, GlobalStateUpdate, GlobalStateUpdateConfig, HighwayConfig, NetworkConfig,
37    ProtocolConfig, ProtocolVersion, StorageCosts, SystemConfig, TransactionConfig, VacancyConfig,
38    WasmConfig,
39};
40
41use crate::utils::{
42    chain_specification::error::{ChainspecAccountsLoadError, Error, GlobalStateUpdateLoadError},
43    Loadable,
44};
45
46// The names of chainspec related files on disk.
47/// The chainspec file name.
48pub const CHAINSPEC_FILENAME: &str = "chainspec.toml";
49/// The genesis accounts file name.
50pub const CHAINSPEC_ACCOUNTS_FILENAME: &str = "accounts.toml";
51/// The global state update file name.
52pub const CHAINSPEC_GLOBAL_STATE_FILENAME: &str = "global_state.toml";
53
54#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
55// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
56#[serde(deny_unknown_fields)]
57struct TomlNetwork {
58    name: String,
59    maximum_net_message_size: u32,
60}
61
62#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
63// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
64#[serde(deny_unknown_fields)]
65struct TomlProtocol {
66    version: ProtocolVersion,
67    hard_reset: bool,
68    activation_point: ActivationPoint,
69}
70
71/// A chainspec configuration as laid out in the TOML-encoded configuration file.
72#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
73// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
74#[serde(deny_unknown_fields)]
75pub(super) struct TomlChainspec {
76    protocol: TomlProtocol,
77    network: TomlNetwork,
78    core: CoreConfig,
79    transactions: TransactionConfig,
80    highway: HighwayConfig,
81    wasm: WasmConfig,
82    system_costs: SystemConfig,
83    vacancy: VacancyConfig,
84    storage_costs: StorageCosts,
85}
86
87impl From<&Chainspec> for TomlChainspec {
88    fn from(chainspec: &Chainspec) -> Self {
89        let protocol = TomlProtocol {
90            version: chainspec.protocol_config.version,
91            hard_reset: chainspec.protocol_config.hard_reset,
92            activation_point: chainspec.protocol_config.activation_point,
93        };
94        let network = TomlNetwork {
95            name: chainspec.network_config.name.clone(),
96            maximum_net_message_size: chainspec.network_config.maximum_net_message_size,
97        };
98        let core = chainspec.core_config.clone();
99        let transactions = chainspec.transaction_config.clone();
100        let highway = chainspec.highway_config;
101        let wasm = chainspec.wasm_config;
102        let system_costs = chainspec.system_costs_config;
103        let vacancy = chainspec.vacancy_config;
104        let storage_costs = chainspec.storage_costs;
105
106        TomlChainspec {
107            protocol,
108            network,
109            core,
110            transactions,
111            highway,
112            wasm,
113            system_costs,
114            vacancy,
115            storage_costs,
116        }
117    }
118}
119
120pub(super) fn parse_toml<P: AsRef<Path>>(
121    chainspec_path: P,
122) -> Result<(Chainspec, ChainspecRawBytes), Error> {
123    let chainspec_bytes =
124        file_utils::read_file(chainspec_path.as_ref()).map_err(Error::LoadChainspec)?;
125    let toml_chainspec: TomlChainspec =
126        toml::from_str(std::str::from_utf8(&chainspec_bytes).unwrap())?;
127
128    let root = chainspec_path
129        .as_ref()
130        .parent()
131        .unwrap_or_else(|| Path::new(""));
132
133    // accounts.toml must live in the same directory as chainspec.toml.
134    let (accounts_config, maybe_genesis_accounts_bytes) = parse_toml_accounts(root)?;
135
136    let network_config = NetworkConfig {
137        name: toml_chainspec.network.name,
138        accounts_config,
139        maximum_net_message_size: toml_chainspec.network.maximum_net_message_size,
140    };
141
142    // global_state_update.toml must live in the same directory as chainspec.toml.
143    let (global_state_update, maybe_global_state_bytes) = match parse_toml_global_state(root)? {
144        Some((config, bytes)) => (
145            Some(
146                GlobalStateUpdate::try_from(config)
147                    .map_err(GlobalStateUpdateLoadError::DecodingKeyValuePairs)?,
148            ),
149            Some(bytes),
150        ),
151        None => (None, None),
152    };
153
154    let protocol_config = ProtocolConfig {
155        version: toml_chainspec.protocol.version,
156        hard_reset: toml_chainspec.protocol.hard_reset,
157        activation_point: toml_chainspec.protocol.activation_point,
158        global_state_update,
159    };
160
161    let chainspec = Chainspec {
162        protocol_config,
163        network_config,
164        core_config: toml_chainspec.core,
165        transaction_config: toml_chainspec.transactions,
166        highway_config: toml_chainspec.highway,
167        wasm_config: toml_chainspec.wasm,
168        system_costs_config: toml_chainspec.system_costs,
169        vacancy_config: toml_chainspec.vacancy,
170        storage_costs: toml_chainspec.storage_costs,
171    };
172    let chainspec_raw_bytes = ChainspecRawBytes::new(
173        Bytes::from(chainspec_bytes),
174        maybe_genesis_accounts_bytes,
175        maybe_global_state_bytes,
176    );
177
178    Ok((chainspec, chainspec_raw_bytes))
179}
180
181impl Loadable for (Chainspec, ChainspecRawBytes) {
182    type Error = Error;
183
184    fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error> {
185        parse_toml(path.as_ref().join(CHAINSPEC_FILENAME))
186    }
187}
188
189/// Returns `Self` and the raw bytes of the file.
190///
191/// If the file doesn't exist, returns `Ok` with an empty `AccountsConfig` and `None` bytes.
192pub(super) fn parse_toml_accounts<P: AsRef<Path>>(
193    dir_path: P,
194) -> Result<(AccountsConfig, Option<Bytes>), ChainspecAccountsLoadError> {
195    let accounts_path = dir_path.as_ref().join(CHAINSPEC_ACCOUNTS_FILENAME);
196    if !accounts_path.is_file() {
197        let config = AccountsConfig::new(vec![], vec![], vec![]);
198        let maybe_bytes = None;
199        return Ok((config, maybe_bytes));
200    }
201    let bytes = file_utils::read_file(accounts_path)?;
202    let config: AccountsConfig = toml::from_str(std::str::from_utf8(&bytes).unwrap())?;
203    Ok((config, Some(Bytes::from(bytes))))
204}
205
206pub(super) fn parse_toml_global_state<P: AsRef<Path>>(
207    path: P,
208) -> Result<Option<(GlobalStateUpdateConfig, Bytes)>, GlobalStateUpdateLoadError> {
209    let update_path = path.as_ref().join(CHAINSPEC_GLOBAL_STATE_FILENAME);
210    if !update_path.is_file() {
211        return Ok(None);
212    }
213    let bytes = file_utils::read_file(update_path)?;
214    let config = toml::from_str(std::str::from_utf8(&bytes).unwrap())?;
215    Ok(Some((config, Bytes::from(bytes))))
216}