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