iota_core/authority/
test_authority_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::{path::PathBuf, sync::Arc};
6
7use fastcrypto::traits::KeyPair;
8use iota_archival::reader::ArchiveReaderBalancer;
9use iota_config::{
10    ExecutionCacheConfig,
11    certificate_deny_config::CertificateDenyConfig,
12    genesis::Genesis,
13    node::{
14        AuthorityOverloadConfig, AuthorityStorePruningConfig, DBCheckpointConfig,
15        ExpensiveSafetyCheckConfig,
16    },
17    transaction_deny_config::TransactionDenyConfig,
18};
19use iota_macros::nondeterministic;
20use iota_network::randomness;
21use iota_protocol_config::ProtocolConfig;
22use iota_storage::IndexStore;
23use iota_swarm_config::{genesis_config::AccountConfig, network_config::NetworkConfig};
24use iota_types::{
25    base_types::{AuthorityName, ObjectID},
26    crypto::AuthorityKeyPair,
27    digests::ChainIdentifier,
28    executable_transaction::VerifiedExecutableTransaction,
29    iota_system_state::IotaSystemStateTrait,
30    object::Object,
31    supported_protocol_versions::SupportedProtocolVersions,
32    transaction::VerifiedTransaction,
33};
34use prometheus::Registry;
35
36use super::epoch_start_configuration::EpochFlag;
37use crate::{
38    authority::{
39        AuthorityState, AuthorityStore, authority_per_epoch_store::AuthorityPerEpochStore,
40        authority_store_tables::AuthorityPerpetualTables,
41        epoch_start_configuration::EpochStartConfiguration,
42    },
43    checkpoints::CheckpointStore,
44    epoch::{
45        committee_store::CommitteeStore, epoch_metrics::EpochMetrics, randomness::RandomnessManager,
46    },
47    execution_cache::build_execution_cache,
48    mock_consensus::{ConsensusMode, MockConsensusClient},
49    module_cache_metrics::ResolverMetrics,
50    rest_index::RestIndexStore,
51    signature_verifier::SignatureVerifierMetrics,
52};
53
54#[derive(Default, Clone)]
55pub struct TestAuthorityBuilder<'a> {
56    store_base_path: Option<PathBuf>,
57    store: Option<Arc<AuthorityStore>>,
58    transaction_deny_config: Option<TransactionDenyConfig>,
59    certificate_deny_config: Option<CertificateDenyConfig>,
60    protocol_config: Option<ProtocolConfig>,
61    reference_gas_price: Option<u64>,
62    node_keypair: Option<&'a AuthorityKeyPair>,
63    genesis: Option<&'a Genesis>,
64    starting_objects: Option<&'a [Object]>,
65    expensive_safety_checks: Option<ExpensiveSafetyCheckConfig>,
66    disable_indexer: bool,
67    accounts: Vec<AccountConfig>,
68    /// By default, we don't insert the genesis checkpoint, which isn't needed
69    /// by most tests.
70    insert_genesis_checkpoint: bool,
71    authority_overload_config: Option<AuthorityOverloadConfig>,
72    cache_config: Option<ExecutionCacheConfig>,
73}
74
75impl<'a> TestAuthorityBuilder<'a> {
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    pub fn with_store_base_path(mut self, path: PathBuf) -> Self {
81        assert!(self.store_base_path.replace(path).is_none());
82        self
83    }
84
85    pub fn with_starting_objects(mut self, objects: &'a [Object]) -> Self {
86        assert!(self.starting_objects.replace(objects).is_none());
87        self
88    }
89
90    pub fn with_store(mut self, store: Arc<AuthorityStore>) -> Self {
91        assert!(self.store.replace(store).is_none());
92        self
93    }
94
95    pub fn with_transaction_deny_config(mut self, config: TransactionDenyConfig) -> Self {
96        assert!(self.transaction_deny_config.replace(config).is_none());
97        self
98    }
99
100    pub fn with_certificate_deny_config(mut self, config: CertificateDenyConfig) -> Self {
101        assert!(self.certificate_deny_config.replace(config).is_none());
102        self
103    }
104
105    pub fn with_protocol_config(mut self, config: ProtocolConfig) -> Self {
106        assert!(self.protocol_config.replace(config).is_none());
107        self
108    }
109
110    pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
111        // If genesis is already set then setting rgp is meaningless since it will be
112        // overwritten.
113        assert!(self.genesis.is_none());
114        assert!(
115            self.reference_gas_price
116                .replace(reference_gas_price)
117                .is_none()
118        );
119        self
120    }
121
122    pub fn with_genesis_and_keypair(
123        mut self,
124        genesis: &'a Genesis,
125        keypair: &'a AuthorityKeyPair,
126    ) -> Self {
127        assert!(self.genesis.replace(genesis).is_none());
128        assert!(self.node_keypair.replace(keypair).is_none());
129        self
130    }
131
132    pub fn with_keypair(mut self, keypair: &'a AuthorityKeyPair) -> Self {
133        assert!(self.node_keypair.replace(keypair).is_none());
134        self
135    }
136
137    /// When providing a network config, we will use the \node_idx validator's
138    /// key as the keypair for the new node.
139    pub fn with_network_config(self, config: &'a NetworkConfig, node_idx: usize) -> Self {
140        self.with_genesis_and_keypair(
141            &config.genesis,
142            config.validator_configs()[node_idx].authority_key_pair(),
143        )
144    }
145
146    pub fn disable_indexer(mut self) -> Self {
147        self.disable_indexer = true;
148        self
149    }
150
151    pub fn insert_genesis_checkpoint(mut self) -> Self {
152        self.insert_genesis_checkpoint = true;
153        self
154    }
155
156    pub fn with_expensive_safety_checks(mut self, config: ExpensiveSafetyCheckConfig) -> Self {
157        assert!(self.expensive_safety_checks.replace(config).is_none());
158        self
159    }
160
161    pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
162        self.accounts = accounts;
163        self
164    }
165
166    pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
167        assert!(self.authority_overload_config.replace(config).is_none());
168        self
169    }
170
171    pub fn with_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
172        self.cache_config = Some(config);
173        self
174    }
175
176    pub async fn build(self) -> Arc<AuthorityState> {
177        let mut local_network_config_builder =
178            iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
179                .with_accounts(self.accounts)
180                .with_reference_gas_price(self.reference_gas_price.unwrap_or(500));
181        if let Some(protocol_config) = &self.protocol_config {
182            local_network_config_builder =
183                local_network_config_builder.with_protocol_version(protocol_config.version);
184        }
185
186        let local_network_config = local_network_config_builder.build();
187        let genesis = &self.genesis.unwrap_or(&local_network_config.genesis);
188        let genesis_committee = genesis.committee().unwrap();
189        let path = self.store_base_path.unwrap_or_else(|| {
190            let dir = std::env::temp_dir();
191            let store_base_path =
192                dir.join(format!("DB_{:?}", nondeterministic!(ObjectID::random())));
193            std::fs::create_dir(&store_base_path).unwrap();
194            store_base_path
195        });
196        let authority_store = match self.store {
197            Some(store) => store,
198            None => {
199                let perpetual_tables =
200                    Arc::new(AuthorityPerpetualTables::open(&path.join("store"), None));
201                // unwrap ok - for testing only.
202                AuthorityStore::open_with_committee_for_testing(
203                    perpetual_tables,
204                    &genesis_committee,
205                    genesis,
206                    0,
207                )
208                .await
209                .unwrap()
210            }
211        };
212        let mut config = local_network_config.validator_configs()[0].clone();
213        if let Some(cache_config) = self.cache_config {
214            config.execution_cache = cache_config;
215        }
216
217        let keypair = if let Some(keypair) = self.node_keypair {
218            keypair.copy()
219        } else {
220            config.authority_key_pair().copy()
221        };
222
223        let secret = Arc::pin(keypair.copy());
224        let name: AuthorityName = secret.public().into();
225        let registry = Registry::new();
226        let cache_metrics = Arc::new(ResolverMetrics::new(&registry));
227        let signature_verifier_metrics = SignatureVerifierMetrics::new(&registry);
228        // `_guard` must be declared here so it is not dropped before
229        // `AuthorityPerEpochStore::new` is called
230        let _guard = self
231            .protocol_config
232            .map(|config| ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone()));
233        let epoch_flags = EpochFlag::default_flags_for_new_epoch(&config);
234        let epoch_start_configuration = EpochStartConfiguration::new(
235            genesis.iota_system_object().into_epoch_start_state(),
236            *genesis.checkpoint().digest(),
237            &genesis.objects(),
238            epoch_flags,
239        )
240        .unwrap();
241        let expensive_safety_checks = self.expensive_safety_checks.unwrap_or_default();
242
243        let cache_traits =
244            build_execution_cache(&epoch_start_configuration, &registry, &authority_store);
245
246        let epoch_store = AuthorityPerEpochStore::new(
247            name,
248            Arc::new(genesis_committee.clone()),
249            &path.join("store"),
250            None,
251            EpochMetrics::new(&registry),
252            epoch_start_configuration,
253            cache_traits.backing_package_store.clone(),
254            cache_traits.object_store.clone(),
255            cache_metrics,
256            signature_verifier_metrics,
257            &expensive_safety_checks,
258            ChainIdentifier::from(*genesis.checkpoint().digest()),
259        );
260        let committee_store = Arc::new(CommitteeStore::new(
261            path.join("epochs"),
262            &genesis_committee,
263            None,
264        ));
265
266        let checkpoint_store = CheckpointStore::new(&path.join("checkpoints"));
267        if self.insert_genesis_checkpoint {
268            checkpoint_store.insert_genesis_checkpoint(
269                genesis.checkpoint(),
270                genesis.checkpoint_contents().clone(),
271                &epoch_store,
272            );
273        }
274        let index_store = if self.disable_indexer {
275            None
276        } else {
277            Some(Arc::new(IndexStore::new(
278                path.join("indexes"),
279                &registry,
280                epoch_store
281                    .protocol_config()
282                    .max_move_identifier_len_as_option(),
283                false,
284            )))
285        };
286        let rest_index = if self.disable_indexer {
287            None
288        } else {
289            Some(Arc::new(RestIndexStore::new(
290                path.join("rest_index"),
291                &authority_store,
292                &checkpoint_store,
293                &epoch_store,
294                &cache_traits.backing_package_store,
295            )))
296        };
297
298        let transaction_deny_config = self.transaction_deny_config.unwrap_or_default();
299        let certificate_deny_config = self.certificate_deny_config.unwrap_or_default();
300        let authority_overload_config = self.authority_overload_config.unwrap_or_default();
301        let pruning_config = AuthorityStorePruningConfig::default();
302
303        config.transaction_deny_config = transaction_deny_config;
304        config.certificate_deny_config = certificate_deny_config;
305        config.authority_overload_config = authority_overload_config;
306        config.authority_store_pruning_config = pruning_config;
307
308        let state = AuthorityState::new(
309            name,
310            secret,
311            SupportedProtocolVersions::SYSTEM_DEFAULT,
312            authority_store,
313            cache_traits,
314            epoch_store.clone(),
315            committee_store,
316            index_store,
317            rest_index,
318            checkpoint_store,
319            &registry,
320            genesis.objects(),
321            &DBCheckpointConfig::default(),
322            config.clone(),
323            usize::MAX,
324            ArchiveReaderBalancer::default(),
325            None,
326        )
327        .await;
328
329        // Set up randomness with no-op consensus (DKG will not complete).
330        let consensus_client = Box::new(MockConsensusClient::new(
331            Arc::downgrade(&state),
332            ConsensusMode::Noop,
333        ));
334        let randomness_manager = RandomnessManager::try_new(
335            Arc::downgrade(&epoch_store),
336            consensus_client,
337            randomness::Handle::new_stub(),
338            &keypair,
339        )
340        .await;
341        if let Some(randomness_manager) = randomness_manager {
342            // Randomness might fail if test configuration does not permit DKG init.
343            // In that case, skip setting it up.
344            epoch_store
345                .set_randomness_manager(randomness_manager)
346                .await
347                .unwrap();
348        }
349
350        // For any type of local testing that does not actually spawn a node, the
351        // checkpoint executor won't be started, which means we won't actually
352        // execute the genesis transaction. In that case, the genesis objects
353        // (e.g. all the genesis test coins) won't be accessible. Executing it
354        // explicitly makes sure all genesis objects are ready for use.
355        state
356            .try_execute_immediately(
357                &VerifiedExecutableTransaction::new_from_checkpoint(
358                    VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
359                    genesis.epoch(),
360                    genesis.checkpoint().sequence_number,
361                ),
362                None,
363                &state.epoch_store_for_testing(),
364            )
365            .await
366            .unwrap();
367
368        // We want to insert these objects directly instead of relying on genesis
369        // because genesis process would set the previous transaction field for
370        // these objects, which would change their object digest. This makes it
371        // difficult to write tests that want to use these objects directly.
372        // TODO: we should probably have a better way to do this.
373        if let Some(starting_objects) = self.starting_objects {
374            state
375                .insert_objects_unsafe_for_testing_only(starting_objects)
376                .await
377                .unwrap();
378        };
379        state
380    }
381}