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