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            &config,
309            (chain_id, chain),
310            checkpoint_store
311                .get_highest_executed_checkpoint_seq_number()
312                .unwrap()
313                .unwrap_or(0),
314            0,
315            Arc::new(SubmittedTransactionCacheMetrics::new(&registry)),
316            None,
317        )
318        .expect("failed to create authority per epoch store");
319
320        let committee_store = Arc::new(CommitteeStore::new(
321            path.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 transaction_deny_config = self.transaction_deny_config.unwrap_or_default();
334        let certificate_deny_config = self.certificate_deny_config.unwrap_or_default();
335        let authority_overload_config = self.authority_overload_config.unwrap_or_default();
336        let mut pruning_config = AuthorityStorePruningConfig::default();
337        if !epoch_store
338            .protocol_config()
339            .simplified_unwrap_then_delete()
340        {
341            // We cannot prune tombstones if simplified_unwrap_then_delete is not enabled.
342            pruning_config.set_killswitch_tombstone_pruning(true);
343        }
344
345        config.transaction_deny_config = transaction_deny_config;
346        config.certificate_deny_config = certificate_deny_config;
347        config.authority_overload_config = authority_overload_config;
348        config.authority_store_pruning_config = pruning_config;
349        config.dev_inspect_disabled = self.dev_inspect_disabled;
350        if let Some(window_ms) = self.recent_submission_dedup_window_ms {
351            config.recent_submission_dedup_window_ms = Some(window_ms);
352        }
353
354        let chain_identifier = ChainIdentifier::from(*genesis.checkpoint().digest());
355        let policy_config = config.policy_config.clone();
356        let firewall_config = config.firewall_config.clone();
357
358        let state = AuthorityState::new(
359            name,
360            secret,
361            SupportedProtocolVersions::SYSTEM_DEFAULT,
362            authority_store,
363            cache_traits,
364            epoch_store.clone(),
365            committee_store,
366            None,
367            checkpoint_store,
368            &registry,
369            &DBCheckpointConfig::default(),
370            config.clone(),
371            chain_identifier,
372            policy_config,
373            firewall_config,
374            Arc::new(PrunerWatermarks::default()),
375        )
376        .await;
377
378        // Set up randomness with no-op consensus (DKG will not complete).
379        if epoch_store.randomness_state_enabled() {
380            let consensus_client = Box::new(MockConsensusClient::new(
381                Arc::downgrade(&state),
382                ConsensusMode::Noop,
383            ));
384            let randomness_manager = RandomnessManager::try_new(
385                Arc::downgrade(&epoch_store),
386                consensus_client,
387                randomness::Handle::new_stub(),
388                Some(config.protocol_key_pair()),
389                RandomnessRoundReceiverHandle::new_for_testing(),
390            )
391            .await;
392            if let Some(randomness_manager) = randomness_manager {
393                // Randomness might fail if test configuration does not permit DKG init.
394                // In that case, skip setting it up.
395                epoch_store
396                    .set_randomness_manager(randomness_manager)
397                    .await
398                    .unwrap();
399            }
400        }
401
402        // For any type of local testing that does not actually spawn a node, the checkpoint executor
403        // won't be started, which means we won't actually execute the genesis transaction. In that case,
404        // the genesis objects (e.g. all the genesis test coins) won't be accessible. Executing it
405        // explicitly makes sure all genesis objects are ready for use.
406        state
407            .try_execute_immediately(
408                &VerifiedExecutableTransaction::new_from_checkpoint(
409                    VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
410                    genesis.epoch(),
411                    genesis.checkpoint().sequence_number,
412                ),
413                ExecutionEnv::new(),
414                &state.epoch_store_for_testing(),
415            )
416            .unwrap();
417
418        let batch = state
419            .get_cache_commit()
420            .build_db_batch(epoch_store.epoch(), &[*genesis.transaction().digest()]);
421
422        state.get_cache_commit().commit_transaction_outputs(
423            epoch_store.epoch(),
424            batch,
425            &[*genesis.transaction().digest()],
426        );
427
428        // We want to insert these objects directly instead of relying on genesis because
429        // genesis process would set the previous transaction field for these objects, which would
430        // change their object digest. This makes it difficult to write tests that want to use
431        // these objects directly.
432        // TODO: we should probably have a better way to do this.
433        if let Some(starting_objects) = self.starting_objects {
434            state
435                .insert_objects_unsafe_for_testing_only(starting_objects)
436                .await
437                .unwrap();
438        };
439
440        state
441    }
442}