1use std::{
2 cmp::max,
3 collections::{BTreeMap, HashMap},
4 path::Path,
5};
6
7use alloy::primitives::Address;
8use anyhow::{Context, Ok, ensure};
9use espresso_types::{
10 FeeAccount, FeeAmount, GenesisHeader, L1BlockInfo, L1Client, SeqTypes, Timestamp, Upgrade,
11 v0_3::ChainConfig,
12};
13use hotshot_types::{VersionedDaCommittee, version_ser};
14use serde::{Deserialize, Serialize};
15use vbs::version::Version;
16use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_VERSION};
17
18#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
20pub struct StakeTableConfig {
21 pub capacity: usize,
22}
23
24#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
26#[serde(untagged)]
27pub enum L1Finalized {
28 Block(L1BlockInfo),
34
35 Number { number: u64 },
41
42 Timestamp { timestamp: Timestamp },
48}
49
50#[derive(Clone, Debug, Deserialize, Serialize)]
52pub struct Genesis {
53 #[serde(with = "version_ser")]
54 pub base_version: Version,
55 #[serde(with = "version_ser")]
56 pub upgrade_version: Version,
57 #[serde(with = "version_ser")]
58 pub genesis_version: Version,
59 pub epoch_height: Option<u64>,
60 pub drb_difficulty: Option<u64>,
61 pub drb_upgrade_difficulty: Option<u64>,
62 pub epoch_start_block: Option<u64>,
63 pub stake_table_capacity: Option<usize>,
64 pub chain_config: ChainConfig,
65 pub stake_table: StakeTableConfig,
66 #[serde(default)]
67 pub accounts: HashMap<FeeAccount, FeeAmount>,
68 pub l1_finalized: L1Finalized,
69 pub header: GenesisHeader,
70 #[serde(rename = "upgrade", with = "upgrade_ser")]
71 #[serde(default)]
72 pub upgrades: BTreeMap<Version, Upgrade>,
73 #[serde(default)]
74 pub da_committees: Option<Vec<VersionedDaCommittee<SeqTypes>>>,
75}
76
77impl Genesis {
78 pub fn max_base_fee(&self) -> FeeAmount {
79 let mut base_fee = self.chain_config.base_fee;
80
81 let upgrades: Vec<&Upgrade> = self.upgrades.values().collect();
82
83 for upgrade in upgrades {
84 let chain_config = upgrade.upgrade_type.chain_config();
85
86 if let Some(cf) = chain_config {
87 base_fee = std::cmp::max(cf.base_fee, base_fee);
88 }
89 }
90
91 base_fee
92 }
93}
94
95impl Genesis {
96 pub fn validate(&self) -> anyhow::Result<()> {
98 ensure!(
99 self.genesis_version <= self.base_version,
100 "genesis_version cannot be greater than base_version"
101 );
102
103 let version = max(self.base_version, self.upgrade_version);
104
105 if version >= EPOCH_VERSION {
106 self.epoch_height
107 .context("epoch_height missing from genesis")?;
108 self.epoch_start_block
109 .context("epoch_start_block missing from genesis")?;
110 }
111
112 if version >= DRB_AND_HEADER_UPGRADE_VERSION {
113 self.drb_difficulty
114 .context("drb_difficulty missing from genesis")?;
115 self.drb_upgrade_difficulty
116 .context("drb_upgrade_difficulty missing from genesis")?;
117 }
118
119 Ok(())
120 }
121
122 pub async fn validate_fee_contract(&self, l1: &L1Client) -> anyhow::Result<()> {
123 if let Some(fee_contract_address) = self.chain_config.fee_contract {
124 tracing::info!("validating fee contract at {fee_contract_address:x}");
125
126 if !l1
127 .retry_on_all_providers(|| l1.is_proxy_contract(fee_contract_address))
128 .await
129 .context("checking if fee contract is a proxy")?
130 {
131 anyhow::bail!("Fee contract address {fee_contract_address:x} is not a proxy");
132 }
133 }
134
135 for (version, upgrade) in &self.upgrades {
137 let chain_config = &upgrade.upgrade_type.chain_config();
138
139 if chain_config.is_none() {
140 continue;
141 }
142
143 let chain_config = chain_config.unwrap();
144
145 if let Some(fee_contract_address) = chain_config.fee_contract {
146 if fee_contract_address == Address::default() {
147 anyhow::bail!("Fee contract cannot use the zero address");
148 } else if !l1
149 .retry_on_all_providers(|| l1.is_proxy_contract(fee_contract_address))
150 .await
151 .context(format!(
152 "checking if fee contract is a proxy in upgrade {version}",
153 ))?
154 {
155 anyhow::bail!("Fee contract's address is not a proxy");
156 }
157 } else {
158 anyhow::bail!("Fee contract's address for the upgrade is missing");
160 }
161 }
162 Ok(())
164 }
165}
166
167mod upgrade_ser {
168 use std::{collections::BTreeMap, fmt};
169
170 use espresso_types::{
171 Upgrade, UpgradeType,
172 v0_1::{TimeBasedUpgrade, UpgradeMode, ViewBasedUpgrade},
173 };
174 use serde::{
175 Deserialize, Deserializer, Serialize, Serializer,
176 de::{self, SeqAccess, Visitor},
177 ser::SerializeSeq,
178 };
179 use vbs::version::Version;
180
181 pub fn serialize<S>(map: &BTreeMap<Version, Upgrade>, serializer: S) -> Result<S::Ok, S::Error>
182 where
183 S: Serializer,
184 {
185 #[derive(Debug, Clone, Serialize, Deserialize)]
186 pub struct Fields {
187 pub version: String,
188 #[serde(flatten)]
189 pub mode: UpgradeMode,
190 #[serde(flatten)]
191 pub upgrade_type: UpgradeType,
192 }
193
194 let mut seq = serializer.serialize_seq(Some(map.len()))?;
195 for (version, upgrade) in map {
196 seq.serialize_element(&Fields {
197 version: version.to_string(),
198 mode: upgrade.mode.clone(),
199 upgrade_type: upgrade.upgrade_type.clone(),
200 })?
201 }
202 seq.end()
203 }
204
205 pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<Version, Upgrade>, D::Error>
206 where
207 D: Deserializer<'de>,
208 {
209 struct VecToHashMap;
210
211 #[derive(Debug, Clone, Serialize, Deserialize)]
212 pub struct Fields {
213 pub version: String,
214 #[serde(flatten)]
218 pub time_based: Option<TimeBasedUpgrade>,
219 #[serde(flatten)]
220 pub view_based: Option<ViewBasedUpgrade>,
221 #[serde(flatten)]
222 pub upgrade_type: UpgradeType,
223 }
224
225 impl<'de> Visitor<'de> for VecToHashMap {
226 type Value = BTreeMap<Version, Upgrade>;
227
228 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
229 formatter.write_str("a vector of tuples (key-value pairs)")
230 }
231
232 fn visit_seq<A>(self, mut seq: A) -> Result<BTreeMap<Version, Upgrade>, A::Error>
233 where
234 A: SeqAccess<'de>,
235 {
236 let mut map = BTreeMap::new();
237
238 while let Some(fields) = seq.next_element::<Fields>()? {
239 let version: Vec<_> = fields.version.split('.').collect();
241
242 let version = Version {
243 major: version[0]
244 .parse()
245 .map_err(|_| de::Error::custom("invalid version format"))?,
246 minor: version[1]
247 .parse()
248 .map_err(|_| de::Error::custom("invalid version format"))?,
249 };
250
251 match (fields.time_based, fields.view_based) {
252 (Some(_), Some(_)) => {
253 return Err(de::Error::custom(
254 "both view and time mode parameters are set",
255 ));
256 },
257 (None, None) => {
258 return Err(de::Error::custom(
259 "no view or time mode parameters provided",
260 ));
261 },
262 (None, Some(v)) => {
263 if v.start_proposing_view > v.stop_proposing_view {
264 return Err(de::Error::custom(
265 "stop_proposing_view is less than start_proposing_view",
266 ));
267 }
268
269 map.insert(
270 version,
271 Upgrade {
272 mode: UpgradeMode::View(v),
273 upgrade_type: fields.upgrade_type,
274 },
275 );
276 },
277 (Some(t), None) => {
278 if t.start_proposing_time.unix_timestamp()
279 > t.stop_proposing_time.unix_timestamp()
280 {
281 return Err(de::Error::custom(
282 "stop_proposing_time is less than start_proposing_time",
283 ));
284 }
285
286 map.insert(
287 version,
288 Upgrade {
289 mode: UpgradeMode::Time(t),
290 upgrade_type: fields.upgrade_type.clone(),
291 },
292 );
293 },
294 }
295 }
296
297 Ok(map)
298 }
299 }
300
301 deserializer.deserialize_seq(VecToHashMap)
302 }
303}
304
305impl Genesis {
306 pub fn to_file(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
307 let toml = toml::to_string_pretty(self)?;
308 std::fs::write(path, toml.as_bytes())?;
309 Ok(())
310 }
311
312 pub fn from_file(path: impl AsRef<Path>) -> anyhow::Result<Self> {
313 let path = path.as_ref();
314 let bytes = std::fs::read(path).context(format!("genesis file {}", path.display()))?;
315 let text = std::str::from_utf8(&bytes).context("genesis file must be UTF-8")?;
316
317 let genesis: Self = toml::from_str(text).context("malformed genesis file")?;
318 genesis.validate().context("validating genesis")?;
319 Ok(genesis)
320 }
321}
322
323#[cfg(test)]
324mod test {
325 use std::{fs, path::Path, sync::Arc};
326
327 use alloy::{
328 node_bindings::Anvil,
329 primitives::{B256, U256},
330 providers::{ProviderBuilder, layers::AnvilProvider},
331 };
332 use espresso_contract_deployer::{self as deployer, Contracts};
333 use espresso_types::{
334 L1BlockInfo, TimeBasedUpgrade, Timestamp, UpgradeMode, UpgradeType, ViewBasedUpgrade,
335 };
336 use espresso_utils::ser::FromStringOrInteger;
337 use tempfile::NamedTempFile;
338 use toml::toml;
339
340 use super::*;
341
342 fn minimal_genesis_toml(version: &str, root_fields: &str) -> String {
343 format!(
344 r#"
345 base_version = "{version}"
346 upgrade_version = "{version}"
347 genesis_version = "{version}"
348 {root_fields}
349
350 [stake_table]
351 capacity = 10
352
353 [chain_config]
354 chain_id = 12345
355 max_block_size = 30000
356 base_fee = 1
357 fee_recipient = "0x0000000000000000000000000000000000000000"
358
359 [header]
360 timestamp = 123456
361
362 [header.chain_config]
363 chain_id = 35353
364 max_block_size = 30720
365 base_fee = 0
366 fee_recipient = "0x0000000000000000000000000000000000000000"
367
368 [l1_finalized]
369 number = 0
370 "#
371 )
372 }
373
374 #[test]
375 fn test_genesis_validation_allows_pre_epoch_without_epoch_fields() {
376 let genesis: Genesis = toml::from_str(&minimal_genesis_toml("0.2", "")).unwrap();
377 genesis.validate().unwrap();
378 }
379
380 #[test]
381 fn test_genesis_validation_requires_epoch_fields() {
382 let genesis: Genesis = toml::from_str(&minimal_genesis_toml("0.3", "")).unwrap();
383 assert!(genesis.validate().is_err());
384 }
385
386 #[test]
387 fn test_genesis_validation_requires_drb_fields() {
388 let genesis: Genesis = toml::from_str(&minimal_genesis_toml(
389 "0.4",
390 r#"
391 epoch_height = 20
392 epoch_start_block = 1
393 stake_table_capacity = 200
394 "#,
395 ))
396 .unwrap();
397 assert!(genesis.validate().is_err());
398 }
399
400 #[test]
401 fn test_genesis_from_file_accepts_complete_v04_genesis() {
402 let file = NamedTempFile::new().unwrap();
403 fs::write(
404 file.path(),
405 minimal_genesis_toml(
406 "0.4",
407 r#"
408 epoch_height = 20
409 epoch_start_block = 1
410 stake_table_capacity = 200
411 drb_difficulty = 10
412 drb_upgrade_difficulty = 20
413 "#,
414 ),
415 )
416 .unwrap();
417
418 assert!(Genesis::from_file(file.path()).is_ok());
419 }
420
421 #[test]
422 fn test_committed_genesis_files_validate() {
423 let genesis_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../data/genesis");
424 let mut checked = 0;
425
426 for entry in fs::read_dir(&genesis_dir).unwrap() {
427 let path = entry.unwrap().path();
428 if path.extension().is_none_or(|ext| ext != "toml") {
429 continue;
430 }
431
432 Genesis::from_file(&path)
433 .unwrap_or_else(|err| panic!("{} failed validation: {err:#}", path.display()));
434 checked += 1;
435 }
436
437 assert!(checked > 0, "no genesis files found");
438 }
439
440 #[test]
441 fn test_genesis_from_toml_with_optional_fields() {
442 let toml = toml! {
443 base_version = "0.1"
444 upgrade_version = "0.2"
445 genesis_version = "0.1"
446
447 [stake_table]
448 capacity = 10
449
450 [chain_config]
451 chain_id = 12345
452 max_block_size = 30000
453 base_fee = 1
454 fee_recipient = "0x0000000000000000000000000000000000000000"
455 fee_contract = "0x0000000000000000000000000000000000000000"
456
457 [header]
458 timestamp = 123456
459
460 [header.chain_config]
461 chain_id = 35353
462 max_block_size = 30720
463 base_fee = 0
464 fee_recipient = "0x0000000000000000000000000000000000000000"
465
466 [accounts]
467 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
468 "0x0000000000000000000000000000000000000000" = 42
469
470 [l1_finalized]
471 number = 64
472 timestamp = "0x123def"
473 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
474 }
475 .to_string();
476
477 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
478 assert_eq!(genesis.genesis_version, Version { major: 0, minor: 1 });
479 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
480 assert_eq!(
481 genesis.chain_config,
482 ChainConfig {
483 chain_id: 12345.into(),
484 max_block_size: 30000.into(),
485 base_fee: 1.into(),
486 fee_recipient: FeeAccount::default(),
487 fee_contract: Some(Address::default()),
488 stake_table_contract: None
489 }
490 );
491 assert_eq!(
492 genesis.header,
493 GenesisHeader {
494 timestamp: Timestamp::from_integer(123456).unwrap(),
495 chain_config: ChainConfig::default(),
496 }
497 );
498 assert_eq!(
499 genesis.accounts,
500 [
501 (
502 FeeAccount::from(Address::from([
503 0x23, 0x61, 0x8e, 0x81, 0xe3, 0xf5, 0xcd, 0xf7, 0xf5, 0x4c, 0x3d, 0x65,
504 0xf7, 0xfb, 0xc0, 0xab, 0xf5, 0xb2, 0x1e, 0x8f
505 ])),
506 100000.into()
507 ),
508 (FeeAccount::default(), 42.into())
509 ]
510 .into_iter()
511 .collect::<HashMap<_, _>>()
512 );
513 assert_eq!(
514 genesis.l1_finalized,
515 L1Finalized::Block(L1BlockInfo {
516 number: 64,
517 timestamp: U256::from(0x123def),
518 hash: B256::from([
520 0x80, 0xf5, 0xdd, 0x11, 0xf2, 0xbd, 0xda, 0x28, 0x14, 0xcb, 0x1a, 0xd9, 0x4e,
521 0xf3, 0x0a, 0x47, 0xde, 0x02, 0xcf, 0x28, 0xad, 0x68, 0xc8, 0x9e, 0x10, 0x4c,
522 0x00, 0xc4, 0xe5, 0x1b, 0xb7, 0xa5
523 ])
524 })
525 );
526 }
527
528 #[test]
529 fn test_genesis_from_toml_without_optional_fields() {
530 let toml = toml! {
531 base_version = "0.1"
532 upgrade_version = "0.2"
533 genesis_version = "0.1"
534
535 [stake_table]
536 capacity = 10
537
538 [chain_config]
539 chain_id = 12345
540 max_block_size = 30000
541 base_fee = 1
542 fee_recipient = "0x0000000000000000000000000000000000000000"
543
544 [header]
545 timestamp = 123456
546 [header.chain_config]
547 chain_id = 35353
548 max_block_size = 30720
549 base_fee = 0
550 fee_recipient = "0x0000000000000000000000000000000000000000"
551
552 [l1_finalized]
553 number = 0
554 }
555 .to_string();
556
557 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
558
559 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
560 assert_eq!(
561 genesis.chain_config,
562 ChainConfig {
563 chain_id: 12345.into(),
564 max_block_size: 30000.into(),
565 base_fee: 1.into(),
566 fee_recipient: FeeAccount::default(),
567 fee_contract: None,
568 stake_table_contract: None,
569 }
570 );
571 assert_eq!(
572 genesis.header,
573 GenesisHeader {
574 timestamp: Timestamp::from_integer(123456).unwrap(),
575 chain_config: ChainConfig::default(),
576 }
577 );
578 assert_eq!(genesis.accounts, HashMap::default());
579 assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 0 });
580 }
581
582 #[test]
583 fn test_genesis_l1_finalized_number_only() {
584 let toml = toml! {
585 base_version = "0.1"
586 upgrade_version = "0.2"
587 genesis_version = "0.1"
588
589 [stake_table]
590 capacity = 10
591
592 [chain_config]
593 chain_id = 12345
594 max_block_size = 30000
595 base_fee = 1
596 fee_recipient = "0x0000000000000000000000000000000000000000"
597
598 [header]
599 timestamp = 123456
600
601 [header.chain_config]
602 chain_id = 35353
603 max_block_size = 30720
604 base_fee = 0
605 fee_recipient = "0x0000000000000000000000000000000000000000"
606
607 [l1_finalized]
608 number = 42
609 }
610 .to_string();
611
612 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
613 assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 42 });
614 }
615
616 #[test]
617 fn test_genesis_l1_finalized_timestamp_only() {
618 let toml = toml! {
619 base_version = "0.1"
620 upgrade_version = "0.2"
621 genesis_version = "0.1"
622
623 [stake_table]
624 capacity = 10
625
626 [chain_config]
627 chain_id = 12345
628 max_block_size = 30000
629 base_fee = 1
630 fee_recipient = "0x0000000000000000000000000000000000000000"
631
632 [header]
633 timestamp = 123456
634
635 [header.chain_config]
636 chain_id = 35353
637 max_block_size = 30720
638 base_fee = 0
639 fee_recipient = "0x0000000000000000000000000000000000000000"
640
641 [l1_finalized]
642 timestamp = "2024-01-02T00:00:00Z"
643 }
644 .to_string();
645
646 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
647 assert_eq!(
648 genesis.l1_finalized,
649 L1Finalized::Timestamp {
650 timestamp: Timestamp::from_string("2024-01-02T00:00:00Z".to_string()).unwrap()
651 }
652 );
653 }
654
655 #[test_log::test(tokio::test(flavor = "multi_thread"))]
660 async fn test_genesis_fee_contract_is_a_proxy() -> anyhow::Result<()> {
661 let anvil = Arc::new(Anvil::new().spawn());
662 let wallet = anvil.wallet().unwrap();
663 let admin = wallet.default_signer().address();
664 let inner_provider = ProviderBuilder::new()
665 .wallet(wallet)
666 .connect_http(anvil.endpoint_url());
667 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
668 let mut contracts = Contracts::new();
669
670 let proxy_addr =
671 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
672
673 let toml = format!(
674 r#"
675 base_version = "0.1"
676 upgrade_version = "0.2"
677 genesis_version = "0.1"
678
679 [stake_table]
680 capacity = 10
681
682 [chain_config]
683 chain_id = 12345
684 max_block_size = 30000
685 base_fee = 1
686 fee_recipient = "0x0000000000000000000000000000000000000000"
687 fee_contract = "{proxy_addr:?}"
688
689 [header]
690 timestamp = 123456
691
692 [header.chain_config]
693 chain_id = 35353
694 max_block_size = 30720
695 base_fee = 0
696 fee_recipient = "0x0000000000000000000000000000000000000000"
697
698 [l1_finalized]
699 number = 42
700 "#,
701 )
702 .to_string();
703
704 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
705
706 let result = genesis
708 .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
709 .await;
710
711 assert!(
712 result.is_ok(),
713 "Expected Fee Contract to be a proxy, but it was not"
714 );
715 Ok(())
716 }
717
718 #[test_log::test(tokio::test(flavor = "multi_thread"))]
719 async fn test_genesis_fee_contract_is_a_proxy_with_upgrades() -> anyhow::Result<()> {
720 let anvil = Arc::new(Anvil::new().spawn());
721 let wallet = anvil.wallet().unwrap();
722 let admin = wallet.default_signer().address();
723 let inner_provider = ProviderBuilder::new()
724 .wallet(wallet)
725 .connect_http(anvil.endpoint_url());
726 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
727 let mut contracts = Contracts::new();
728
729 let proxy_addr =
730 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
731
732 let toml = format!(
733 r#"
734 base_version = "0.1"
735 upgrade_version = "0.2"
736 genesis_version = "0.1"
737
738 [stake_table]
739 capacity = 10
740
741 [chain_config]
742 chain_id = 12345
743 max_block_size = 30000
744 base_fee = 1
745 fee_recipient = "0x0000000000000000000000000000000000000000"
746
747 [header]
748 timestamp = 123456
749
750 [header.chain_config]
751 chain_id = 35353
752 max_block_size = 30720
753 base_fee = 0
754 fee_recipient = "0x0000000000000000000000000000000000000000"
755
756 [l1_finalized]
757 number = 42
758
759 [[upgrade]]
760 version = "0.2"
761 start_proposing_view = 5
762 stop_proposing_view = 15
763
764 [upgrade.fee]
765
766 [upgrade.fee.chain_config]
767 chain_id = 12345
768 max_block_size = 30000
769 base_fee = 1
770 fee_recipient = "0x0000000000000000000000000000000000000000"
771 fee_contract = "{proxy_addr:?}"
772
773
774 "#,
775 )
776 .to_string();
777
778 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
779
780 let result = genesis
782 .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
783 .await;
784
785 assert!(
786 result.is_ok(),
787 "Expected Fee Contract to be a proxy, but it was not"
788 );
789 Ok(())
790 }
791
792 #[test_log::test(tokio::test(flavor = "multi_thread"))]
793 async fn test_genesis_missing_fee_contract_with_upgrades() {
794 let toml = toml! {
795 base_version = "0.1"
796 upgrade_version = "0.2"
797 genesis_version = "0.1"
798
799 [stake_table]
800 capacity = 10
801
802 [chain_config]
803 chain_id = 12345
804 max_block_size = 30000
805 base_fee = 1
806 fee_recipient = "0x0000000000000000000000000000000000000000"
807
808 [header]
809 timestamp = 123456
810
811 [header.chain_config]
812 chain_id = 35353
813 max_block_size = 30720
814 base_fee = 0
815 fee_recipient = "0x0000000000000000000000000000000000000000"
816
817 [l1_finalized]
818 number = 42
819
820 [[upgrade]]
821 version = "0.2"
822 start_proposing_view = 5
823 stop_proposing_view = 15
824
825 [upgrade.fee]
826
827 [upgrade.fee.chain_config]
828 chain_id = 12345
829 max_block_size = 30000
830 base_fee = 1
831 fee_recipient = "0x0000000000000000000000000000000000000000"
832
833 [[upgrade]]
834 version = "0.3"
835 start_proposing_view = 5
836 stop_proposing_view = 15
837
838 [upgrade.epoch]
839 [upgrade.epoch.chain_config]
840 chain_id = 999999999
841 max_block_size = 3000
842 base_fee = 1
843 fee_recipient = "0x0000000000000000000000000000000000000000"
844 bid_recipient = "0x0000000000000000000000000000000000000000"
845 fee_contract = "0xa15bb66138824a1c7167f5e85b957d04dd34e468" }
847 .to_string();
848
849 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
850 let rpc_url = "https://ethereum-sepolia.publicnode.com";
851
852 let result = genesis
854 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
855 .await;
856
857 if let Err(e) = result {
859 assert!(
861 e.to_string()
862 .contains("Fee contract's address for the upgrade is missing")
863 );
864 } else {
865 panic!("Expected the fee contract to be missing, but the validation succeeded");
866 }
867 }
868
869 #[test_log::test(tokio::test(flavor = "multi_thread"))]
870 async fn test_genesis_upgrade_fee_contract_address_is_zero() {
871 let toml = toml! {
872 base_version = "0.1"
873 upgrade_version = "0.2"
874 genesis_version = "0.1"
875
876 [stake_table]
877 capacity = 10
878
879 [chain_config]
880 chain_id = 12345
881 max_block_size = 30000
882 base_fee = 1
883 fee_recipient = "0x0000000000000000000000000000000000000000"
884
885 [header]
886 timestamp = 123456
887
888 [header.chain_config]
889 chain_id = 35353
890 max_block_size = 30720
891 base_fee = 0
892 fee_recipient = "0x0000000000000000000000000000000000000000"
893
894 [l1_finalized]
895 number = 42
896
897 [[upgrade]]
898 version = "0.2"
899 start_proposing_view = 5
900 stop_proposing_view = 15
901
902 [upgrade.fee]
903 [upgrade.fee.chain_config]
904 chain_id = 12345
905 max_block_size = 30000
906 base_fee = 1
907 fee_recipient = "0x0000000000000000000000000000000000000000"
908 fee_contract = "0x0000000000000000000000000000000000000000"
909 }
910 .to_string();
911
912 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
913 let rpc_url = "https://ethereum-sepolia.publicnode.com";
914
915 let result = genesis
917 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
918 .await;
919
920 if let Err(e) = result {
922 assert!(
924 e.to_string()
925 .contains("Fee contract cannot use the zero address")
926 );
927 } else {
928 panic!(
929 "Expected the fee contract to complain about the zero address but the validation \
930 succeeded"
931 );
932 }
933 }
934
935 #[test_log::test(tokio::test(flavor = "multi_thread"))]
936 async fn test_genesis_fee_contract_l1_failover() -> anyhow::Result<()> {
937 let anvil = Arc::new(Anvil::new().spawn());
938 let wallet = anvil.wallet().unwrap();
939 let admin = wallet.default_signer().address();
940 let inner_provider = ProviderBuilder::new()
941 .wallet(wallet)
942 .connect_http(anvil.endpoint_url());
943 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
944 let mut contracts = Contracts::new();
945
946 let proxy_addr =
947 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
948
949 let toml = format!(
950 r#"
951 base_version = "0.1"
952 upgrade_version = "0.2"
953 genesis_version = "0.1"
954
955 [stake_table]
956 capacity = 10
957
958 [chain_config]
959 chain_id = 12345
960 max_block_size = 30000
961 base_fee = 1
962 fee_recipient = "0x0000000000000000000000000000000000000000"
963 fee_contract = "{proxy_addr:?}"
964
965 [header]
966 timestamp = 123456
967
968 [header.chain_config]
969 chain_id = 35353
970 max_block_size = 30720
971 base_fee = 0
972 fee_recipient = "0x0000000000000000000000000000000000000000"
973
974 [l1_finalized]
975 number = 42
976 "#
977 )
978 .to_string();
979
980 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
981 genesis
982 .validate_fee_contract(
983 &L1Client::new(vec![
984 "http://notareall1provider".parse().unwrap(),
985 anvil.endpoint().parse().unwrap(),
986 ])
987 .unwrap(),
988 )
989 .await
990 .unwrap();
991
992 Ok(())
993 }
994
995 #[test]
996 fn test_genesis_from_toml_units() {
997 let toml = toml! {
998 base_version = "0.1"
999 upgrade_version = "0.2"
1000 genesis_version = "0.1"
1001
1002 [stake_table]
1003 capacity = 10
1004
1005 [chain_config]
1006 chain_id = 12345
1007 max_block_size = "30mb"
1008 base_fee = "1 gwei"
1009 fee_recipient = "0x0000000000000000000000000000000000000000"
1010
1011 [header]
1012 timestamp = "2024-05-16T11:20:28-04:00"
1013
1014
1015
1016 [header.chain_config]
1017 chain_id = 35353
1018 max_block_size = 30720
1019 base_fee = 0
1020 fee_recipient = "0x0000000000000000000000000000000000000000"
1021
1022 [l1_finalized]
1023 number = 0
1024 }
1025 .to_string();
1026
1027 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
1028 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
1029 assert_eq!(*genesis.chain_config.max_block_size, 30000000);
1030 assert_eq!(genesis.chain_config.base_fee, 1_000_000_000.into());
1031 assert_eq!(
1032 genesis.header,
1033 GenesisHeader {
1034 timestamp: Timestamp::from_integer(1715872828).unwrap(),
1035 chain_config: ChainConfig::default(),
1036 }
1037 )
1038 }
1039
1040 #[test]
1041 fn test_genesis_toml_fee_upgrade_view_mode() {
1042 let toml = toml! {
1045 base_version = "0.1"
1046 upgrade_version = "0.2"
1047 genesis_version = "0.1"
1048
1049 [stake_table]
1050 capacity = 10
1051
1052 [chain_config]
1053 chain_id = 12345
1054 max_block_size = 30000
1055 base_fee = 1
1056 fee_recipient = "0x0000000000000000000000000000000000000000"
1057 fee_contract = "0x0000000000000000000000000000000000000000"
1058
1059 [header]
1060 timestamp = 123456
1061
1062 [header.chain_config]
1063 chain_id = 35353
1064 max_block_size = 30720
1065 base_fee = 0
1066 fee_recipient = "0x0000000000000000000000000000000000000000"
1067
1068 [accounts]
1069 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1070 "0x0000000000000000000000000000000000000000" = 42
1071
1072 [l1_finalized]
1073 number = 64
1074 timestamp = "0x123def"
1075 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1076
1077 [[upgrade]]
1078 version = "0.2"
1079 start_proposing_view = 1
1080 stop_proposing_view = 15
1081
1082 [upgrade.fee]
1083
1084 [upgrade.fee.chain_config]
1085 chain_id = 12345
1086 max_block_size = 30000
1087 base_fee = 1
1088 fee_recipient = "0x0000000000000000000000000000000000000000"
1089 fee_contract = "0x0000000000000000000000000000000000000000"
1090 }
1091 .to_string();
1092
1093 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
1094
1095 let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
1096 println!("{genesis_upgrade:?}");
1097
1098 assert_eq!(*version, Version { major: 0, minor: 2 });
1099
1100 let upgrade = Upgrade {
1101 mode: UpgradeMode::View(ViewBasedUpgrade {
1102 start_voting_view: None,
1103 stop_voting_view: None,
1104 start_proposing_view: 1,
1105 stop_proposing_view: 15,
1106 }),
1107 upgrade_type: UpgradeType::Fee {
1108 chain_config: genesis.chain_config,
1109 },
1110 };
1111
1112 assert_eq!(*genesis_upgrade, upgrade);
1113 }
1114
1115 #[test]
1116 fn test_genesis_toml_fee_upgrade_time_mode() {
1117 let toml = toml! {
1120 base_version = "0.1"
1121 upgrade_version = "0.2"
1122 genesis_version = "0.1"
1123
1124 [stake_table]
1125 capacity = 10
1126
1127 [chain_config]
1128 chain_id = 12345
1129 max_block_size = 30000
1130 base_fee = 1
1131 fee_recipient = "0x0000000000000000000000000000000000000000"
1132 fee_contract = "0x0000000000000000000000000000000000000000"
1133
1134 [header]
1135 timestamp = 123456
1136
1137 [header.chain_config]
1138 chain_id = 35353
1139 max_block_size = 30720
1140 base_fee = 0
1141 fee_recipient = "0x0000000000000000000000000000000000000000"
1142
1143 [accounts]
1144 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1145 "0x0000000000000000000000000000000000000000" = 42
1146
1147 [l1_finalized]
1148 number = 64
1149 timestamp = "0x123def"
1150 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1151
1152 [[upgrade]]
1153 version = "0.2"
1154 start_proposing_time = "2024-01-01T00:00:00Z"
1155 stop_proposing_time = "2024-01-02T00:00:00Z"
1156
1157 [upgrade.fee]
1158
1159 [upgrade.fee.chain_config]
1160 chain_id = 12345
1161 max_block_size = 30000
1162 base_fee = 1
1163 fee_recipient = "0x0000000000000000000000000000000000000000"
1164 fee_contract = "0x0000000000000000000000000000000000000000"
1165 }
1166 .to_string();
1167
1168 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
1169
1170 let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
1171
1172 assert_eq!(*version, Version { major: 0, minor: 2 });
1173
1174 let upgrade = Upgrade {
1175 mode: UpgradeMode::Time(TimeBasedUpgrade {
1176 start_voting_time: None,
1177 stop_voting_time: None,
1178 start_proposing_time: Timestamp::from_string("2024-01-01T00:00:00Z".to_string())
1179 .unwrap(),
1180 stop_proposing_time: Timestamp::from_string("2024-01-02T00:00:00Z".to_string())
1181 .unwrap(),
1182 }),
1183 upgrade_type: UpgradeType::Fee {
1184 chain_config: genesis.chain_config,
1185 },
1186 };
1187
1188 assert_eq!(*genesis_upgrade, upgrade);
1189 }
1190
1191 #[test]
1192 fn test_genesis_toml_fee_upgrade_view_and_time_mode() {
1193 let toml = toml! {
1196 base_version = "0.1"
1197 upgrade_version = "0.2"
1198 genesis_version = "0.1"
1199
1200 [stake_table]
1201 capacity = 10
1202
1203 [chain_config]
1204 chain_id = 12345
1205 max_block_size = 30000
1206 base_fee = 1
1207 fee_recipient = "0x0000000000000000000000000000000000000000"
1208 fee_contract = "0x0000000000000000000000000000000000000000"
1209
1210 [header]
1211 timestamp = 123456
1212
1213 [header.chain_config]
1214 chain_id = 35353
1215 max_block_size = 30720
1216 base_fee = 0
1217 fee_recipient = "0x0000000000000000000000000000000000000000"
1218
1219 [accounts]
1220 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1221 "0x0000000000000000000000000000000000000000" = 42
1222
1223 [l1_finalized]
1224 number = 64
1225 timestamp = "0x123def"
1226 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1227
1228 [[upgrade]]
1229 version = "0.2"
1230 start_proposing_view = 1
1231 stop_proposing_view = 10
1232 start_proposing_time = 1
1233 stop_proposing_time = 10
1234
1235 [upgrade.fee]
1236
1237 [upgrade.fee.chain_config]
1238 chain_id = 12345
1239 max_block_size = 30000
1240 base_fee = 1
1241 fee_recipient = "0x0000000000000000000000000000000000000000"
1242 fee_contract = "0x0000000000000000000000000000000000000000"
1243 }
1244 .to_string();
1245
1246 toml::from_str::<Genesis>(&toml).unwrap_err();
1247 }
1248
1249 #[test]
1250 fn test_fee_and_epoch_upgrade_toml() {
1251 let toml = toml! {
1252 base_version = "0.1"
1253 upgrade_version = "0.2"
1254 genesis_version = "0.1"
1255 epoch_height = 20
1256 drb_difficulty = 10
1257 drb_upgrade_difficulty = 20
1258 epoch_start_block = 1
1259 stake_table_capacity = 200
1260
1261 [stake_table]
1262 capacity = 10
1263
1264 [chain_config]
1265 chain_id = 12345
1266 max_block_size = 30000
1267 base_fee = 1
1268 fee_recipient = "0x0000000000000000000000000000000000000000"
1269 fee_contract = "0x0000000000000000000000000000000000000000"
1270
1271 [header]
1272 timestamp = 123456
1273
1274 [header.chain_config]
1275 chain_id = 35353
1276 max_block_size = 30720
1277 base_fee = 0
1278 fee_recipient = "0x0000000000000000000000000000000000000000"
1279
1280 [accounts]
1281 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1282 "0x0000000000000000000000000000000000000000" = 42
1283
1284 [l1_finalized]
1285 number = 64
1286 timestamp = "0x123def"
1287 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1288
1289 [[upgrade]]
1290 version = "0.3"
1291 start_proposing_view = 1
1292 stop_proposing_view = 10
1293
1294 [upgrade.epoch]
1295 [upgrade.epoch.chain_config]
1296 chain_id = 12345
1297 max_block_size = 30000
1298 base_fee = 1
1299 fee_recipient = "0x0000000000000000000000000000000000000000"
1300 fee_contract = "0x0000000000000000000000000000000000000000"
1301 stake_table_contract = "0x0000000000000000000000000000000000000000"
1302
1303 [[upgrade]]
1304 version = "0.2"
1305 start_proposing_view = 1
1306 stop_proposing_view = 15
1307
1308 [upgrade.fee]
1309
1310 [upgrade.fee.chain_config]
1311 chain_id = 12345
1312 max_block_size = 30000
1313 base_fee = 1
1314 fee_recipient = "0x0000000000000000000000000000000000000000"
1315 fee_contract = "0x0000000000000000000000000000000000000000"
1316 }
1317 .to_string();
1318
1319 toml::from_str::<Genesis>(&toml).unwrap();
1320 }
1321
1322 #[test]
1323 fn test_genesis_chain_config() {
1324 let toml = toml! {
1325 base_version = "0.1"
1326 upgrade_version = "0.2"
1327 genesis_version = "0.2"
1328 epoch_height = 20
1329 drb_difficulty = 10
1330 drb_upgrade_difficulty = 20
1331 epoch_start_block = 1
1332 stake_table_capacity = 200
1333
1334 [stake_table]
1335 capacity = 10
1336
1337 [genesis_chain_config]
1338 chain_id = 33
1339 max_block_size = 5000
1340 base_fee = 1
1341 fee_recipient = "0x0000000000000000000000000000000000000000"
1342 fee_contract = "0x0000000000000000000000000000000000000000"
1343
1344 [chain_config]
1345 chain_id = 12345
1346 max_block_size = 30000
1347 base_fee = 1
1348 fee_recipient = "0x0000000000000000000000000000000000000000"
1349 fee_contract = "0x0000000000000000000000000000000000000000"
1350
1351 [header]
1352 timestamp = 123456
1353
1354 [header.chain_config]
1355 chain_id = 33
1356 max_block_size = 5000
1357 base_fee = 1
1358 fee_recipient = "0x0000000000000000000000000000000000000000"
1359 fee_contract = "0x0000000000000000000000000000000000000000"
1360
1361 [accounts]
1362 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1363 "0x0000000000000000000000000000000000000000" = 42
1364
1365 [l1_finalized]
1366 number = 64
1367 timestamp = "0x123def"
1368 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1369
1370 [[upgrade]]
1371 version = "0.3"
1372 start_proposing_view = 1
1373 stop_proposing_view = 10
1374
1375 [upgrade.epoch]
1376 [upgrade.epoch.chain_config]
1377 chain_id = 12345
1378 max_block_size = 30000
1379 base_fee = 1
1380 fee_recipient = "0x0000000000000000000000000000000000000000"
1381 fee_contract = "0x0000000000000000000000000000000000000000"
1382 stake_table_contract = "0x0000000000000000000000000000000000000000"
1383
1384 [[upgrade]]
1385 version = "0.2"
1386 start_proposing_view = 1
1387 stop_proposing_view = 15
1388
1389 [upgrade.fee]
1390
1391 [upgrade.fee.chain_config]
1392 chain_id = 12345
1393 max_block_size = 30000
1394 base_fee = 1
1395 fee_recipient = "0x0000000000000000000000000000000000000000"
1396 fee_contract = "0x0000000000000000000000000000000000000000"
1397 }
1398 .to_string();
1399
1400 let genesis = toml::from_str::<Genesis>(&toml).unwrap();
1401
1402 assert_eq!(genesis.header.chain_config.chain_id, 33.into());
1403 assert_eq!(genesis.chain_config.chain_id, 12345.into());
1404
1405 assert_eq!(genesis.header.chain_config.max_block_size, 5000.into());
1406 assert_eq!(genesis.chain_config.max_block_size, 30000.into());
1407 }
1408
1409 #[test]
1410 fn test_genesis_da_committees() {
1411 let toml = toml! {
1412 base_version = "0.1"
1413 upgrade_version = "0.5"
1414 genesis_version = "0.1"
1415 epoch_height = 20
1416 drb_difficulty = 10
1417 drb_upgrade_difficulty = 20
1418 epoch_start_block = 1
1419 stake_table_capacity = 200
1420
1421 [stake_table]
1422 capacity = 10
1423
1424 [chain_config]
1425 chain_id = 12345
1426 max_block_size = 30000
1427 base_fee = 1
1428 fee_recipient = "0x0000000000000000000000000000000000000000"
1429 fee_contract = "0x0000000000000000000000000000000000000000"
1430
1431 [l1_finalized]
1432 number = 64
1433 timestamp = "0x123def"
1434 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1435
1436 [header]
1437 timestamp = 123456
1438
1439 [header.chain_config]
1440 chain_id = 33
1441 max_block_size = 5000
1442 base_fee = 1
1443 fee_recipient = "0x0000000000000000000000000000000000000000"
1444 fee_contract = "0x0000000000000000000000000000000000000000"
1445
1446 [[upgrade]]
1447 version = "0.5"
1448 start_proposing_view = 1
1449 stop_proposing_view = 15
1450
1451 [upgrade.da]
1452 [upgrade.da.chain_config]
1453 chain_id = 12345
1454 max_block_size = 30000
1455 base_fee = 1
1456 fee_recipient = "0x0000000000000000000000000000000000000000"
1457 fee_contract = "0x0000000000000000000000000000000000000000"
1458 stake_table_contract = "0x0000000000000000000000000000000000000000"
1459
1460 [[da_committees]]
1461 start_version = "0.6"
1462 start_epoch = 10
1463 committee = [
1464 { stake_table_entry = { stake_key = "BLS_VER_KEY~bQszS-QKYvUij2g20VqS8asttGSb95NrTu2PUj0uMh1CBUxNy1FqyPDjZqB29M7ZbjWqj79QkEOWkpga84AmDYUeTuWmy-0P1AdKHD3ehc-dKvei78BDj5USwXPJiDUlCxvYs_9rWYhagaq-5_LXENr78xel17spftNd5MA1Mw5U", stake_amount = "0x1"}, state_ver_key = "SCHNORR_VER_KEY~lJqDaVZyM0hWP2Br52IX5FeE-dCAIC-dPX7bL5-qUx-vjbunwe-ENOeZxj6FuOyvDCFzoGeP7yZ0fM995qF-CRE"},
1465 { stake_table_entry = { stake_key = "BLS_VER_KEY~bQszS-QKYvUij2g20VqS8asttGSb95NrTu2PUj0uMh1CBUxNy1FqyPDjZqB29M7ZbjWqj79QkEOWkpga84AmDYUeTuWmy-0P1AdKHD3ehc-dKvei78BDj5USwXPJiDUlCxvYs_9rWYhagaq-5_LXENr78xel17spftNd5MA1Mw5U", stake_amount = "0x1"}, state_ver_key = "SCHNORR_VER_KEY~lJqDaVZyM0hWP2Br52IX5FeE-dCAIC-dPX7bL5-qUx-vjbunwe-ENOeZxj6FuOyvDCFzoGeP7yZ0fM995qF-CRE"}
1466 ]
1467 }
1468 .to_string();
1469
1470 let genesis = toml::from_str::<Genesis>(&toml).unwrap();
1471
1472 let da_committees = genesis
1473 .da_committees
1474 .expect("DA committees should be present");
1475 assert_eq!(da_committees.len(), 1);
1476
1477 let da_committee = &da_committees[0];
1478
1479 assert_eq!(da_committee.start_version, Version { major: 0, minor: 6 });
1480 assert_eq!(da_committee.start_epoch, 10);
1481 assert_eq!(da_committee.committee.len(), 2);
1482 assert_eq!(
1483 da_committee.committee[0].stake_table_entry.stake_amount,
1484 U256::from(1)
1485 );
1486 assert_eq!(
1487 da_committee.committee[1].stake_table_entry.stake_amount,
1488 U256::from(1)
1489 );
1490 }
1491
1492 #[test]
1498 fn demo_da_committees_match_dev_mnemonic() {
1499 use std::collections::HashSet;
1500
1501 use alloy::signers::local::coins_bip39::{English, Mnemonic};
1502 use espresso_keyset::{KeySet, KeySetOptions};
1503 use hotshot_types::{
1504 light_client::StateKeyPair,
1505 signature_key::{BLSKeyPair, BLSPubKey, SchnorrPubKey},
1506 };
1507 use staking_cli::{DEMO_VALIDATOR_START_INDEX, DEV_MNEMONIC};
1508
1509 let mnemonic = Mnemonic::<English>::new_from_phrase(DEV_MNEMONIC).unwrap();
1510 let mut expected_bls: HashSet<BLSPubKey> = HashSet::new();
1511 let mut expected_schnorr: HashSet<SchnorrPubKey> = HashSet::new();
1512 for val_index in 0..5u64 {
1513 let keyset = KeySet::try_from(KeySetOptions {
1514 mnemonic: Some(mnemonic.clone()),
1515 index: Some(u64::from(DEMO_VALIDATOR_START_INDEX) + val_index),
1516 key_file: None,
1517 private_staking_key: None,
1518 private_state_key: None,
1519 private_x25519_key: None,
1520 })
1521 .unwrap();
1522 expected_bls.insert(BLSKeyPair::from(keyset.staking).ver_key());
1523 expected_schnorr.insert(StateKeyPair::from_sign_key(keyset.state).ver_key());
1524 }
1525
1526 let path = Path::new(env!("CARGO_MANIFEST_DIR"))
1527 .join("../../../data/genesis/demo-da-committees.toml");
1528 let genesis = Genesis::from_file(&path).unwrap();
1529 let da_committees = genesis.da_committees.expect("da_committees in genesis");
1530 assert!(!da_committees.is_empty());
1531 for committee in &da_committees {
1532 for entry in &committee.committee {
1533 assert!(
1534 expected_bls.contains(&entry.stake_table_entry.stake_key),
1535 "{path:?} epoch {} references a BLS key not derived from DEV_MNEMONIC at \
1536 indices {DEMO_VALIDATOR_START_INDEX}..{}",
1537 committee.start_epoch,
1538 u64::from(DEMO_VALIDATOR_START_INDEX) + 5,
1539 );
1540 assert!(
1541 expected_schnorr.contains(&entry.state_ver_key),
1542 "{path:?} epoch {} references a Schnorr key not derived from DEV_MNEMONIC at \
1543 indices {DEMO_VALIDATOR_START_INDEX}..{}",
1544 committee.start_epoch,
1545 u64::from(DEMO_VALIDATOR_START_INDEX) + 5,
1546 );
1547 }
1548 }
1549 }
1550}