1use crate::test_environment::{
2 DBWrapper, mock_genesis_root_contract, test_network_hash,
3};
4use std::cmp::Ordering;
5use std::collections::BTreeMap;
6use std::sync::OnceLock;
7use thruput_xdr::network_config::{
8 ConfigParam, ConfigParamID, NetworkConfig, NetworkConfigV0,
9};
10
11pub fn stock_network_config() -> NetworkConfig {
22 static STOCK: OnceLock<NetworkConfig> = OnceLock::new();
23 STOCK
24 .get_or_init(|| {
25 let db = DBWrapper::new(
26 None,
27 &test_network_hash(),
28 &mock_genesis_root_contract(),
29 None,
30 );
31 db.get_network_config()
32 })
33 .clone()
34}
35
36pub fn config_params(config: &NetworkConfig) -> &[ConfigParam] {
42 match config {
43 NetworkConfig::_0 { v0 } => &v0.params.0,
44 other => panic!(
45 "unsupported network configuration version: {other:?}\n\
46 This build of the SDK only knows NetworkConfig::_0."
47 ),
48 }
49}
50
51pub fn find_config_param(
70 config: &NetworkConfig,
71 param: ConfigParamID,
72) -> Option<&ConfigParam> {
73 config_params(config).iter().find(|p| p.param() == param)
74}
75
76#[derive(Clone, Debug, Default)]
102#[must_use = "a NetworkConfigBuilder does nothing until `build` is called"]
103pub struct NetworkConfigBuilder {
104 base: Option<NetworkConfig>,
105 overrides: BTreeMap<ConfigParamID, ConfigParam>,
106}
107
108impl NetworkConfigBuilder {
109 pub fn new() -> Self {
111 Self::default()
112 }
113
114 pub fn with_base(mut self, base: NetworkConfig) -> Self {
122 self.base = Some(base);
123 self
124 }
125
126 pub fn with_config_param(mut self, param: ConfigParam) -> Self {
131 self.overrides.insert(param.param(), param);
132 self
133 }
134
135 pub fn with_max_sha256_buffers(self, max_sha256_buffers: u32) -> Self {
138 self.with_config_param(ConfigParam::kConfigParamMaxSHA256Buffers {
139 max_sha256_buffers,
140 })
141 }
142
143 pub fn with_max_cross_invoke_depth(
151 self,
152 max_cross_invoke_depth: u32,
153 ) -> Self {
154 self.with_config_param(ConfigParam::kConfigParamMaxCrossInvokeDepth {
155 max_cross_invoke_depth,
156 })
157 }
158
159 pub fn with_max_new_contracts_per_tx(
161 self,
162 max_new_contracts_per_tx: u32,
163 ) -> Self {
164 self.with_config_param(ConfigParam::kConfigParamMaxNewContractsPerTx {
165 max_new_contracts_per_tx,
166 })
167 }
168
169 pub fn with_max_bytestring_len(self, max_bytestring_len: u32) -> Self {
171 self.with_config_param(ConfigParam::kConfigParamMaxBytestringLen {
172 max_bytestring_len,
173 })
174 }
175
176 pub fn with_max_contract_size_bytes(
179 self,
180 max_contract_size_bytes: u32,
181 ) -> Self {
182 self.with_config_param(ConfigParam::kConfigParamMaxContractSizeBytes {
183 max_contract_size_bytes,
184 })
185 }
186
187 pub fn with_max_savepoint_depth(self, max_savepoint_depth: u32) -> Self {
191 self.with_config_param(ConfigParam::kConfigParamMaxSavepointDepth {
192 max_savepoint_depth,
193 })
194 }
195
196 pub fn with_max_sha256_buffer_bytes(
200 self,
201 max_sha256_buffer_bytes: u32,
202 ) -> Self {
203 self.with_config_param(ConfigParam::kConfigParamMaxSHA256BufferBytes {
204 max_sha256_buffer_bytes,
205 })
206 }
207
208 pub fn with_max_auth_token_depth(self, max_auth_token_depth: u32) -> Self {
211 self.with_config_param(ConfigParam::kConfigParamMaxAuthTokenDepth {
212 max_auth_token_depth,
213 })
214 }
215
216 pub fn with_max_rv32_code_bytes(self, max_rv32_code_bytes: u32) -> Self {
219 self.with_config_param(ConfigParam::kConfigParamMaxRv32CodeBytes {
220 max_rv32_code_bytes,
221 })
222 }
223
224 pub fn with_max_rv32_memory_bytes(
227 self,
228 max_rv32_memory_bytes: u32,
229 ) -> Self {
230 self.with_config_param(ConfigParam::kConfigParamMaxRv32MemoryBytes {
231 max_rv32_memory_bytes,
232 })
233 }
234
235 pub fn is_stock(&self) -> bool {
238 self.base.is_none() && self.overrides.is_empty()
239 }
240
241 pub fn build(self) -> NetworkConfig {
248 let base = self.base.unwrap_or_else(stock_network_config);
249 let mut params = config_params(&base).to_vec();
250
251 for (param_id, param) in self.overrides {
254 let id = param_id.0;
255 let index = usize::try_from(id).unwrap_or_else(|_| {
256 panic!("network configuration parameter id {id} is negative")
257 });
258 match index.cmp(¶ms.len()) {
259 Ordering::Less => params[index] = param,
260 Ordering::Equal => params.push(param),
261 Ordering::Greater => panic!(
262 "cannot state {} (id {id}): the configuration states \
263 {} parameter(s), so this would leave ids {}..{id} \
264 unstated, and the list must be a gapless prefix of \
265 the id space. State the parameters in between too -- \
266 or, if this hogd build does not know id {id} at all, \
267 drop the override.",
268 param.param(),
269 params.len(),
270 params.len(),
271 ),
272 }
273 }
274
275 for (index, param) in params.iter().enumerate() {
276 assert_eq!(
277 param.param().0,
278 index as i32,
279 "network configuration parameters must be a gapless prefix \
280 of the id space in id order, but position {index} states \
281 {}. hogd rejects any other arrangement (fatally, from \
282 inside the FFI).",
283 param.param(),
284 );
285 }
286
287 NetworkConfig::_0 {
288 v0: NetworkConfigV0 {
289 params: params.try_into().expect(
290 "the parameter list cannot exceed the XDR length bound",
291 ),
292 },
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 fn config_of(params: Vec<ConfigParam>) -> NetworkConfig {
302 NetworkConfig::_0 {
303 v0: NetworkConfigV0 {
304 params: params.try_into().unwrap(),
305 },
306 }
307 }
308
309 #[test]
310 fn the_stock_config_states_a_gapless_prefix_of_the_id_space() {
311 let stock = stock_network_config();
312 let params = config_params(&stock);
313
314 assert!(!params.is_empty(), "a chain states its configuration");
315 for (index, param) in params.iter().enumerate() {
316 assert_eq!(param.param().0, index as i32);
317 }
318 }
319
320 #[test]
321 fn a_builder_with_no_overrides_is_the_stock_config() {
322 let builder = NetworkConfigBuilder::new();
323 assert!(builder.is_stock());
324 assert_eq!(builder.build(), stock_network_config());
325 }
326
327 #[test]
328 fn setting_a_parameter_leaves_the_others_at_their_stock_values() {
329 let stock = stock_network_config();
330 let built = NetworkConfigBuilder::new()
331 .with_max_sha256_buffers(1)
332 .build();
333
334 assert_eq!(
335 find_config_param(
336 &built,
337 ConfigParamID::kConfigParamMaxSHA256Buffers
338 ),
339 Some(&ConfigParam::kConfigParamMaxSHA256Buffers {
340 max_sha256_buffers: 1
341 }),
342 );
343 assert_eq!(&config_params(&built)[1..], &config_params(&stock)[1..],);
344 }
345
346 #[test]
347 fn the_last_setter_for_a_parameter_wins() {
348 let built = NetworkConfigBuilder::new()
349 .with_max_sha256_buffers(1)
350 .with_max_sha256_buffers(4)
351 .build();
352
353 assert_eq!(
354 find_config_param(
355 &built,
356 ConfigParamID::kConfigParamMaxSHA256Buffers
357 ),
358 Some(&ConfigParam::kConfigParamMaxSHA256Buffers {
359 max_sha256_buffers: 4
360 }),
361 );
362 }
363
364 #[test]
365 fn overrides_apply_to_an_explicit_base() {
366 let base = config_of(vec![
367 ConfigParam::kConfigParamMaxSHA256Buffers {
368 max_sha256_buffers: 7,
369 },
370 ConfigParam::kConfigParamMaxCrossInvokeDepth {
371 max_cross_invoke_depth: 9,
372 },
373 ]);
374 let built = NetworkConfigBuilder::new()
375 .with_base(base)
376 .with_max_cross_invoke_depth(3)
377 .build();
378
379 assert_eq!(
380 config_params(&built),
381 [
382 ConfigParam::kConfigParamMaxSHA256Buffers {
383 max_sha256_buffers: 7
384 },
385 ConfigParam::kConfigParamMaxCrossInvokeDepth {
386 max_cross_invoke_depth: 3
387 },
388 ],
389 );
390 }
391
392 #[test]
393 fn every_typed_setter_states_its_own_parameter() {
394 let built = NetworkConfigBuilder::new()
398 .with_base(config_of(vec![]))
399 .with_max_sha256_buffers(1)
400 .with_max_cross_invoke_depth(2)
401 .with_max_new_contracts_per_tx(3)
402 .with_max_bytestring_len(4)
403 .with_max_contract_size_bytes(5)
404 .with_max_savepoint_depth(6)
405 .with_max_sha256_buffer_bytes(7)
406 .with_max_auth_token_depth(8)
407 .with_max_rv32_code_bytes(9)
408 .with_max_rv32_memory_bytes(10)
409 .build();
410
411 assert_eq!(
412 config_params(&built),
413 [
414 ConfigParam::kConfigParamMaxSHA256Buffers {
415 max_sha256_buffers: 1
416 },
417 ConfigParam::kConfigParamMaxCrossInvokeDepth {
418 max_cross_invoke_depth: 2
419 },
420 ConfigParam::kConfigParamMaxNewContractsPerTx {
421 max_new_contracts_per_tx: 3
422 },
423 ConfigParam::kConfigParamMaxBytestringLen {
424 max_bytestring_len: 4
425 },
426 ConfigParam::kConfigParamMaxContractSizeBytes {
427 max_contract_size_bytes: 5
428 },
429 ConfigParam::kConfigParamMaxSavepointDepth {
430 max_savepoint_depth: 6
431 },
432 ConfigParam::kConfigParamMaxSHA256BufferBytes {
433 max_sha256_buffer_bytes: 7
434 },
435 ConfigParam::kConfigParamMaxAuthTokenDepth {
436 max_auth_token_depth: 8
437 },
438 ConfigParam::kConfigParamMaxRv32CodeBytes {
439 max_rv32_code_bytes: 9
440 },
441 ConfigParam::kConfigParamMaxRv32MemoryBytes {
442 max_rv32_memory_bytes: 10
443 },
444 ],
445 );
446 }
447
448 #[test]
449 fn an_override_may_extend_the_prefix_by_one() {
450 let base =
451 config_of(vec![ConfigParam::kConfigParamMaxSHA256Buffers {
452 max_sha256_buffers: 7,
453 }]);
454 let built = NetworkConfigBuilder::new()
455 .with_base(base)
456 .with_max_cross_invoke_depth(3)
457 .build();
458
459 assert_eq!(config_params(&built).len(), 2);
460 }
461
462 #[test]
465 #[should_panic(expected = "gapless prefix")]
466 fn an_override_that_would_leave_a_gap_is_refused() {
467 let base = config_of(vec![]);
468 let _ = NetworkConfigBuilder::new()
469 .with_base(base)
470 .with_max_cross_invoke_depth(3)
471 .build();
472 }
473
474 #[test]
475 #[should_panic(expected = "gapless prefix")]
476 fn a_base_that_is_not_a_prefix_is_refused() {
477 let base =
478 config_of(vec![ConfigParam::kConfigParamMaxCrossInvokeDepth {
479 max_cross_invoke_depth: 9,
480 }]);
481 let _ = NetworkConfigBuilder::new().with_base(base).build();
482 }
483
484 #[test]
485 fn a_builder_with_a_base_is_not_stock() {
486 assert!(
487 !NetworkConfigBuilder::new()
488 .with_base(stock_network_config())
489 .is_stock()
490 );
491 }
492}