Skip to main content

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