1use std::{cell::RefCell, error::Error, fmt::Debug, marker::PhantomData, rc::Rc};
2
3use serde::{Deserialize, Serialize};
4use support::constants::{DEFAULT_TYPESTATE, THIS_IS_A_BUG};
5
6use crate::{
7 shared::{
8 errors::{ConfigError, FieldError},
9 helpers::{merge_errors, merge_errors_vecs},
10 macros::states,
11 node::{self, JamNodeConfig, JamNodeConfigBuilder},
12 resources::{Resources, ResourcesBuilder},
13 types::{
14 Arg, AssetLocation, Chain, ChainDefaultContext, Command, Image, ValidationContext,
15 },
16 },
17 types::{JamNodeMode, JamProtocolParameterType},
18 utils::{default_chain_jam, default_command_jam, default_protocol_para_type_jam},
19};
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct JamchainConfig {
23 #[serde(default = "default_protocol_para_type_jam")]
25 protocol_params_type: JamProtocolParameterType,
26 #[serde(default = "default_chain_jam")]
28 id: Chain,
29 #[serde(default = "default_command_jam")]
31 default_command: Option<Command>,
32 default_image: Option<Image>,
34 default_resources: Option<Resources>,
36 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
38 default_args: Vec<Arg>,
39 chain_spec_path: Option<AssetLocation>,
41 chain_spec_command: Option<Command>,
42 corevm_monitor_command: Option<Command>,
44 corevm_builder_command: Option<Command>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 genesis_overrides: Option<serde_json::Value>,
49 #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
51 nodes: Vec<JamNodeConfig>,
52}
53
54impl JamchainConfig {
55 pub fn id(&self) -> &Chain {
57 &self.id
58 }
59
60 pub fn protocol_params_type(&self) -> &JamProtocolParameterType {
62 &self.protocol_params_type
63 }
64
65 pub fn default_command(&self) -> Option<&Command> {
67 self.default_command.as_ref()
68 }
69
70 pub fn default_image(&self) -> Option<&Image> {
72 self.default_image.as_ref()
73 }
74
75 pub fn default_resources(&self) -> Option<&Resources> {
77 self.default_resources.as_ref()
78 }
79
80 pub fn default_args(&self) -> Vec<&Arg> {
82 self.default_args.iter().collect::<Vec<&Arg>>()
83 }
84
85 pub fn chain_spec_path(&self) -> Option<&AssetLocation> {
87 self.chain_spec_path.as_ref()
88 }
89
90 pub fn chain_spec_command(&self) -> Option<&Command> {
92 self.chain_spec_command.as_ref()
93 }
94
95 pub fn corevm_monitor_command(&self) -> Option<&Command> {
97 self.corevm_monitor_command.as_ref()
98 }
99
100 pub fn corevm_builder_command(&self) -> Option<&Command> {
102 self.corevm_builder_command.as_ref()
103 }
104
105 pub fn genesis_overrides(&self) -> Option<&serde_json::Value> {
107 self.genesis_overrides.as_ref()
108 }
109
110 pub fn nodes(&self) -> Vec<&JamNodeConfig> {
112 self.nodes.iter().collect::<Vec<&JamNodeConfig>>()
113 }
114}
115
116states! {
117 Initial,
118 WithId,
119 WithAtLeastOneNode
120}
121
122pub struct JamchainConfigBuilder<State> {
124 config: JamchainConfig,
125 validation_context: Rc<RefCell<ValidationContext>>,
126 errors: Vec<anyhow::Error>,
127 _state: PhantomData<State>,
128}
129
130impl Default for JamchainConfigBuilder<Initial> {
131 fn default() -> Self {
132 Self {
133 config: JamchainConfig {
134 protocol_params_type: JamProtocolParameterType::Tiny,
135 id: "dev"
136 .try_into()
137 .expect(&format!("{DEFAULT_TYPESTATE} {THIS_IS_A_BUG}")),
138 default_command: Some(
139 "polkajam"
140 .try_into()
141 .expect(&format!("{DEFAULT_TYPESTATE} {THIS_IS_A_BUG}")),
142 ),
143 default_image: None,
144 default_resources: None,
145 default_args: vec![],
146 chain_spec_path: None,
147 chain_spec_command: None,
148 corevm_monitor_command: None,
149 corevm_builder_command: None,
150 genesis_overrides: None,
151 nodes: vec![],
152 },
153 validation_context: Default::default(),
154 errors: vec![],
155 _state: PhantomData,
156 }
157 }
158}
159
160impl<A> JamchainConfigBuilder<A> {
161 fn transition<B>(
162 config: JamchainConfig,
163 validation_context: Rc<RefCell<ValidationContext>>,
164 errors: Vec<anyhow::Error>,
165 ) -> JamchainConfigBuilder<B> {
166 JamchainConfigBuilder {
167 config,
168 validation_context,
169 errors,
170 _state: PhantomData,
171 }
172 }
173
174 fn default_chain_context(&self) -> ChainDefaultContext {
175 ChainDefaultContext {
176 default_command: self.config.default_command.clone(),
177 default_image: self.config.default_image.clone(),
178 default_resources: self.config.default_resources.clone(),
179 default_db_snapshot: None,
180 default_args: self.config.default_args.clone(),
181 }
182 }
183
184 fn create_node_builder<F>(&self, f: F) -> JamNodeConfigBuilder<node::Buildable>
185 where
186 F: FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
187 {
188 f(JamNodeConfigBuilder::new(
189 self.default_chain_context(),
190 self.validation_context.clone(),
191 ))
192 }
193}
194
195impl JamchainConfigBuilder<Initial> {
196 pub fn new(
197 validation_context: Rc<RefCell<ValidationContext>>,
198 ) -> JamchainConfigBuilder<Initial> {
199 Self {
200 validation_context,
201 ..Self::default()
202 }
203 }
204
205 pub fn with_id<T>(self, chain: T) -> JamchainConfigBuilder<WithId>
207 where
208 T: TryInto<Chain>,
209 T::Error: Error + Send + Sync + 'static,
210 {
211 match chain.try_into() {
212 Ok(id) => Self::transition(
213 JamchainConfig { id, ..self.config },
214 self.validation_context,
215 self.errors,
216 ),
217 Err(error) => Self::transition(
218 self.config,
219 self.validation_context,
220 merge_errors(self.errors, FieldError::Chain(error.into()).into()),
221 ),
222 }
223 }
224}
225
226impl JamchainConfigBuilder<WithId> {
227 pub fn with_default_command<T>(self, command: T) -> Self
229 where
230 T: TryInto<Command>,
231 T::Error: Error + Send + Sync + 'static,
232 {
233 match command.try_into() {
234 Ok(command) => Self::transition(
235 JamchainConfig {
236 default_command: Some(command),
237 ..self.config
238 },
239 self.validation_context,
240 self.errors,
241 ),
242 Err(error) => Self::transition(
243 self.config,
244 self.validation_context,
245 merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
246 ),
247 }
248 }
249
250 pub fn with_default_image<T>(self, image: T) -> Self
252 where
253 T: TryInto<Image>,
254 T::Error: Error + Send + Sync + 'static,
255 {
256 match image.try_into() {
257 Ok(image) => Self::transition(
258 JamchainConfig {
259 default_image: Some(image),
260 ..self.config
261 },
262 self.validation_context,
263 self.errors,
264 ),
265 Err(error) => Self::transition(
266 self.config,
267 self.validation_context,
268 merge_errors(self.errors, FieldError::DefaultImage(error.into()).into()),
269 ),
270 }
271 }
272
273 pub fn with_default_resources(
275 self,
276 f: impl FnOnce(ResourcesBuilder) -> ResourcesBuilder,
277 ) -> Self {
278 match f(ResourcesBuilder::new()).build() {
279 Ok(default_resources) => Self::transition(
280 JamchainConfig {
281 default_resources: Some(default_resources),
282 ..self.config
283 },
284 self.validation_context,
285 self.errors,
286 ),
287 Err(errors) => Self::transition(
288 self.config,
289 self.validation_context,
290 merge_errors_vecs(
291 self.errors,
292 errors
293 .into_iter()
294 .map(|error| FieldError::DefaultResources(error).into())
295 .collect::<Vec<_>>(),
296 ),
297 ),
298 }
299 }
300
301 pub fn with_default_args(self, args: Vec<Arg>) -> Self {
303 Self::transition(
304 JamchainConfig {
305 default_args: args,
306 ..self.config
307 },
308 self.validation_context,
309 self.errors,
310 )
311 }
312
313 pub fn with_chain_spec_path(self, location: impl Into<AssetLocation>) -> Self {
315 Self::transition(
316 JamchainConfig {
317 chain_spec_path: Some(location.into()),
318 ..self.config
319 },
320 self.validation_context,
321 self.errors,
322 )
323 }
324
325 pub fn with_chain_spec_command<T>(self, command: T) -> Self
329 where
330 T: TryInto<Command>,
331 T::Error: Error + Send + Sync + 'static,
332 {
333 match command.try_into() {
334 Ok(command) => Self::transition(
335 JamchainConfig {
336 chain_spec_command: Some(command),
337 ..self.config
338 },
339 self.validation_context,
340 self.errors,
341 ),
342 Err(error) => Self::transition(
343 self.config,
344 self.validation_context,
345 merge_errors(self.errors, FieldError::Command(error.into()).into()),
346 ),
347 }
348 }
349
350 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
353 Self::transition(
354 JamchainConfig {
355 genesis_overrides: Some(genesis_overrides.into()),
356 ..self.config
357 },
358 self.validation_context,
359 self.errors,
360 )
361 }
362
363 pub fn with_corevm_monitor_command<T>(self, command: T) -> Self
365 where
366 T: TryInto<Command>,
367 T::Error: Error + Send + Sync + 'static,
368 {
369 match command.try_into() {
370 Ok(command) => Self::transition(
371 JamchainConfig {
372 corevm_monitor_command: Some(command),
373 ..self.config
374 },
375 self.validation_context,
376 self.errors,
377 ),
378 Err(error) => Self::transition(
379 self.config,
380 self.validation_context,
381 merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
382 ),
383 }
384 }
385
386 pub fn with_corevm_builder_command<T>(self, command: T) -> Self
388 where
389 T: TryInto<Command>,
390 T::Error: Error + Send + Sync + 'static,
391 {
392 match command.try_into() {
393 Ok(command) => Self::transition(
394 JamchainConfig {
395 corevm_builder_command: Some(command),
396 ..self.config
397 },
398 self.validation_context,
399 self.errors,
400 ),
401 Err(error) => Self::transition(
402 self.config,
403 self.validation_context,
404 merge_errors(self.errors, FieldError::DefaultCommand(error.into()).into()),
405 ),
406 }
407 }
408
409 pub fn with_validator(
412 self,
413 f: impl FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
414 ) -> JamchainConfigBuilder<WithAtLeastOneNode> {
415 match self
416 .create_node_builder(f)
417 .with_mode(JamNodeMode::Validator)
418 .build()
419 {
420 Ok(node) => Self::transition(
421 JamchainConfig {
422 nodes: [self.config.nodes, vec![node]].concat(),
423 ..self.config
424 },
425 self.validation_context,
426 self.errors,
427 ),
428 Err((name, errors)) => Self::transition(
429 self.config,
430 self.validation_context,
431 merge_errors_vecs(
432 self.errors,
433 errors
434 .into_iter()
435 .map(|error| ConfigError::Node(name.clone(), error).into())
436 .collect::<Vec<_>>(),
437 ),
438 ),
439 }
440 }
441
442 pub fn with_ordinary(
444 self,
445 f: impl FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
446 ) -> JamchainConfigBuilder<WithAtLeastOneNode> {
447 match self
448 .create_node_builder(f)
449 .with_mode(JamNodeMode::Ordinary)
450 .build()
451 {
452 Ok(node) => Self::transition(
453 JamchainConfig {
454 nodes: [self.config.nodes, vec![node]].concat(),
455 ..self.config
456 },
457 self.validation_context,
458 self.errors,
459 ),
460 Err((name, errors)) => Self::transition(
461 self.config,
462 self.validation_context,
463 merge_errors_vecs(
464 self.errors,
465 errors
466 .into_iter()
467 .map(|error| ConfigError::Node(name.clone(), error).into())
468 .collect::<Vec<_>>(),
469 ),
470 ),
471 }
472 }
473
474 pub fn with_proxy(
476 self,
477 f: impl FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
478 ) -> JamchainConfigBuilder<WithAtLeastOneNode> {
479 match self
480 .create_node_builder(f)
481 .with_mode(JamNodeMode::Proxy)
482 .build()
483 {
484 Ok(node) => Self::transition(
485 JamchainConfig {
486 nodes: [self.config.nodes, vec![node]].concat(),
487 ..self.config
488 },
489 self.validation_context,
490 self.errors,
491 ),
492 Err((name, errors)) => Self::transition(
493 self.config,
494 self.validation_context,
495 merge_errors_vecs(
496 self.errors,
497 errors
498 .into_iter()
499 .map(|error| ConfigError::Node(name.clone(), error).into())
500 .collect::<Vec<_>>(),
501 ),
502 ),
503 }
504 }
505
506 }
527
528impl JamchainConfigBuilder<WithAtLeastOneNode> {
529 pub fn with_genesis_overrides(self, genesis_overrides: impl Into<serde_json::Value>) -> Self {
532 Self::transition(
533 JamchainConfig {
534 genesis_overrides: Some(genesis_overrides.into()),
535 ..self.config
536 },
537 self.validation_context,
538 self.errors,
539 )
540 }
541
542 pub fn with_validator(
545 self,
546 f: impl FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
547 ) -> JamchainConfigBuilder<WithAtLeastOneNode> {
548 match self
549 .create_node_builder(f)
550 .with_mode(JamNodeMode::Validator)
551 .build()
552 {
553 Ok(node) => Self::transition(
554 JamchainConfig {
555 nodes: [self.config.nodes, vec![node]].concat(),
556 ..self.config
557 },
558 self.validation_context,
559 self.errors,
560 ),
561 Err((name, errors)) => Self::transition(
562 self.config,
563 self.validation_context,
564 merge_errors_vecs(
565 self.errors,
566 errors
567 .into_iter()
568 .map(|error| ConfigError::Node(name.clone(), error).into())
569 .collect::<Vec<_>>(),
570 ),
571 ),
572 }
573 }
574
575 pub fn with_ordinary(
577 self,
578 f: impl FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
579 ) -> JamchainConfigBuilder<WithAtLeastOneNode> {
580 match self
581 .create_node_builder(f)
582 .with_mode(JamNodeMode::Ordinary)
583 .build()
584 {
585 Ok(node) => Self::transition(
586 JamchainConfig {
587 nodes: [self.config.nodes, vec![node]].concat(),
588 ..self.config
589 },
590 self.validation_context,
591 self.errors,
592 ),
593 Err((name, errors)) => Self::transition(
594 self.config,
595 self.validation_context,
596 merge_errors_vecs(
597 self.errors,
598 errors
599 .into_iter()
600 .map(|error| ConfigError::Node(name.clone(), error).into())
601 .collect::<Vec<_>>(),
602 ),
603 ),
604 }
605 }
606
607 pub fn with_proxy(
609 self,
610 f: impl FnOnce(JamNodeConfigBuilder<node::Initial>) -> JamNodeConfigBuilder<node::Buildable>,
611 ) -> JamchainConfigBuilder<WithAtLeastOneNode> {
612 match self
613 .create_node_builder(f)
614 .with_mode(JamNodeMode::Proxy)
615 .build()
616 {
617 Ok(node) => Self::transition(
618 JamchainConfig {
619 nodes: [self.config.nodes, vec![node]].concat(),
620 ..self.config
621 },
622 self.validation_context,
623 self.errors,
624 ),
625 Err((name, errors)) => Self::transition(
626 self.config,
627 self.validation_context,
628 merge_errors_vecs(
629 self.errors,
630 errors
631 .into_iter()
632 .map(|error| ConfigError::Node(name.clone(), error).into())
633 .collect::<Vec<_>>(),
634 ),
635 ),
636 }
637 }
638
639 pub fn build(self) -> Result<JamchainConfig, Vec<anyhow::Error>> {
641 if !self.errors.is_empty() {
642 return Err(self
643 .errors
644 .into_iter()
645 .map(|error| ConfigError::Jamchain(error).into())
646 .collect::<Vec<_>>());
647 }
648
649 Ok(self.config)
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use serde_json::json;
656
657 use super::*;
658
659 fn overrides() -> serde_json::Value {
660 json!({
661 "services": [{ "id": 5, "code": "/blobs/parasim-service.jam" }],
662 "assigners": { "0": 5 },
663 })
664 }
665
666 #[test]
669 fn genesis_overrides_are_kept_as_given() {
670 let config = JamchainConfigBuilder::new(Default::default())
671 .with_id("dev")
672 .with_genesis_overrides(overrides())
673 .with_validator(|node| node.with_name("jam0"))
674 .build()
675 .expect("the chain config builds");
676
677 assert_eq!(config.genesis_overrides(), Some(&overrides()));
678 }
679
680 #[test]
682 fn genesis_overrides_can_follow_the_nodes() {
683 let config = JamchainConfigBuilder::new(Default::default())
684 .with_id("dev")
685 .with_validator(|node| node.with_name("jam0"))
686 .with_genesis_overrides(overrides())
687 .build()
688 .expect("the chain config builds");
689
690 assert_eq!(config.genesis_overrides(), Some(&overrides()));
691 }
692
693 #[test]
694 fn genesis_overrides_are_absent_by_default() {
695 let config = JamchainConfigBuilder::new(Default::default())
696 .with_id("dev")
697 .with_validator(|node| node.with_name("jam0"))
698 .build()
699 .expect("the chain config builds");
700
701 assert_eq!(config.genesis_overrides(), None);
702 }
703}