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