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::{
13        AuthorityOverloadConfig, AuthorityStorePruningConfig, DBCheckpointConfig,
14        ExpensiveSafetyCheckConfig,
15    },
16    transaction_deny_config::TransactionDenyConfig,
17};
18use iota_network::randomness;
19use iota_protocol_config::{Chain, ProtocolConfig};
20use iota_swarm_config::{genesis_config::AccountConfig, network_config::NetworkConfig};
21use iota_types::{
22    base_types::AuthorityName, crypto::AuthorityKeyPair, digests::ChainIdentifier,
23    executable_transaction::VerifiedExecutableTransaction, iota_system_state::IotaSystemStateTrait,
24    object::Object, supported_protocol_versions::SupportedProtocolVersions,
25    transaction::VerifiedTransaction,
26};
27use prometheus_filtered::Registry;
28
29use super::{backpressure::BackpressureManager, epoch_start_configuration::EpochFlag};
30use crate::{
31    authority::{
32        AuthorityState, AuthorityStore,
33        authority_per_epoch_store::AuthorityPerEpochStore,
34        authority_store_pruner::ObjectsCompactionFilter,
35        authority_store_tables::{
36            AuthorityPerpetualTables, AuthorityPerpetualTablesOptions, AuthorityPrunerTables,
37        },
38        epoch_start_configuration::EpochStartConfiguration,
39    },
40    checkpoints::CheckpointStore,
41    epoch::{
42        committee_store::CommitteeStore, epoch_metrics::EpochMetrics, randomness::RandomnessManager,
43    },
44    execution_cache::build_execution_cache,
45    grpc_indexes::{GRPC_INDEXES_DIR, GrpcIndexesStore},
46    jsonrpc_index::IndexStore,
47    mock_consensus::{ConsensusMode, MockConsensusClient},
48    module_cache_metrics::ResolverMetrics,
49    signature_verifier::SignatureVerifierMetrics,
50};
51
52#[derive(Default, Clone)]
53pub struct TestAuthorityBuilder<'a> {
54    store_base_path: Option<PathBuf>,
55    store: Option<Arc<AuthorityStore>>,
56    transaction_deny_config: Option<TransactionDenyConfig>,
57    certificate_deny_config: Option<CertificateDenyConfig>,
58    protocol_config: Option<ProtocolConfig>,
59    reference_gas_price: Option<u64>,
60    node_keypair: Option<&'a AuthorityKeyPair>,
61    genesis: Option<&'a Genesis>,
62    starting_objects: Option<&'a [Object]>,
63    expensive_safety_checks: Option<ExpensiveSafetyCheckConfig>,
64    disable_indexer: bool,
65    accounts: Vec<AccountConfig>,
66    /// By default, we don't insert the genesis checkpoint, which isn't needed
67    /// by most tests.
68    insert_genesis_checkpoint: bool,
69    authority_overload_config: Option<AuthorityOverloadConfig>,
70    cache_config: Option<ExecutionCacheConfig>,
71    disable_execute_genesis_transactions: bool,
72    chain_override: Option<Chain>,
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 fn disable_execute_genesis_transactions(mut self) -> Self {
177        self.disable_execute_genesis_transactions = true;
178        self
179    }
180
181    pub fn with_chain_override(mut self, chain: Chain) -> Self {
182        self.chain_override = Some(chain);
183        self
184    }
185
186    pub async fn build(self) -> Arc<AuthorityState> {
187        let protocol_config = self.protocol_config.clone();
188
189        // Genesis must build the system framework at the binary format version it was
190        // compiled with. A test override that lowers `move_binary_format_version`
191        // must not apply while genesis verifies the system packages.
192        // Build genesis with the framework's binary format version
193        // restored, then apply the unmodified override below for transaction
194        // execution.
195        let local_network_config = {
196            let _genesis_guard = protocol_config.clone().map(|mut config| {
197                let framework_binary_format_version =
198                    ProtocolConfig::get_for_version(config.version, Chain::Unknown)
199                        .move_binary_format_version();
200                config.set_move_binary_format_version_for_testing(framework_binary_format_version);
201                ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone())
202            });
203
204            let mut local_network_config_builder =
205                iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
206                    .with_accounts(self.accounts)
207                    .with_reference_gas_price(self.reference_gas_price.unwrap_or(500));
208            if let Some(protocol_config) = &self.protocol_config {
209                local_network_config_builder =
210                    local_network_config_builder.with_protocol_version(protocol_config.version);
211            }
212            local_network_config_builder.build()
213        };
214
215        // `_guard` must be declared here so it is not dropped before
216        // `AuthorityPerEpochStore::new` is called
217        let _guard = protocol_config
218            .map(|config| ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone()));
219
220        let genesis = &self.genesis.unwrap_or(&local_network_config.genesis);
221        let genesis_committee = genesis.committee().unwrap();
222        let storage_dir = self
223            .store_base_path
224            .unwrap_or_else(|| iota_common::tempdir().keep());
225        let mut config = local_network_config.validator_configs()[0].clone();
226        let registry = Registry::new();
227        let mut pruner_db = None;
228        if config
229            .authority_store_pruning_config
230            .enable_compaction_filter
231        {
232            pruner_db = Some(Arc::new(AuthorityPrunerTables::open(
233                &storage_dir.join("store"),
234            )));
235        }
236        let compaction_filter = pruner_db
237            .clone()
238            .map(|db| ObjectsCompactionFilter::new(db, &registry));
239
240        let authority_store = match self.store {
241            Some(store) => store,
242            None => {
243                let perpetual_tables_options = AuthorityPerpetualTablesOptions {
244                    compaction_filter,
245                    ..Default::default()
246                };
247                let perpetual_tables = Arc::new(AuthorityPerpetualTables::open(
248                    &storage_dir.join("store"),
249                    Some(perpetual_tables_options),
250                ));
251                // unwrap ok - for testing only.
252                AuthorityStore::open_with_committee_for_testing(
253                    perpetual_tables,
254                    &genesis_committee,
255                    genesis,
256                )
257                .await
258                .unwrap()
259            }
260        };
261        if let Some(cache_config) = self.cache_config {
262            config.execution_cache_config = cache_config;
263        }
264
265        let keypair = if let Some(keypair) = self.node_keypair {
266            keypair.copy()
267        } else {
268            config.authority_key_pair().copy()
269        };
270
271        let secret = Arc::pin(keypair.copy());
272        let name: AuthorityName = secret.public().into();
273        let cache_metrics = Arc::new(ResolverMetrics::new(&registry));
274        let signature_verifier_metrics = SignatureVerifierMetrics::new(&registry);
275        let epoch_flags = EpochFlag::default_flags_for_new_epoch(&config);
276        let epoch_start_configuration = EpochStartConfiguration::new(
277            genesis.iota_system_object().into_epoch_start_state(),
278            *genesis.checkpoint().digest(),
279            &genesis.objects(),
280            epoch_flags,
281        )
282        .unwrap();
283        let expensive_safety_checks = self.expensive_safety_checks.unwrap_or_default();
284
285        let checkpoint_store = CheckpointStore::new(&storage_dir.join("checkpoints"));
286        let backpressure_manager =
287            BackpressureManager::new_from_checkpoint_store(&checkpoint_store);
288
289        let cache_traits = build_execution_cache(
290            &config.execution_cache_config,
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            &storage_dir.join("store"),
306            None,
307            EpochMetrics::new(&registry),
308            epoch_start_configuration,
309            cache_traits.backing_package_store.clone(),
310            cache_metrics,
311            signature_verifier_metrics,
312            &expensive_safety_checks,
313            (chain_id, chain),
314            checkpoint_store
315                .get_highest_executed_checkpoint_seq_number()
316                .unwrap()
317                .unwrap_or(0),
318        )
319        .expect("failed to create authority per epoch store");
320        let committee_store = Arc::new(CommitteeStore::new(
321            storage_dir.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                storage_dir.join("indexes"),
338                &registry,
339                epoch_store
340                    .protocol_config()
341                    .max_move_identifier_len_as_option(),
342            )))
343        };
344        let grpc_indexes_store = if self.disable_indexer {
345            None
346        } else {
347            Some(Arc::new(
348                GrpcIndexesStore::new(
349                    storage_dir.join(GRPC_INDEXES_DIR),
350                    Arc::clone(&authority_store),
351                    &checkpoint_store,
352                )
353                .await,
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        let policy_config = config.policy_config.clone();
369        let firewall_config = config.firewall_config.clone();
370
371        let state = AuthorityState::new(
372            name,
373            secret,
374            SupportedProtocolVersions::SYSTEM_DEFAULT,
375            authority_store,
376            cache_traits,
377            epoch_store.clone(),
378            committee_store,
379            index_store,
380            grpc_indexes_store,
381            checkpoint_store,
382            &registry,
383            genesis.objects(),
384            &DBCheckpointConfig::default(),
385            config.clone(),
386            None,
387            chain_identifier,
388            pruner_db,
389            None,
390            policy_config,
391            firewall_config,
392        )
393        .await;
394
395        // Set up randomness with no-op consensus (DKG will not complete).
396        let consensus_client = Box::new(MockConsensusClient::new(
397            Arc::downgrade(&state),
398            ConsensusMode::Noop,
399        ));
400        let randomness_manager = RandomnessManager::try_new(
401            Arc::downgrade(&epoch_store),
402            consensus_client,
403            randomness::Handle::new_stub(),
404            &keypair,
405        )
406        .await;
407        if let Ok(randomness_manager) = randomness_manager {
408            // Randomness might fail if test configuration does not permit DKG init.
409            // In that case, skip setting it up.
410            epoch_store
411                .set_randomness_manager(randomness_manager)
412                .await
413                .unwrap();
414        }
415
416        if !self.disable_execute_genesis_transactions {
417            // For any type of local testing that does not actually spawn a node, the
418            // checkpoint executor won't be started, which means we won't actually
419            // execute the genesis transaction. In that case, the genesis objects
420            // (e.g. all the genesis test coins) won't be accessible. Executing it
421            // explicitly makes sure all genesis objects are ready for use.
422            state
423                .try_execute_immediately(
424                    &VerifiedExecutableTransaction::new_from_checkpoint(
425                        VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
426                        genesis.epoch(),
427                        genesis.checkpoint().sequence_number,
428                    ),
429                    None,
430                    &state.epoch_store_for_testing(),
431                )
432                .unwrap();
433
434            let batch = state.get_cache_commit().build_db_batch(
435                epoch_store.epoch(),
436                genesis.checkpoint().sequence_number,
437                &[*genesis.transaction().digest()],
438            );
439
440            state.get_cache_commit().commit_transaction_outputs(
441                epoch_store.epoch(),
442                batch,
443                &[*genesis.transaction().digest()],
444            );
445        }
446
447        // We want to insert these objects directly instead of relying on genesis
448        // because genesis process would set the previous transaction field for
449        // these objects, which would change their object digest. This makes it
450        // difficult to write tests that want to use these objects directly.
451        // TODO: we should probably have a better way to do this.
452        if let Some(starting_objects) = self.starting_objects {
453            state
454                .insert_objects_unsafe_for_testing_only(starting_objects)
455                .await;
456        };
457        state
458    }
459}