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::{collections::BTreeSet, 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, authority_per_epoch_store::AuthorityPerEpochStore,
33        authority_store_tables::AuthorityPerpetualTables,
34        epoch_start_configuration::EpochStartConfiguration,
35    },
36    checkpoints::CheckpointStore,
37    epoch::{
38        committee_store::CommitteeStore, epoch_metrics::EpochMetrics, randomness::RandomnessManager,
39    },
40    execution_cache::build_execution_cache,
41    mock_consensus::{ConsensusMode, MockConsensusClient},
42    module_cache_metrics::ResolverMetrics,
43    rpc_indexes::{
44        RpcIndexesStore,
45        schema::{IndexGroup, RPC_INDEXES_DIR},
46    },
47    signature_verifier::SignatureVerifierMetrics,
48};
49
50#[derive(Default, Clone)]
51pub struct TestAuthorityBuilder<'a> {
52    store_base_path: Option<PathBuf>,
53    store: Option<Arc<AuthorityStore>>,
54    transaction_deny_config: Option<TransactionDenyConfig>,
55    certificate_deny_config: Option<CertificateDenyConfig>,
56    verifier_signing_config: Option<VerifierSigningConfig>,
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    num_epochs_to_retain: Option<u64>,
73    num_epochs_to_retain_for_checkpoints: Option<u64>,
74    num_epochs_to_retain_for_indexes: Option<u64>,
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_verifier_signing_config(mut self, config: VerifierSigningConfig) -> Self {
108        assert!(self.verifier_signing_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_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    /// The number of historic epochs whose superseded object versions are
189    /// retained beyond the current one. `u64::MAX` retains all of them.
190    pub fn with_num_epochs_to_retain(mut self, num_epochs_to_retain: u64) -> Self {
191        assert!(
192            self.num_epochs_to_retain
193                .replace(num_epochs_to_retain)
194                .is_none()
195        );
196        self
197    }
198
199    /// The number of epochs of transaction and checkpoint history retained,
200    /// counting the epoch the node has last executed. Left unset, the
201    /// pruning config's own default applies.
202    pub fn with_num_epochs_to_retain_for_checkpoints(mut self, num_epochs_to_retain: u64) -> Self {
203        assert!(
204            self.num_epochs_to_retain_for_checkpoints
205                .replace(num_epochs_to_retain)
206                .is_none()
207        );
208        self
209    }
210
211    /// The number of epochs of RPC index history retained, counting the
212    /// epoch the node is in. Left unset, index pruning is off.
213    pub fn with_num_epochs_to_retain_for_indexes(mut self, num_epochs_to_retain: u64) -> Self {
214        assert!(
215            self.num_epochs_to_retain_for_indexes
216                .replace(num_epochs_to_retain)
217                .is_none()
218        );
219        self
220    }
221
222    pub fn with_chain_override(mut self, chain: Chain) -> Self {
223        self.chain_override = Some(chain);
224        self
225    }
226
227    pub async fn build(self) -> Arc<AuthorityState> {
228        let protocol_config = self.protocol_config.clone();
229
230        // Genesis must build the system framework at the binary format version it was
231        // compiled with. A test override that lowers `move_binary_format_version`
232        // must not apply while genesis verifies the system packages.
233        // Build genesis with the framework's binary format version
234        // restored, then apply the unmodified override below for transaction
235        // execution.
236        let local_network_config = {
237            let _genesis_guard = protocol_config.clone().map(|mut config| {
238                let framework_binary_format_version =
239                    ProtocolConfig::get_for_version(config.version, Chain::Unknown)
240                        .move_binary_format_version();
241                config.set_move_binary_format_version_for_testing(framework_binary_format_version);
242                ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone())
243            });
244
245            let mut local_network_config_builder =
246                iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
247                    .with_accounts(self.accounts)
248                    .with_reference_gas_price(self.reference_gas_price.unwrap_or(500));
249            if let Some(protocol_config) = &self.protocol_config {
250                local_network_config_builder =
251                    local_network_config_builder.with_protocol_version(protocol_config.version);
252            }
253            local_network_config_builder.build()
254        };
255
256        // `_guard` must be declared here so it is not dropped before
257        // `AuthorityPerEpochStore::new` is called
258        let _guard = protocol_config
259            .map(|config| ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone()));
260
261        let genesis = &self.genesis.unwrap_or(&local_network_config.genesis);
262        let genesis_committee = genesis.committee().unwrap();
263        let storage_dir = self
264            .store_base_path
265            .unwrap_or_else(|| iota_common::tempdir().keep());
266        let mut config = local_network_config.validator_configs()[0].clone();
267        let registry = Registry::new();
268
269        let authority_store = match self.store {
270            Some(store) => store,
271            None => {
272                // unwrap ok - for testing only.
273                let (perpetual_tables, historic_objects, historic_ledger, epoch_markers) =
274                    AuthorityPerpetualTables::open_with_historic_objects(
275                        &storage_dir.join("store"),
276                        None,
277                    )
278                    .unwrap();
279                AuthorityStore::open_with_committee_for_testing(
280                    Arc::new(perpetual_tables),
281                    Arc::new(historic_objects),
282                    Arc::new(historic_ledger),
283                    Arc::new(epoch_markers),
284                    &genesis_committee,
285                    genesis,
286                )
287                .await
288                .unwrap()
289            }
290        };
291        if let Some(cache_config) = self.cache_config {
292            config.execution_cache_config = cache_config;
293        }
294
295        let keypair = if let Some(keypair) = self.node_keypair {
296            keypair.copy()
297        } else {
298            config.authority_key_pair().copy()
299        };
300
301        let secret = Arc::pin(keypair.copy());
302        let name: AuthorityName = secret.public().into();
303        let cache_metrics = Arc::new(ResolverMetrics::new(&registry));
304        let signature_verifier_metrics = SignatureVerifierMetrics::new(&registry);
305        let epoch_flags = EpochFlag::default_flags_for_new_epoch(&config);
306        let epoch_start_configuration = EpochStartConfiguration::new(
307            genesis.iota_system_object().into_epoch_start_state(),
308            *genesis.checkpoint().digest(),
309            &genesis.objects(),
310            epoch_flags,
311        )
312        .unwrap();
313        let expensive_safety_checks = self.expensive_safety_checks.unwrap_or_default();
314
315        let checkpoint_store = CheckpointStore::new(&storage_dir.join("checkpoints"));
316        let backpressure_manager =
317            BackpressureManager::new_from_checkpoint_store(&checkpoint_store);
318
319        let cache_traits = build_execution_cache(
320            &config.execution_cache_config,
321            &registry,
322            &authority_store,
323            backpressure_manager.clone(),
324        );
325
326        let chain_id = ChainIdentifier::from(*genesis.checkpoint().digest());
327        let chain = match self.chain_override {
328            Some(chain) => chain,
329            None => chain_id.chain(),
330        };
331
332        let epoch_store = AuthorityPerEpochStore::new(
333            name,
334            Arc::new(genesis_committee.clone()),
335            &storage_dir.join("store"),
336            None,
337            EpochMetrics::new(&registry),
338            epoch_start_configuration,
339            cache_traits.backing_package_store.clone(),
340            cache_metrics,
341            signature_verifier_metrics,
342            &expensive_safety_checks,
343            (chain_id, chain),
344            checkpoint_store
345                .get_highest_executed_checkpoint_seq_number()
346                .unwrap()
347                .unwrap_or(0),
348        )
349        .expect("failed to create authority per epoch store");
350        let committee_store = Arc::new(CommitteeStore::new(
351            storage_dir.join("epochs"),
352            &genesis_committee,
353            None,
354        ));
355
356        if self.insert_genesis_checkpoint {
357            checkpoint_store.insert_genesis_checkpoint(
358                genesis.checkpoint(),
359                genesis.checkpoint_contents().clone(),
360                &epoch_store,
361            );
362        }
363        let rpc_indexes_store = if self.disable_indexer {
364            None
365        } else {
366            Some(
367                RpcIndexesStore::new(
368                    storage_dir.join(RPC_INDEXES_DIR),
369                    None,
370                    &registry,
371                    BTreeSet::from([IndexGroup::JsonRpc, IndexGroup::Grpc]),
372                    epoch_store
373                        .protocol_config()
374                        .max_move_identifier_len_as_option(),
375                    self.num_epochs_to_retain_for_indexes,
376                    &authority_store,
377                    &checkpoint_store,
378                    Arc::default(),
379                )
380                .await
381                .expect("failed to open the RPC index store"),
382            )
383        };
384
385        let transaction_deny_config = self.transaction_deny_config.unwrap_or_default();
386        let certificate_deny_config = self.certificate_deny_config.unwrap_or_default();
387        let verifier_signing_config = self.verifier_signing_config.unwrap_or_default();
388        let authority_overload_config = self.authority_overload_config.unwrap_or_default();
389        let mut pruning_config = AuthorityStorePruningConfig::default();
390        if let Some(num_epochs_to_retain) = self.num_epochs_to_retain {
391            pruning_config.set_num_epochs_to_retain(num_epochs_to_retain);
392        }
393        if let Some(num_epochs_to_retain) = self.num_epochs_to_retain_for_checkpoints {
394            pruning_config.set_num_epochs_to_retain_for_checkpoints(Some(num_epochs_to_retain));
395        }
396
397        config.transaction_deny_config = transaction_deny_config;
398        config.certificate_deny_config = certificate_deny_config;
399        config.verifier_signing_config = verifier_signing_config;
400        config.authority_overload_config = authority_overload_config;
401        config.authority_store_pruning_config = pruning_config;
402
403        let chain_identifier = ChainIdentifier::from(*genesis.checkpoint().digest());
404        let policy_config = config.policy_config.clone();
405
406        let state = AuthorityState::new(
407            name,
408            secret,
409            SupportedProtocolVersions::SYSTEM_DEFAULT,
410            authority_store,
411            cache_traits,
412            epoch_store.clone(),
413            committee_store,
414            rpc_indexes_store,
415            checkpoint_store,
416            &registry,
417            config.clone(),
418            None,
419            chain_identifier,
420            policy_config,
421            None,
422        )
423        .await;
424
425        // Set up randomness with no-op consensus (DKG will not complete).
426        let consensus_client = Box::new(MockConsensusClient::new(
427            Arc::downgrade(&state),
428            ConsensusMode::Noop,
429        ));
430        let randomness_manager = RandomnessManager::try_new(
431            Arc::downgrade(&epoch_store),
432            consensus_client,
433            randomness::Handle::new_stub(),
434            &keypair,
435        )
436        .await;
437        if let Ok(randomness_manager) = randomness_manager {
438            // Randomness might fail if test configuration does not permit DKG init.
439            // In that case, skip setting it up.
440            epoch_store
441                .set_randomness_manager(randomness_manager)
442                .await
443                .unwrap();
444        }
445
446        if !self.disable_execute_genesis_transactions {
447            // For any type of local testing that does not actually spawn a node, the
448            // checkpoint executor won't be started, which means we won't actually
449            // execute the genesis transaction. In that case, the genesis objects
450            // (e.g. all the genesis test coins) won't be accessible. Executing it
451            // explicitly makes sure all genesis objects are ready for use.
452            state
453                .try_execute_immediately(
454                    &VerifiedExecutableTransaction::new_from_checkpoint(
455                        VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
456                        genesis.epoch(),
457                        genesis.checkpoint().sequence_number,
458                    ),
459                    ExecutionEnv::new(),
460                    &state.epoch_store_for_testing(),
461                )
462                .unwrap();
463
464            let batch = state.get_cache_commit().build_db_batch(
465                epoch_store.epoch(),
466                genesis.checkpoint().sequence_number,
467                &[*genesis.transaction().digest()],
468            );
469
470            state.get_cache_commit().commit_transaction_outputs(
471                epoch_store.epoch(),
472                batch,
473                &[*genesis.transaction().digest()],
474            );
475        }
476
477        // We want to insert these objects directly instead of relying on genesis
478        // because genesis process would set the previous transaction field for
479        // these objects, which would change their object digest. This makes it
480        // difficult to write tests that want to use these objects directly.
481        // TODO: we should probably have a better way to do this.
482        if let Some(starting_objects) = self.starting_objects {
483            state
484                .insert_objects_unsafe_for_testing_only(starting_objects)
485                .await;
486        };
487        state
488    }
489}