Skip to main content

iota_swarm_config/
network_config_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    net::SocketAddr,
7    num::NonZeroUsize,
8    path::{Path, PathBuf},
9    sync::Arc,
10};
11
12use fastcrypto::traits::KeyPair;
13use iota_config::{
14    ExecutionCacheConfig, IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME,
15    genesis::{TokenAllocation, TokenDistributionScheduleBuilder},
16    node::AuthorityOverloadConfig,
17};
18use iota_genesis_builder::genesis_build_effects::GenesisBuildEffects;
19use iota_protocol_config::Chain;
20use iota_sdk_types::Address;
21use iota_types::{
22    base_types::AuthorityName,
23    committee::{Committee, ProtocolVersion},
24    crypto::{AccountKeyPair, PublicKey, get_key_pair_from_rng},
25    object::Object,
26    supported_protocol_versions::SupportedProtocolVersions,
27    traffic_control::{PolicyConfig, RemoteFirewallConfig},
28};
29use rand::rngs::OsRng;
30
31use crate::{
32    genesis_config::{
33        AccountConfig, DEFAULT_GAS_AMOUNT, GenesisConfig, ValidatorGenesisConfig,
34        ValidatorGenesisConfigBuilder,
35    },
36    network_config::NetworkConfig,
37    node_config_builder::ValidatorConfigBuilder,
38};
39
40pub enum CommitteeConfig {
41    Size(NonZeroUsize),
42    Validators(Vec<ValidatorGenesisConfig>),
43    AccountKeys(Vec<AccountKeyPair>),
44    /// Indicates that a committee should be deterministically generated, using
45    /// the provided rng as a source of randomness as well as generating
46    /// deterministic network port information.
47    Deterministic((NonZeroUsize, Option<Vec<AccountKeyPair>>)),
48}
49
50pub type SupportedProtocolVersionsCallback = Arc<
51    dyn Fn(
52            usize,                 // validator idx
53            Option<AuthorityName>, // None for fullnode
54        ) -> SupportedProtocolVersions
55        + Send
56        + Sync
57        + 'static,
58>;
59
60#[derive(Clone)]
61pub enum ProtocolVersionsConfig {
62    // use SYSTEM_DEFAULT
63    Default,
64    // Use one range for all validators.
65    Global(SupportedProtocolVersions),
66    // A closure that returns the versions for each validator.
67    // TODO: This doesn't apply to fullnodes.
68    PerValidator(SupportedProtocolVersionsCallback),
69}
70
71pub type GlobalStateHashV1EnabledCallback = Arc<dyn Fn(usize) -> bool + Send + Sync + 'static>;
72
73#[derive(Clone)]
74pub enum GlobalStateHashV1EnabledConfig {
75    Global(bool),
76    PerValidator(GlobalStateHashV1EnabledCallback),
77}
78
79pub struct ConfigBuilder<R = OsRng> {
80    rng: Option<R>,
81    config_directory: PathBuf,
82    supported_protocol_versions_config: Option<ProtocolVersionsConfig>,
83    chain_override: Option<Chain>,
84    committee: CommitteeConfig,
85    genesis_config: Option<GenesisConfig>,
86    reference_gas_price: Option<u64>,
87    additional_objects: Vec<Object>,
88    num_unpruned_validators: Option<usize>,
89    authority_overload_config: Option<AuthorityOverloadConfig>,
90    execution_cache_config: Option<ExecutionCacheConfig>,
91    data_ingestion_dir: Option<PathBuf>,
92    policy_config: Option<PolicyConfig>,
93    firewall_config: Option<RemoteFirewallConfig>,
94    max_submit_position: Option<usize>,
95    submit_delay_step_override_millis: Option<u64>,
96    global_state_hash_v1_enabled_config: Option<GlobalStateHashV1EnabledConfig>,
97    empty_validator_genesis: bool,
98    admin_interface_address: Option<SocketAddr>,
99}
100
101impl ConfigBuilder {
102    pub fn new<P: AsRef<Path>>(config_directory: P) -> Self {
103        Self {
104            rng: Some(OsRng),
105            config_directory: config_directory.as_ref().into(),
106            supported_protocol_versions_config: None,
107            chain_override: None,
108            // FIXME: A network with only 1 validator does not have liveness.
109            // We need to change this. There are some tests that depend on it though.
110            committee: CommitteeConfig::Size(NonZeroUsize::new(1).unwrap()),
111            genesis_config: None,
112            reference_gas_price: None,
113            additional_objects: vec![],
114            num_unpruned_validators: None,
115            authority_overload_config: None,
116            execution_cache_config: None,
117            data_ingestion_dir: None,
118            policy_config: None,
119            firewall_config: None,
120            max_submit_position: None,
121            submit_delay_step_override_millis: None,
122            global_state_hash_v1_enabled_config: Some(GlobalStateHashV1EnabledConfig::Global(true)),
123            empty_validator_genesis: false,
124            admin_interface_address: None,
125        }
126    }
127
128    pub fn new_with_temp_dir() -> Self {
129        Self::new(iota_common::tempdir().keep())
130    }
131}
132
133impl<R> ConfigBuilder<R> {
134    pub fn committee(mut self, committee: CommitteeConfig) -> Self {
135        self.committee = committee;
136        self
137    }
138
139    pub fn committee_size(mut self, committee_size: NonZeroUsize) -> Self {
140        self.committee = CommitteeConfig::Size(committee_size);
141        self
142    }
143
144    pub fn deterministic_committee_size(mut self, committee_size: NonZeroUsize) -> Self {
145        self.committee = CommitteeConfig::Deterministic((committee_size, None));
146        self
147    }
148
149    pub fn deterministic_committee_validators(mut self, keys: Vec<AccountKeyPair>) -> Self {
150        self.committee = CommitteeConfig::Deterministic((
151            NonZeroUsize::new(keys.len()).expect("Validator keys should be non empty"),
152            Some(keys),
153        ));
154        self
155    }
156
157    pub fn with_validator_account_keys(mut self, keys: Vec<AccountKeyPair>) -> Self {
158        self.committee = CommitteeConfig::AccountKeys(keys);
159        self
160    }
161
162    pub fn with_validators(mut self, validators: Vec<ValidatorGenesisConfig>) -> Self {
163        self.committee = CommitteeConfig::Validators(validators);
164        self
165    }
166
167    pub fn with_genesis_config(mut self, genesis_config: GenesisConfig) -> Self {
168        assert!(self.genesis_config.is_none(), "Genesis config already set");
169        self.genesis_config = Some(genesis_config);
170        self
171    }
172
173    pub fn with_chain_override(mut self, chain: Chain) -> Self {
174        assert!(self.chain_override.is_none(), "Chain override already set");
175        self.chain_override = Some(chain);
176        self
177    }
178
179    pub fn with_num_unpruned_validators(mut self, n: usize) -> Self {
180        self.num_unpruned_validators = Some(n);
181        self
182    }
183
184    pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
185        self.data_ingestion_dir = Some(path);
186        self
187    }
188
189    pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
190        self.reference_gas_price = Some(reference_gas_price);
191        self
192    }
193
194    pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
195        self.get_or_init_genesis_config().accounts = accounts;
196        self
197    }
198
199    pub fn with_chain_start_timestamp_ms(mut self, chain_start_timestamp_ms: u64) -> Self {
200        self.get_or_init_genesis_config()
201            .parameters
202            .chain_start_timestamp_ms = chain_start_timestamp_ms;
203        self
204    }
205
206    pub fn with_objects<I: IntoIterator<Item = Object>>(mut self, objects: I) -> Self {
207        self.additional_objects.extend(objects);
208        self
209    }
210
211    pub fn with_epoch_duration(mut self, epoch_duration_ms: u64) -> Self {
212        self.get_or_init_genesis_config()
213            .parameters
214            .epoch_duration_ms = epoch_duration_ms;
215        self
216    }
217
218    pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
219        self.get_or_init_genesis_config()
220            .parameters
221            .protocol_version = protocol_version;
222        self
223    }
224
225    pub fn with_supported_protocol_versions(mut self, c: SupportedProtocolVersions) -> Self {
226        self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::Global(c));
227        self
228    }
229
230    pub fn with_supported_protocol_version_callback(
231        mut self,
232        func: SupportedProtocolVersionsCallback,
233    ) -> Self {
234        self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::PerValidator(func));
235        self
236    }
237
238    pub fn with_supported_protocol_versions_config(mut self, c: ProtocolVersionsConfig) -> Self {
239        self.supported_protocol_versions_config = Some(c);
240        self
241    }
242
243    pub fn with_global_state_hash_v1_enabled_callback(
244        mut self,
245        func: GlobalStateHashV1EnabledCallback,
246    ) -> Self {
247        self.global_state_hash_v1_enabled_config =
248            Some(GlobalStateHashV1EnabledConfig::PerValidator(func));
249        self
250    }
251
252    pub fn with_global_state_hash_v1_enabled_config(
253        mut self,
254        c: GlobalStateHashV1EnabledConfig,
255    ) -> Self {
256        self.global_state_hash_v1_enabled_config = Some(c);
257        self
258    }
259
260    pub fn with_authority_overload_config(mut self, c: AuthorityOverloadConfig) -> Self {
261        self.authority_overload_config = Some(c);
262        self
263    }
264
265    pub fn with_execution_cache_config(mut self, c: ExecutionCacheConfig) -> Self {
266        self.execution_cache_config = Some(c);
267        self
268    }
269
270    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
271        self.policy_config = config;
272        self
273    }
274
275    pub fn with_firewall_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
276        self.firewall_config = config;
277        self
278    }
279
280    pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
281        self.max_submit_position = Some(max_submit_position);
282        self
283    }
284
285    pub fn with_submit_delay_step_override_millis(
286        mut self,
287        submit_delay_step_override_millis: u64,
288    ) -> Self {
289        self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
290        self
291    }
292
293    pub fn with_admin_interface_address(mut self, admin_interface_address: SocketAddr) -> Self {
294        self.admin_interface_address = Some(admin_interface_address);
295        self
296    }
297
298    pub fn rng<N: rand::RngCore + rand::CryptoRng>(self, rng: N) -> ConfigBuilder<N> {
299        ConfigBuilder {
300            rng: Some(rng),
301            config_directory: self.config_directory,
302            supported_protocol_versions_config: self.supported_protocol_versions_config,
303            committee: self.committee,
304            genesis_config: self.genesis_config,
305            chain_override: self.chain_override,
306            reference_gas_price: self.reference_gas_price,
307            additional_objects: self.additional_objects,
308            num_unpruned_validators: self.num_unpruned_validators,
309            authority_overload_config: self.authority_overload_config,
310            execution_cache_config: self.execution_cache_config,
311            data_ingestion_dir: self.data_ingestion_dir,
312            policy_config: self.policy_config,
313            firewall_config: self.firewall_config,
314            max_submit_position: self.max_submit_position,
315            submit_delay_step_override_millis: self.submit_delay_step_override_millis,
316            global_state_hash_v1_enabled_config: self.global_state_hash_v1_enabled_config,
317            empty_validator_genesis: self.empty_validator_genesis,
318            admin_interface_address: self.admin_interface_address,
319        }
320    }
321
322    fn get_or_init_genesis_config(&mut self) -> &mut GenesisConfig {
323        if self.genesis_config.is_none() {
324            self.genesis_config = Some(GenesisConfig::for_local_testing());
325        }
326        self.genesis_config.as_mut().unwrap()
327    }
328
329    /// Avoid initializing validator genesis in memory.
330    ///
331    /// This allows callers to create the genesis blob,
332    /// and use a file pointer to configure the validators.
333    pub fn with_empty_validator_genesis(mut self) -> Self {
334        self.empty_validator_genesis = true;
335        self
336    }
337}
338
339impl<R: rand::RngCore + rand::CryptoRng> ConfigBuilder<R> {
340    // TODO right now we always randomize ports, we may want to have a default port
341    // configuration
342    pub fn build(self) -> NetworkConfig {
343        let committee = self.committee;
344
345        let mut rng = self.rng.unwrap();
346        let validators = match committee {
347            CommitteeConfig::Size(size) => {
348                // We always get fixed authority keys from this function (which is isolated from
349                // external test randomness because it uses a fixed seed). Necessary because
350                // some tests call `make_tx_certs_and_signed_effects`, which
351                // locally forges a cert using this same committee.
352                let (_, keys) = Committee::new_simple_test_committee_of_size(size.into());
353
354                keys.into_iter()
355                    .map(|authority_key| {
356                        let mut builder = ValidatorGenesisConfigBuilder::new()
357                            .with_authority_key_pair(authority_key);
358                        if let Some(rgp) = self.reference_gas_price {
359                            builder = builder.with_gas_price(rgp);
360                        }
361                        builder.build(&mut rng)
362                    })
363                    .collect::<Vec<_>>()
364            }
365
366            CommitteeConfig::Validators(v) => v,
367
368            CommitteeConfig::AccountKeys(keys) => {
369                // See above re fixed authority keys
370                let (_, authority_keys) = Committee::new_simple_test_committee_of_size(keys.len());
371                keys.into_iter()
372                    .zip(authority_keys)
373                    .map(|(account_key, authority_key)| {
374                        let mut builder = ValidatorGenesisConfigBuilder::new()
375                            .with_authority_key_pair(authority_key)
376                            .with_account_key_pair(account_key);
377                        if let Some(rgp) = self.reference_gas_price {
378                            builder = builder.with_gas_price(rgp);
379                        }
380                        builder.build(&mut rng)
381                    })
382                    .collect::<Vec<_>>()
383            }
384            CommitteeConfig::Deterministic((size, keys)) => {
385                // If no keys are provided, generate them.
386                let keys = keys.unwrap_or(
387                    (0..size.get())
388                        .map(|_| get_key_pair_from_rng(&mut rng).1)
389                        .collect(),
390                );
391
392                let mut configs = vec![];
393                for (i, key) in keys.into_iter().enumerate() {
394                    let port_offset = 8000 + i * 10;
395                    let mut builder = ValidatorGenesisConfigBuilder::new()
396                        .with_ip("127.0.0.1".to_owned())
397                        .with_account_key_pair(key)
398                        .with_deterministic_ports(port_offset as u16);
399                    if let Some(rgp) = self.reference_gas_price {
400                        builder = builder.with_gas_price(rgp);
401                    }
402                    configs.push(builder.build(&mut rng));
403                }
404                configs
405            }
406        };
407
408        let mut genesis_config = self
409            .genesis_config
410            .unwrap_or_else(GenesisConfig::for_local_testing);
411
412        let (account_keys, allocations) = genesis_config.generate_accounts(&mut rng).unwrap();
413
414        let token_distribution_schedule = {
415            let mut builder = TokenDistributionScheduleBuilder::new();
416            for allocation in allocations {
417                builder.add_allocation(allocation);
418            }
419            // Add allocations for each validator
420            for validator in &validators {
421                let account_key: PublicKey = validator.account_key_pair.public();
422                let address = Address::from(&account_key);
423                // Give each validator some gas so they can pay for their transactions.
424                let gas_coin = TokenAllocation {
425                    recipient_address: address,
426                    amount_nanos: DEFAULT_GAS_AMOUNT,
427                    staked_with_validator: None,
428                    staked_with_timelock_expiration: None,
429                };
430                let stake = TokenAllocation {
431                    recipient_address: address,
432                    amount_nanos: validator.stake,
433                    staked_with_validator: Some(address),
434                    staked_with_timelock_expiration: None,
435                };
436                builder.add_allocation(gas_coin);
437                builder.add_allocation(stake);
438            }
439            builder.build()
440        };
441
442        let GenesisBuildEffects {
443            genesis,
444            migration_tx_data,
445        } = {
446            let mut builder = iota_genesis_builder::Builder::new()
447                .with_parameters(genesis_config.parameters)
448                .add_objects(self.additional_objects);
449            for source in std::mem::take(&mut genesis_config.migration_sources) {
450                builder = builder.add_migration_source(source);
451            }
452
453            for (i, validator) in validators.iter().enumerate() {
454                let name = validator
455                    .name
456                    .clone()
457                    .unwrap_or(format!("validator-{i}").to_string());
458                let validator_info = validator.to_validator_info(name);
459                builder =
460                    builder.add_validator(validator_info.info, validator_info.proof_of_possession);
461            }
462
463            builder = builder.with_token_distribution_schedule(token_distribution_schedule);
464
465            // Add delegator to genesis builder.
466            if let Some(delegator) = genesis_config.delegator {
467                builder = builder.with_delegator(delegator);
468            }
469
470            for validator in &validators {
471                builder = builder.add_validator_signature(&validator.authority_key_pair);
472            }
473
474            builder.build()
475        };
476
477        if let Some(migration_tx_data) = migration_tx_data {
478            migration_tx_data
479                .save(
480                    self.config_directory
481                        .join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME),
482                )
483                .expect("Should be able to save the migration data");
484        }
485
486        let validator_configs = validators
487            .into_iter()
488            .enumerate()
489            .map(|(idx, mut validator)| {
490                let mut builder = ValidatorConfigBuilder::new()
491                    .with_config_directory(self.config_directory.clone())
492                    .with_policy_config(self.policy_config.clone())
493                    .with_firewall_config(self.firewall_config.clone());
494
495                if let Some(chain) = self.chain_override {
496                    builder = builder.with_chain_override(chain);
497                }
498
499                if let Some(max_submit_position) = self.max_submit_position {
500                    builder = builder.with_max_submit_position(max_submit_position);
501                }
502
503                if let Some(submit_delay_step_override_millis) =
504                    self.submit_delay_step_override_millis
505                {
506                    builder = builder
507                        .with_submit_delay_step_override_millis(submit_delay_step_override_millis);
508                }
509
510                if let Some(authority_overload_config) = &self.authority_overload_config {
511                    builder =
512                        builder.with_authority_overload_config(authority_overload_config.clone());
513                }
514
515                if let Some(execution_cache_config) = &self.execution_cache_config {
516                    builder = builder.with_execution_cache_config(execution_cache_config.clone());
517                }
518
519                if let Some(path) = &self.data_ingestion_dir {
520                    builder = builder.with_data_ingestion_dir(path.clone());
521                }
522
523                if let Some(spvc) = &self.supported_protocol_versions_config {
524                    let supported_versions = match spvc {
525                        ProtocolVersionsConfig::Default => {
526                            SupportedProtocolVersions::SYSTEM_DEFAULT
527                        }
528                        ProtocolVersionsConfig::Global(v) => *v,
529                        ProtocolVersionsConfig::PerValidator(func) => {
530                            func(idx, Some(validator.authority_key_pair.public().into()))
531                        }
532                    };
533                    builder = builder.with_supported_protocol_versions(supported_versions);
534                }
535                if let Some(num_unpruned_validators) = self.num_unpruned_validators {
536                    if idx < num_unpruned_validators {
537                        builder = builder.with_unpruned_checkpoints();
538                    }
539                }
540                if let Some(admin_interface_address) = self.admin_interface_address {
541                    validator.admin_interface_address = admin_interface_address;
542                }
543                if self.empty_validator_genesis {
544                    builder.build_without_genesis(validator)
545                } else {
546                    builder.build(validator, genesis.clone())
547                }
548            })
549            .collect();
550        NetworkConfig {
551            validator_configs,
552            genesis,
553            account_keys,
554        }
555    }
556}
557
558#[cfg(test)]
559mod tests {
560    use iota_config::node::Genesis;
561
562    #[test]
563    fn serialize_genesis_config_in_place() {
564        let dir = tempfile::TempDir::new().unwrap();
565        let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
566        let genesis = network_config.genesis;
567
568        let g = Genesis::new(genesis);
569
570        let mut s = serde_yaml::to_string(&g).unwrap();
571        let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
572        loaded_genesis
573            .genesis()
574            .unwrap()
575            .checkpoint_contents()
576            .digest(); // cache digest before comparing.
577        assert_eq!(g, loaded_genesis);
578
579        // If both in-place and file location are provided, prefer the in-place variant
580        s.push_str("\ngenesis-file-location: path/to/file");
581        let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
582        loaded_genesis
583            .genesis()
584            .unwrap()
585            .checkpoint_contents()
586            .digest(); // cache digest before comparing.
587        assert_eq!(g, loaded_genesis);
588    }
589
590    #[test]
591    fn load_genesis_config_from_file() {
592        let file = tempfile::NamedTempFile::new().unwrap();
593        let genesis_config = Genesis::new_from_file(file.path());
594
595        let dir = tempfile::TempDir::new().unwrap();
596        let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
597        let genesis = network_config.genesis;
598        genesis.save(file.path()).unwrap();
599
600        let loaded_genesis = genesis_config.genesis().unwrap();
601        loaded_genesis.checkpoint_contents().digest(); // cache digest before comparing.
602        assert_eq!(&genesis, loaded_genesis);
603    }
604}
605
606#[cfg(test)]
607mod test {
608    use std::{collections::HashSet, sync::Arc};
609
610    use iota_config::genesis::Genesis;
611    use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
612    use iota_types::{
613        epoch_data::EpochData,
614        gas::IotaGasStatus,
615        in_memory_storage::InMemoryStorage,
616        iota_system_state::IotaSystemStateTrait,
617        metrics::LimitsMetrics,
618        transaction::{CheckedInputObjects, TransactionDataAPI},
619    };
620
621    #[test]
622    fn roundtrip() {
623        let dir = tempfile::TempDir::new().unwrap();
624        let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
625        let genesis = network_config.genesis;
626
627        let s = serde_yaml::to_string(&genesis).unwrap();
628        let from_s: Genesis = serde_yaml::from_str(&s).unwrap();
629        // cache the digest so that the comparison succeeds.
630        from_s.checkpoint_contents().digest();
631        assert_eq!(genesis, from_s);
632    }
633
634    #[test]
635    fn genesis_transaction() {
636        let builder = crate::network_config_builder::ConfigBuilder::new_with_temp_dir();
637        let network_config = builder.build();
638        let genesis = network_config.genesis;
639        let protocol_version =
640            ProtocolVersion::new(genesis.iota_system_object().protocol_version());
641        let protocol_config = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown);
642
643        let genesis_transaction = genesis.transaction().clone();
644
645        let genesis_digest = *genesis_transaction.digest();
646
647        let silent = true;
648        let executor = iota_execution::executor(&protocol_config, silent, None)
649            .expect("Creating an executor should not fail here");
650
651        // Use a throwaway metrics registry for genesis transaction execution.
652        let registry = prometheus::Registry::new();
653        let metrics = Arc::new(LimitsMetrics::new(&registry));
654        let expensive_checks = false;
655        let certificate_deny_set = HashSet::new();
656        let epoch = EpochData::new_test();
657        let transaction_data = &genesis_transaction.data().intent_message().value;
658        let (kind, signer, mut gas_data) = transaction_data.execution_parts();
659        gas_data.objects = vec![];
660        let input_objects = CheckedInputObjects::new_for_genesis(vec![]);
661
662        let (_inner_temp_store, _, effects, _execution_error) = executor
663            .execute_transaction_to_effects(
664                &InMemoryStorage::new(Vec::new()),
665                &protocol_config,
666                metrics,
667                expensive_checks,
668                &certificate_deny_set,
669                &epoch.epoch_id(),
670                epoch.epoch_start_timestamp(),
671                input_objects,
672                gas_data,
673                IotaGasStatus::new_unmetered(),
674                kind,
675                signer,
676                genesis_digest,
677                &mut None,
678            );
679
680        assert_eq!(&effects, genesis.effects());
681    }
682}