Skip to main content

sui_core/authority/
test_authority_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::ExecutionEnv;
5use super::backpressure::BackpressureManager;
6use super::epoch_start_configuration::EpochFlag;
7use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
8use crate::authority::authority_store_pruner::PrunerWatermarks;
9use crate::authority::authority_store_tables::{
10    AuthorityPerpetualTables, AuthorityPerpetualTablesOptions,
11};
12use crate::authority::epoch_start_configuration::EpochStartConfiguration;
13use crate::authority::submitted_transaction_cache::SubmittedTransactionCacheMetrics;
14use crate::authority::{AuthorityState, AuthorityStore};
15use crate::checkpoints::CheckpointStore;
16use crate::epoch::committee_store::CommitteeStore;
17use crate::epoch::epoch_metrics::EpochMetrics;
18use crate::epoch::randomness::RandomnessManager;
19use crate::execution_cache::build_execution_cache;
20use crate::mock_consensus::{ConsensusMode, MockConsensusClient};
21use crate::module_cache_metrics::ResolverMetrics;
22use crate::randomness_round_receiver::RandomnessRoundReceiverHandle;
23use crate::signature_verifier::SignatureVerifierMetrics;
24use fastcrypto::traits::KeyPair;
25use prometheus::Registry;
26use std::path::PathBuf;
27use std::sync::Arc;
28use sui_config::ExecutionCacheConfig;
29use sui_config::certificate_deny_config::CertificateDenyConfig;
30use sui_config::genesis::Genesis;
31use sui_config::node::AuthorityOverloadConfig;
32use sui_config::node::{
33    AuthorityStorePruningConfig, DBCheckpointConfig, ExpensiveSafetyCheckConfig,
34};
35use sui_config::transaction_deny_config::TransactionDenyConfig;
36use sui_macros::nondeterministic;
37use sui_network::randomness;
38use sui_protocol_config::{Chain, ProtocolConfig};
39use sui_swarm_config::genesis_config::AccountConfig;
40use sui_swarm_config::network_config::NetworkConfig;
41use sui_types::base_types::{AuthorityName, ObjectID};
42use sui_types::crypto::AuthorityKeyPair;
43use sui_types::digests::ChainIdentifier;
44use sui_types::executable_transaction::VerifiedExecutableTransaction;
45use sui_types::object::Object;
46use sui_types::sui_system_state::SuiSystemStateTrait;
47use sui_types::supported_protocol_versions::SupportedProtocolVersions;
48use sui_types::transaction::VerifiedTransaction;
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    protocol_config: Option<ProtocolConfig>,
57    reference_gas_price: Option<u64>,
58    node_keypair: Option<&'a AuthorityKeyPair>,
59    genesis: Option<&'a Genesis>,
60    /// Pre-built network config to avoid rebuilding genesis
61    network_config: Option<&'a NetworkConfig>,
62    starting_objects: Option<&'a [Object]>,
63    expensive_safety_checks: Option<ExpensiveSafetyCheckConfig>,
64    accounts: Vec<AccountConfig>,
65    /// By default, we don't insert the genesis checkpoint, which isn't needed by most tests.
66    insert_genesis_checkpoint: bool,
67    authority_overload_config: Option<AuthorityOverloadConfig>,
68    cache_config: Option<ExecutionCacheConfig>,
69    chain_override: Option<Chain>,
70    dev_inspect_disabled: bool,
71    recent_submission_dedup_window_ms: Option<u64>,
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_dev_inspect_disabled(mut self) -> Self {
100        self.dev_inspect_disabled = true;
101        self
102    }
103
104    pub fn with_certificate_deny_config(mut self, config: CertificateDenyConfig) -> Self {
105        assert!(self.certificate_deny_config.replace(config).is_none());
106        self
107    }
108
109    pub fn with_protocol_config(mut self, config: ProtocolConfig) -> Self {
110        assert!(self.protocol_config.replace(config).is_none());
111        self
112    }
113
114    pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
115        // If genesis is already set then setting rgp is meaningless since it will be overwritten.
116        assert!(self.genesis.is_none());
117        assert!(
118            self.reference_gas_price
119                .replace(reference_gas_price)
120                .is_none()
121        );
122        self
123    }
124
125    pub fn with_genesis_and_keypair(
126        mut self,
127        genesis: &'a Genesis,
128        keypair: &'a AuthorityKeyPair,
129    ) -> Self {
130        assert!(self.genesis.replace(genesis).is_none());
131        assert!(self.node_keypair.replace(keypair).is_none());
132        self
133    }
134
135    pub fn with_keypair(mut self, keypair: &'a AuthorityKeyPair) -> Self {
136        assert!(self.node_keypair.replace(keypair).is_none());
137        self
138    }
139
140    /// When providing a network config, we will use the \node_idx validator's
141    /// key as the keypair for the new node.
142    pub fn with_network_config(self, config: &'a NetworkConfig, node_idx: usize) -> Self {
143        self.with_genesis_and_keypair(
144            &config.genesis,
145            config.validator_configs()[node_idx].protocol_key_pair(),
146        )
147    }
148
149    /// Provide a pre-built network config to avoid rebuilding genesis.
150    /// This is useful when creating multiple authorities that should share the same genesis.
151    pub fn with_shared_network_config(mut self, config: &'a NetworkConfig) -> Self {
152        assert!(self.network_config.replace(config).is_none());
153        self
154    }
155
156    pub fn insert_genesis_checkpoint(mut self) -> Self {
157        self.insert_genesis_checkpoint = true;
158        self
159    }
160
161    pub fn with_expensive_safety_checks(mut self, config: ExpensiveSafetyCheckConfig) -> Self {
162        assert!(self.expensive_safety_checks.replace(config).is_none());
163        self
164    }
165
166    pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
167        self.accounts = accounts;
168        self
169    }
170
171    pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
172        assert!(self.authority_overload_config.replace(config).is_none());
173        self
174    }
175
176    pub fn with_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
177        self.cache_config = Some(config);
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 fn with_recent_submission_dedup_window_ms(mut self, window_ms: u64) -> Self {
187        assert!(
188            self.recent_submission_dedup_window_ms
189                .replace(window_ms)
190                .is_none()
191        );
192        self
193    }
194
195    pub async fn build(self) -> Arc<AuthorityState> {
196        // `_guard` must be declared here so it is not dropped before
197        // `AuthorityPerEpochStore::new` is called
198        //
199        // Only create a guard if an explicit protocol_config was provided.
200        let _guard = self
201            .protocol_config
202            .clone()
203            .map(|config| ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone()));
204
205        // Use pre-built network config if available, otherwise build one
206        let owned_network_config;
207        let local_network_config: &NetworkConfig = if let Some(config) = self.network_config {
208            config
209        } else {
210            let mut local_network_config_builder =
211                sui_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            owned_network_config = local_network_config_builder.build();
219            &owned_network_config
220        };
221        let genesis = &self.genesis.unwrap_or(&local_network_config.genesis);
222        let genesis_committee = genesis.committee();
223        let path = self.store_base_path.unwrap_or_else(|| {
224            let dir = std::env::temp_dir();
225            let store_base_path =
226                dir.join(format!("DB_{:?}", nondeterministic!(ObjectID::random())));
227            std::fs::create_dir(&store_base_path).unwrap();
228            store_base_path
229        });
230        let mut config = local_network_config.validator_configs()[0].clone();
231        let registry = Registry::new();
232
233        let authority_store = match self.store {
234            Some(store) => store,
235            None => {
236                let perpetual_tables_options = AuthorityPerpetualTablesOptions::default();
237                let perpetual_tables = Arc::new(AuthorityPerpetualTables::open(
238                    &path.join("store"),
239                    Some(perpetual_tables_options),
240                    None,
241                ));
242                // unwrap ok - for testing only.
243                AuthorityStore::open_with_committee_for_testing(
244                    perpetual_tables,
245                    &genesis_committee,
246                    genesis,
247                )
248                .await
249                .unwrap()
250            }
251        };
252
253        if let Some(cache_config) = self.cache_config {
254            config.execution_cache = cache_config;
255        }
256
257        let keypair = if let Some(keypair) = self.node_keypair {
258            keypair
259        } else {
260            config.protocol_key_pair()
261        };
262
263        let secret = Arc::pin(keypair.copy());
264        let name: AuthorityName = secret.public().into();
265        let cache_metrics = Arc::new(ResolverMetrics::new(&registry));
266        let signature_verifier_metrics = SignatureVerifierMetrics::new(&registry);
267        let epoch_flags = EpochFlag::default_flags_for_new_epoch(&config);
268        let epoch_start_configuration = EpochStartConfiguration::new(
269            genesis.sui_system_object().into_epoch_start_state(),
270            *genesis.checkpoint().digest(),
271            &genesis.objects(),
272            epoch_flags,
273        )
274        .unwrap();
275        let expensive_safety_checks = self.expensive_safety_checks.unwrap_or_default();
276
277        let pruner_watermarks = Arc::new(PrunerWatermarks::default());
278        let checkpoint_store =
279            CheckpointStore::new(&path.join("checkpoints"), pruner_watermarks.clone());
280        let backpressure_manager =
281            BackpressureManager::new_from_checkpoint_store(&checkpoint_store);
282
283        let cache_traits = build_execution_cache(
284            &Default::default(),
285            &registry,
286            &authority_store,
287            backpressure_manager.clone(),
288        );
289
290        let chain_id = ChainIdentifier::from(*genesis.checkpoint().digest());
291        let chain = match self.chain_override {
292            Some(chain) => chain,
293            None => chain_id.chain(),
294        };
295
296        let epoch_store = AuthorityPerEpochStore::new(
297            name,
298            Arc::new(genesis_committee.clone()),
299            &path.join("store"),
300            None,
301            EpochMetrics::new(&registry),
302            epoch_start_configuration,
303            cache_traits.backing_package_store.clone(),
304            cache_traits.object_store.clone(),
305            cache_metrics,
306            signature_verifier_metrics,
307            &expensive_safety_checks,
308            (chain_id, chain),
309            checkpoint_store
310                .get_highest_executed_checkpoint_seq_number()
311                .unwrap()
312                .unwrap_or(0),
313            0,
314            Arc::new(SubmittedTransactionCacheMetrics::new(&registry)),
315            None,
316        )
317        .expect("failed to create authority per epoch store");
318
319        let committee_store = Arc::new(CommitteeStore::new(
320            path.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 transaction_deny_config = self.transaction_deny_config.unwrap_or_default();
333        let certificate_deny_config = self.certificate_deny_config.unwrap_or_default();
334        let authority_overload_config = self.authority_overload_config.unwrap_or_default();
335        let mut pruning_config = AuthorityStorePruningConfig::default();
336        if !epoch_store
337            .protocol_config()
338            .simplified_unwrap_then_delete()
339        {
340            // We cannot prune tombstones if simplified_unwrap_then_delete is not enabled.
341            pruning_config.set_killswitch_tombstone_pruning(true);
342        }
343
344        config.transaction_deny_config = transaction_deny_config;
345        config.certificate_deny_config = certificate_deny_config;
346        config.authority_overload_config = authority_overload_config;
347        config.authority_store_pruning_config = pruning_config;
348        config.dev_inspect_disabled = self.dev_inspect_disabled;
349        if let Some(window_ms) = self.recent_submission_dedup_window_ms {
350            config.recent_submission_dedup_window_ms = Some(window_ms);
351        }
352
353        let chain_identifier = ChainIdentifier::from(*genesis.checkpoint().digest());
354        let policy_config = config.policy_config.clone();
355        let firewall_config = config.firewall_config.clone();
356
357        let state = AuthorityState::new(
358            name,
359            secret,
360            SupportedProtocolVersions::SYSTEM_DEFAULT,
361            authority_store,
362            cache_traits,
363            epoch_store.clone(),
364            committee_store,
365            None,
366            checkpoint_store,
367            &registry,
368            &DBCheckpointConfig::default(),
369            config.clone(),
370            chain_identifier,
371            policy_config,
372            firewall_config,
373            Arc::new(PrunerWatermarks::default()),
374        )
375        .await;
376
377        // Set up randomness with no-op consensus (DKG will not complete).
378        if epoch_store.randomness_state_enabled() {
379            let consensus_client = Box::new(MockConsensusClient::new(
380                Arc::downgrade(&state),
381                ConsensusMode::Noop,
382            ));
383            let randomness_manager = RandomnessManager::try_new(
384                Arc::downgrade(&epoch_store),
385                consensus_client,
386                randomness::Handle::new_stub(),
387                Some(config.protocol_key_pair()),
388                RandomnessRoundReceiverHandle::new_for_testing(),
389            )
390            .await;
391            if let Some(randomness_manager) = randomness_manager {
392                // Randomness might fail if test configuration does not permit DKG init.
393                // In that case, skip setting it up.
394                epoch_store
395                    .set_randomness_manager(randomness_manager)
396                    .await
397                    .unwrap();
398            }
399        }
400
401        // For any type of local testing that does not actually spawn a node, the checkpoint executor
402        // won't be started, which means we won't actually execute the genesis transaction. In that case,
403        // the genesis objects (e.g. all the genesis test coins) won't be accessible. Executing it
404        // explicitly makes sure all genesis objects are ready for use.
405        state
406            .try_execute_immediately(
407                &VerifiedExecutableTransaction::new_from_checkpoint(
408                    VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
409                    genesis.epoch(),
410                    genesis.checkpoint().sequence_number,
411                ),
412                ExecutionEnv::new(),
413                &state.epoch_store_for_testing(),
414            )
415            .unwrap();
416
417        let batch = state
418            .get_cache_commit()
419            .build_db_batch(epoch_store.epoch(), &[*genesis.transaction().digest()]);
420
421        state.get_cache_commit().commit_transaction_outputs(
422            epoch_store.epoch(),
423            batch,
424            &[*genesis.transaction().digest()],
425        );
426
427        // We want to insert these objects directly instead of relying on genesis because
428        // genesis process would set the previous transaction field for these objects, which would
429        // change their object digest. This makes it difficult to write tests that want to use
430        // these objects directly.
431        // TODO: we should probably have a better way to do this.
432        if let Some(starting_objects) = self.starting_objects {
433            state
434                .insert_objects_unsafe_for_testing_only(starting_objects)
435                .await
436                .unwrap();
437        };
438
439        state
440    }
441}