1mod confidentiality_values;
2pub(crate) mod json;
3
4use crate::generation::confidentiality_values::{
5 PENDING_CREATE_EXPIRE_TIME, USED_CORRELATION_ID_EXPIRE_TIME,
6};
7use crate::public_models::domain::egress_ip_range::EgressIpRange;
8use crate::{
9 error::Error, output::shell_out, EmissionRate, LimitedAgentIdentity, MemberIdentity,
10 TrustIdentity, ValidatorIdentity,
11};
12use json::{JsonMap, JsonTryAsObjectMut, JsonTryGetMut, JsonValue};
13use serde_json::json;
14use std::path::{Path, PathBuf};
15
16#[derive(Debug)]
17pub struct ChainSpec {
18 pub trust: TrustIdentity,
19 pub limited_agent: LimitedAgentIdentity,
20 pub members: Vec<MemberIdentity>,
21 pub validators: Vec<ValidatorIdentity>,
22 pub validator_emission_rate: EmissionRate,
23}
24
25const CHAIN_SPEC_SOURCE_JSON_FILENAME: &str = "chainSpecSource.json";
27
28const CHAIN_SPEC_FILENAME_CONVENTION: &str = "chainSpecModified.json";
30
31const BLOCKS_UNTIL_AUTO_EXIT: usize = 30 * 24 * 60 * 60 / 6; const TOTAL_CREATED: usize = 0;
34const TOTAL_REDEEMED: usize = 0;
35const EPOCH_DURATION: usize = 100;
36
37impl ChainSpec {
38 fn unzip_chainspec_template(
39 chain_spec_template_zip: &Path,
40 workdir: &Path,
41 ) -> crate::Result<()> {
42 let cmd = format!(
44 "unzip -o {} -d {}",
45 chain_spec_template_zip
46 .to_str()
47 .ok_or_else(|| Error::OsStringIsNotValidString(chain_spec_template_zip.into()))?,
48 &workdir
49 .to_str()
50 .ok_or_else(|| Error::OsStringIsNotValidString(workdir.into()))?
51 );
52 let _res = shell_out(&cmd)?;
53
54 let chainspec_source_path = Path::join(workdir, CHAIN_SPEC_SOURCE_JSON_FILENAME);
55 if !std::path::Path::exists(&chainspec_source_path) {
56 return Err(Error::ChainSpecSourceFileMissing(chainspec_source_path));
57 }
58
59 Ok(())
60 }
61
62 pub fn generate_into<P: AsRef<Path> + std::fmt::Debug>(
69 &self,
70 chain_spec_template_zip: P,
71 target_dir: P,
72 ) -> crate::Result<PathBuf> {
73 let workdir = std::fs::canonicalize(&target_dir).map_err(|e| {
74 Error::IoError(
75 e,
76 format!("Failed to canonicalize working dir path {:?}", target_dir),
77 )
78 })?;
79
80 let chain_spec_template_zip =
81 std::fs::canonicalize(&chain_spec_template_zip).map_err(|e| {
82 Error::IoError(
83 e,
84 format!(
85 "Failed to canonicalize chain spec template path {:?}",
86 chain_spec_template_zip
87 ),
88 )
89 })?;
90 Self::unzip_chainspec_template(&chain_spec_template_zip, &workdir)?;
91
92 let chainspec_source_path = Path::join(&workdir, CHAIN_SPEC_SOURCE_JSON_FILENAME);
93 let outfile_path = Path::join(&workdir, CHAIN_SPEC_FILENAME_CONVENTION);
94
95 self.fill_chain_spec(&chainspec_source_path, &outfile_path)?;
96 Ok(outfile_path)
97 }
98
99 fn fill_chain_spec(
100 &self,
101 chainspec_source_path: &Path,
102 outfile_path: &Path,
103 ) -> crate::Result<()> {
104 let template: JsonValue =
105 serde_json::from_reader(std::fs::File::open(chainspec_source_path).unwrap()).unwrap();
106 let output = self.patch_template(template)?;
107
108 std::fs::write(outfile_path, output.to_string()).map_err(|e| {
109 Error::IoError(
110 e,
111 format!("Failed to write chain spec to {:?}", outfile_path),
112 )
113 })
114 }
115
116 fn patch_template(&self, template: JsonValue) -> crate::Result<JsonValue> {
117 let mut output = template;
118 self.set_boot_nodes(&mut output)?;
119 self.set_runtime(&mut output)?;
120 Ok(output)
121 }
122
123 fn set_boot_nodes(&self, output: &mut JsonValue) -> crate::Result<()> {
124 let boot_nodes = output.try_get_mut("bootNodes")?;
125 *boot_nodes = json!([]);
126 Ok(())
127 }
128
129 fn set_runtime(&self, output: &mut JsonValue) -> crate::Result<()> {
130 let runtime = output
131 .try_get_mut("genesis")?
132 .try_get_mut("runtime")?
133 .try_as_object_mut()?;
134
135 self.insert_xandstrate_values(runtime);
136 self.insert_xandvalidators(runtime);
137 self.set_pallet_session_keys(runtime)?;
138 self.set_confidentiality(runtime)?;
139 self.set_validator_emissions(runtime)?;
140
141 Ok(())
142 }
143
144 fn insert_xandstrate_values(&self, runtime: &mut JsonMap) {
145 let encryption_keys = json!(self
146 .members
147 .iter()
148 .map(|m| json!([&m.address, &m.pub_key]))
149 .chain(
150 self.validators
151 .iter()
152 .map(|v| json!([&v.key_gen_summary.val_kp_pub, &v.key_gen_summary.pub_key]))
153 )
154 .chain(std::iter::once(json!([
155 &self.trust.address,
156 &self.trust.pub_key
157 ])))
158 .collect::<Vec<_>>());
159 let registered_members = json!(self
160 .members
161 .iter()
162 .map(|m| json!([m.address, true]))
163 .collect::<Vec<_>>());
164
165 let allow_listed_cidr_blocks = json!(self
166 .members
167 .iter()
168 .map(|m| format_cidr_blocks(&m.address.to_string(), &m.cidr_blocks))
169 .chain(self.validators.iter().map(|v| {
170 format_cidr_blocks(&v.key_gen_summary.val_kp_pub.to_string(), &v.cidr_blocks)
171 }))
172 .chain(std::iter::once(format_cidr_blocks(
173 &self.trust.address.to_string(),
174 &self.trust.cidr_blocks,
175 )))
176 .chain(std::iter::once(format_cidr_blocks(
177 &self.limited_agent.address.to_string(),
178 &self.limited_agent.cidr_blocks,
179 )))
180 .collect::<Vec<_>>());
181
182 let values = json!({
183 "bannedMembers": [],
184 "blocksUntilAutoExit": BLOCKS_UNTIL_AUTO_EXIT,
185 "limitedAgentId": self.limited_agent.address,
186 "nodeEncryptionKey": encryption_keys,
187 "registeredMembers": registered_members,
188 "trustNodeId": self.trust.address,
189 "allowListedCidrBlocks": allow_listed_cidr_blocks
190 });
191
192 runtime.insert("xandstrate".to_string(), values);
193 }
194
195 fn insert_xandvalidators(&self, runtime: &mut JsonMap) {
196 runtime.insert("xandvalidators".to_string(), json!({
197 "epochDuration": EPOCH_DURATION,
198 "validators": self.validators.iter().map(|v| v.key_gen_summary.val_kp_pub.to_string()).collect::<Vec<_>>()
199 }));
200 }
201
202 fn set_pallet_session_keys(&self, runtime: &mut JsonMap) -> crate::Result<()> {
203 let keys = runtime.try_get_mut("palletSession")?.try_get_mut("keys")?;
204
205 *keys = json!(self
206 .validators
207 .iter()
208 .map(|v| {
209 let keys = &v.key_gen_summary;
210 let authority = &keys.val_kp_pub;
211
212 json!([
213 authority.to_string(),
214 authority.to_string(),
215 {
216 "aura": &keys.produce_blocks_kp_pub.to_string(),
217 "grandpa": &keys.finalize_blocks_kp_pub.to_string(),
218 }
219 ])
220 })
221 .collect::<Vec<JsonValue>>());
222
223 Ok(())
224 }
225
226 fn set_confidentiality(&self, runtime: &mut JsonMap) -> crate::Result<()> {
227 let confidentiality = runtime.try_get_mut("confidentiality")?;
228 *confidentiality = json!({
229 "totalCreated": TOTAL_CREATED,
230 "totalRedeemed": TOTAL_REDEEMED,
231 "pendingCreateExpireTime": PENDING_CREATE_EXPIRE_TIME,
232 "usedKeyImages": [],
233 "txos": [],
234 "identityTags": [],
235 "pendingCreates": [],
236 "pendingRedeems": [],
237 "clearUtxos": [],
238 "usedCorrelationIdExpireTime": USED_CORRELATION_ID_EXPIRE_TIME,
239 });
240 Ok(())
241 }
242
243 fn set_validator_emissions(&self, runtime: &mut JsonMap) -> crate::Result<()> {
244 let emissions = runtime.try_get_mut("validatorEmissions")?;
245 *emissions = json!({
246 "emissionRateSetting": {
247 "minor_units_per_emission": self.validator_emission_rate.minor_units_per_validator_emission,
248 "block_quota": self.validator_emission_rate.block_quota,
249 }
250 });
251 Ok(())
252 }
253}
254
255fn format_cidr_blocks(address: &str, cidr_blocks: &[EgressIpRange]) -> JsonValue {
256 const MAX_CIDR_BLOCKS: usize = 10;
258
259 if cidr_blocks.len() > MAX_CIDR_BLOCKS {
260 panic!(
261 "Chainspec only supports allowlisting up to {} CIDR blocks per network participant",
262 MAX_CIDR_BLOCKS
263 );
264 }
265
266 let cidr_arrays = cidr_blocks
267 .iter()
268 .map(EgressIpRange::as_allowlist_json)
269 .chain(std::iter::repeat(JsonValue::Null))
271 .take(MAX_CIDR_BLOCKS)
272 .collect::<Vec<_>>();
273
274 json!([address, cidr_arrays])
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::cli::commands::generate::config::{ConfigEmissionRate, ConfigurationFile};
281 use std::fs;
282 use std::num::NonZeroU64;
283 use temp_dir::TempDir;
284
285 use insta::assert_json_snapshot;
286
287 #[derive(Default)]
288 struct TestScenario {
289 pub emssion_rate: ConfigEmissionRate,
290 }
291
292 impl TestScenario {
293 pub fn with_validator_emissions(
294 minor_units_per_validator_emission: u64,
295 block_quota: NonZeroU64,
296 ) -> Self {
297 Self {
298 emssion_rate: ConfigEmissionRate {
299 minor_units_per_validator_emission,
300 block_quota,
301 },
302 }
303 }
304 }
305
306 fn chainspec_from_sample_config(test_scenario: Option<&TestScenario>) -> ChainSpec {
307 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample_config.yaml");
308 let file = std::fs::File::open(path).unwrap();
309
310 let config = if let Some(test_scenario) = test_scenario {
311 ConfigurationFile {
312 validator_emission_rate: test_scenario.emssion_rate.clone(),
313 ..serde_yaml::from_reader(file).unwrap()
314 }
315 } else {
316 ConfigurationFile {
317 ..serde_yaml::from_reader(file).unwrap()
318 }
319 };
320
321 config.build_chain_spec_data().unwrap()
322 }
323
324 fn snapshot_generated_output(test_scenario: Option<&TestScenario>) {
325 let chainspec = chainspec_from_sample_config(test_scenario);
326
327 let version = "16.0.0";
328 let template_path: PathBuf = Path::new(&format!(
329 "{}/.test-assets/chain-spec-template.{}.zip",
330 env!("CARGO_MANIFEST_DIR"),
331 version
332 ))
333 .into();
334 let tempdir = TempDir::new().unwrap();
335 let temp_target = tempdir.path().into();
336 let template_path = chainspec.generate_into(template_path, temp_target).unwrap();
337 let template = fs::read_to_string(template_path).unwrap();
338 let as_json: JsonValue = serde_json::from_str(&template).unwrap();
339 assert_json_snapshot!(as_json, {".genesis.runtime.system.code" => "[blob]"});
340 }
341
342 #[test]
343 fn generate_with_validator_emissions_disabled() {
344 let zero_emission_scenario =
345 TestScenario::with_validator_emissions(0, 1.try_into().unwrap());
346 snapshot_generated_output(Some(&zero_emission_scenario));
347 }
348
349 #[test]
350 fn generate_with_validator_emissions_enabled() {
351 snapshot_generated_output(None);
352 }
353}