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