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