fuel_core_chain_config/config/
chain.rs1use fuel_core_storage::MerkleRoot;
2use fuel_core_types::{
3 blockchain::header::StateTransitionBytecodeVersion,
4 fuel_crypto::Hasher,
5 fuel_tx::ConsensusParameters,
6 fuel_types::{
7 fmt_truncated_hex,
8 AssetId,
9 },
10};
11use serde::{
12 Deserialize,
13 Serialize,
14};
15#[cfg(feature = "std")]
16use std::fs::File;
17#[cfg(feature = "std")]
18use std::path::Path;
19
20use crate::{
21 genesis::GenesisCommitment,
22 ConsensusConfig,
23};
24
25#[cfg(feature = "std")]
26use crate::SnapshotMetadata;
27
28pub const LOCAL_TESTNET: &str = "local_testnet";
29pub const BYTECODE_NAME: &str = "state_transition_bytecode.wasm";
30
31#[derive(Clone, derivative::Derivative, Deserialize, Serialize, Eq, PartialEq)]
32#[derivative(Debug)]
33pub struct ChainConfig {
34 pub chain_name: String,
35 pub consensus_parameters: ConsensusParameters,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 #[serde(default)]
38 pub genesis_state_transition_version: Option<StateTransitionBytecodeVersion>,
39 #[serde(skip)]
42 #[derivative(Debug(format_with = "fmt_truncated_hex::<16>"))]
43 pub state_transition_bytecode: Vec<u8>,
44 pub consensus: ConsensusConfig,
45}
46
47#[cfg(feature = "test-helpers")]
48impl Default for ChainConfig {
49 fn default() -> Self {
50 Self {
51 chain_name: "local".into(),
52 consensus_parameters: ConsensusParameters::default(),
53 genesis_state_transition_version: Some(
54 fuel_core_types::blockchain::header::LATEST_STATE_TRANSITION_VERSION,
55 ),
56 state_transition_bytecode: vec![],
58 consensus: ConsensusConfig::default_poa(),
59 }
60 }
61}
62
63impl ChainConfig {
64 pub const BASE_ASSET: AssetId = AssetId::zeroed();
65
66 #[cfg(feature = "std")]
67 pub fn load(path: impl AsRef<Path>) -> anyhow::Result<Self> {
68 use std::io::Read;
69 let path = path.as_ref();
70 let mut json = String::new();
71 std::fs::File::open(path)?.read_to_string(&mut json)?;
72 let mut chain_config: ChainConfig =
73 serde_json::from_str(json.as_str()).map_err(|e| {
74 anyhow::Error::new(e).context(format!(
75 "an error occurred while loading the chain state file: {:?}",
76 path.to_str()
77 ))
78 })?;
79
80 let bytecode_path = path.with_file_name(BYTECODE_NAME);
81
82 let bytecode = if bytecode_path.exists() {
83 std::fs::read(bytecode_path).map_err(|e| {
84 anyhow::Error::new(e).context(format!(
85 "an error occurred while loading the state transition bytecode: {:?}",
86 path.to_str()
87 ))
88 })?
89 } else {
90 Vec::new()
91 };
92 chain_config.state_transition_bytecode = bytecode;
93
94 Ok(chain_config)
95 }
96
97 #[cfg(feature = "std")]
98 pub fn from_snapshot_metadata(
99 snapshot_metadata: &SnapshotMetadata,
100 ) -> anyhow::Result<Self> {
101 Self::load(&snapshot_metadata.chain_config)
102 }
103
104 #[cfg(feature = "std")]
105 pub fn write(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
106 use anyhow::Context;
107
108 let bytecode_path = path.as_ref().with_file_name(BYTECODE_NAME);
109 let chain_config_file = File::create(path)?;
110
111 serde_json::to_writer_pretty(chain_config_file, self)
112 .context("failed to dump chain parameters snapshot to JSON")?;
113 std::fs::write(bytecode_path, &self.state_transition_bytecode)
114 .context("failed to write state transition bytecode")?;
115
116 Ok(())
117 }
118
119 #[cfg(feature = "test-helpers")]
120 pub fn local_testnet() -> Self {
121 Self {
122 chain_name: LOCAL_TESTNET.to_string(),
123 ..Default::default()
124 }
125 }
126
127 #[cfg(feature = "test-helpers")]
128 pub fn local_testnet_with_consensus_parameters(cp: &ConsensusParameters) -> Self {
129 Self {
130 chain_name: LOCAL_TESTNET.to_string(),
131 consensus_parameters: cp.clone(),
132 ..Default::default()
133 }
134 }
135}
136
137impl GenesisCommitment for ChainConfig {
138 fn root(&self) -> anyhow::Result<MerkleRoot> {
139 let chain_config_bytes =
140 postcard::to_allocvec(&self).map_err(anyhow::Error::msg)?;
141 let config_hash = *Hasher::default()
142 .chain(chain_config_bytes.as_slice())
143 .chain(self.state_transition_bytecode.as_slice())
144 .finalize();
145
146 Ok(config_hash)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 #[cfg(feature = "std")]
153 use std::env::temp_dir;
154
155 use super::ChainConfig;
156
157 #[cfg(feature = "std")]
158 #[test]
159 fn can_roundtrip_write_and_read() {
160 let tmp_dir = temp_dir();
161 let file = tmp_dir.join("config.json");
162
163 let disk_config = ChainConfig::local_testnet();
164 disk_config.write(&file).unwrap();
165
166 let load_config = ChainConfig::load(&file).unwrap();
167
168 assert_eq!(disk_config, load_config);
169 }
170
171 #[test]
172 fn snapshot_local_testnet_config() {
173 let config = ChainConfig::local_testnet();
174 let json = serde_json::to_string_pretty(&config).unwrap();
175 insta::assert_snapshot!(json);
176 }
177}