Skip to main content

zombienet_orchestrator/generators/
jam_config.rs

1use configuration::types::JamNodeMode;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    generators::{chain_spec::merge, errors::GeneratorError},
6    network_spec::jamchain::JamchainSpec,
7};
8
9/// Generate config file used to get the chain-spec
10/// Using this json format:
11/// {
12//     "id": "dev",
13//     "genesis_validators": [
14//         {
15//             "peer_id": "dev:0",
16//             "bandersnatch": "dev:0",
17//             "net_addr": "127.0.0.1:40000"
18//         },
19//         {
20//             "peer_id": "dev:1",
21//             "bandersnatch": "dev:1",
22//             "net_addr": "127.0.0.1:40001"
23//         },
24//         {
25//             "peer_id": "dev:2",
26//             "bandersnatch": "dev:2",
27//             "net_addr": "127.0.0.1:40002"
28//         },
29//         {
30//             "peer_id": "dev:3",
31//             "bandersnatch": "dev:3",
32//             "net_addr": "127.0.0.1:40003"
33//         },
34//         {
35//             "peer_id": "dev:4",
36//             "bandersnatch": "dev:4",
37//             "net_addr": "127.0.0.1:40004"
38//         },
39//         {
40//             "peer_id": "dev:5",
41//             "bandersnatch": "dev:5",
42//             "net_addr": "127.0.0.1:40005"
43//         }
44//     ]
45// }
46///
47
48#[derive(Debug, Clone, Default, Serialize, Deserialize)]
49pub struct GenesisValidator {
50    pub peer_id: String,
51    pub bandersnatch: String,
52    pub net_addr: String,
53}
54
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct GenesisConfig {
57    pub id: String,
58    pub genesis_validators: Vec<GenesisValidator>,
59    /// Whatever the genesis overrides add beyond the generated keys, passed to `gen-spec` as is.
60    #[serde(flatten)]
61    pub overrides: serde_json::Map<String, serde_json::Value>,
62}
63
64pub fn generate(jam_spec: &JamchainSpec) -> Result<GenesisConfig, GeneratorError> {
65    let genesis_validators = jam_spec
66        .nodes
67        .iter()
68        .filter_map(|n| {
69            if let JamNodeMode::Validator = n.mode {
70                Some(GenesisValidator {
71                    peer_id: n.peer_id.clone(),
72                    bandersnatch: n
73                        .accounts
74                        .accounts
75                        .get("bandersnatch")
76                        .expect("bandersnatch should be present.")
77                        .public_key
78                        .clone(),
79                    // The genesis validator set doubles as the p2p address book, so this
80                    // must be the port the node actually listens on (`--port`), not the
81                    // rpc one.
82                    net_addr: format!("127.0.0.1:{}", n.port.0),
83                })
84            } else {
85                None
86            }
87        })
88        .collect();
89
90    let gen_config = GenesisConfig {
91        id: jam_spec.id.as_str().into(),
92        genesis_validators,
93        overrides: Default::default(),
94    };
95
96    let Some(overrides) = &jam_spec.genesis_overrides else {
97        return Ok(gen_config);
98    };
99    // Merged as JSON and read back, so the generated keys keep their place in the file.
100    let mut merged = serde_json::to_value(gen_config).map_err(encode_error)?;
101    merge(&mut merged, overrides);
102    serde_json::from_value(merged).map_err(encode_error)
103}
104
105fn encode_error(error: serde_json::Error) -> GeneratorError {
106    GeneratorError::EncodeDecodeError(error.to_string())
107}
108
109#[cfg(test)]
110mod tests {
111    use configuration::types::Chain;
112
113    use super::*;
114    use crate::{
115        network_spec::jamnode::JamNodeSpec,
116        shared::types::{NodeAccount, NodeAccounts, ParkedPort},
117    };
118
119    fn jam_node(name: &str, mode: JamNodeMode, p2p_port: u16, rpc_port: u16) -> JamNodeSpec {
120        JamNodeSpec {
121            name: name.to_string(),
122            mode,
123            peer_id: format!("peer-{name}"),
124            accounts: NodeAccounts {
125                seed: name.to_string(),
126                accounts: [(
127                    "bandersnatch".to_string(),
128                    NodeAccount::new(format!("addr-{name}"), format!("pub-{name}")),
129                )]
130                .into(),
131            },
132            port: ParkedPort(p2p_port, Default::default()),
133            rpc_port: ParkedPort(rpc_port, Default::default()),
134            ..Default::default()
135        }
136    }
137
138    fn jam_spec(nodes: Vec<JamNodeSpec>) -> JamchainSpec {
139        JamchainSpec {
140            id: Chain::try_from("dev").unwrap(),
141            default_command: None,
142            default_image: None,
143            default_resources: None,
144            default_args: vec![],
145            chain_spec_command: String::new(),
146            nodes,
147            genesis_overrides: None,
148        }
149    }
150
151    /// Exactly what `serde_json::to_string_pretty` wrote for a one-validator network before the
152    /// overrides existed. Every existing user's `jam_config.json` is this file.
153    const ALICE_ALONE: &str = r#"{
154  "id": "dev",
155  "genesis_validators": [
156    {
157      "peer_id": "peer-alice",
158      "bandersnatch": "pub-alice",
159      "net_addr": "127.0.0.1:40001"
160    }
161  ]
162}"#;
163
164    fn alice_alone(genesis_overrides: Option<serde_json::Value>) -> JamchainSpec {
165        JamchainSpec {
166            genesis_overrides,
167            ..jam_spec(vec![jam_node("alice", JamNodeMode::Validator, 40001, 9944)])
168        }
169    }
170
171    fn written(spec: &JamchainSpec) -> String {
172        serde_json::to_string_pretty(&generate(spec).unwrap()).unwrap()
173    }
174
175    /// The overrides are how a caller puts services, authorizer queues and privileges into
176    /// genesis; the generator knows nothing about those keys and must pass every one of them
177    /// through, next to what it generated itself.
178    #[test]
179    fn genesis_overrides_are_added_to_the_generated_config() {
180        let overrides = serde_json::json!({
181            "services": [{
182                "id": 5,
183                "code": "/blobs/parasim-service.jam",
184                "balance": "18446744073709551615",
185            }],
186            "auth_queues": { "0": "2bbda8cb" },
187            "assigners": { "0": 5 },
188            "privileges": { "bless": 0, "assign": { "0": 5 } },
189        });
190
191        let config: serde_json::Value =
192            serde_json::from_str(&written(&alice_alone(Some(overrides.clone())))).unwrap();
193
194        for key in ["services", "auth_queues", "assigners", "privileges"] {
195            assert_eq!(
196                config[key], overrides[key],
197                "{key} did not reach the config"
198            );
199        }
200        assert_eq!(config["id"], "dev");
201        assert_eq!(
202            config["genesis_validators"][0]["net_addr"],
203            "127.0.0.1:40001"
204        );
205    }
206
207    /// Nobody who does not use the overrides should see their file change — not even in key
208    /// order, which is what a naive detour through `serde_json::Value` would reshuffle.
209    #[test]
210    fn without_overrides_the_config_is_written_exactly_as_before() {
211        assert_eq!(written(&alice_alone(None)), ALICE_ALONE);
212        assert_eq!(
213            written(&alice_alone(Some(serde_json::json!({})))),
214            ALICE_ALONE
215        );
216    }
217
218    // The nodes dial each other using the addresses in the genesis validator set, so
219    // pointing them at the rpc port silently splits the network.
220    #[test]
221    fn genesis_validators_are_addressed_by_their_p2p_port() {
222        let spec = jam_spec(vec![
223            jam_node("alice", JamNodeMode::Validator, 40001, 9944),
224            jam_node("bob", JamNodeMode::Validator, 40002, 9945),
225        ]);
226
227        let config = generate(&spec).unwrap();
228
229        let addrs: Vec<&str> = config
230            .genesis_validators
231            .iter()
232            .map(|v| v.net_addr.as_str())
233            .collect();
234        assert_eq!(addrs, vec!["127.0.0.1:40001", "127.0.0.1:40002"]);
235    }
236
237    #[test]
238    fn only_validators_are_included_in_genesis() {
239        let spec = jam_spec(vec![
240            jam_node("alice", JamNodeMode::Validator, 40001, 9944),
241            jam_node("dave", JamNodeMode::Ordinary, 40002, 9945),
242            jam_node("eve", JamNodeMode::Proxy, 40003, 9946),
243        ]);
244
245        let config = generate(&spec).unwrap();
246
247        assert_eq!(config.id, "dev");
248        assert_eq!(config.genesis_validators.len(), 1);
249        assert_eq!(config.genesis_validators[0].peer_id, "peer-alice");
250    }
251}