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::jsonrpc_index::IndexStore;
21use crate::mock_consensus::{ConsensusMode, MockConsensusClient};
22use crate::module_cache_metrics::ResolverMetrics;
23use crate::rpc_index::RpcIndexStore;
24use crate::signature_verifier::SignatureVerifierMetrics;
25use fastcrypto::traits::KeyPair;
26use prometheus::Registry;
27use std::path::PathBuf;
28use std::sync::Arc;
29use sui_config::ExecutionCacheConfig;
30use sui_config::certificate_deny_config::CertificateDenyConfig;
31use sui_config::genesis::Genesis;
32use sui_config::node::AuthorityOverloadConfig;
33use sui_config::node::{
34    AuthorityStorePruningConfig, DBCheckpointConfig, ExpensiveSafetyCheckConfig,
35};
36use sui_config::transaction_deny_config::TransactionDenyConfig;
37use sui_macros::nondeterministic;
38use sui_network::randomness;
39use sui_protocol_config::{Chain, ProtocolConfig};
40use sui_swarm_config::genesis_config::AccountConfig;
41use sui_swarm_config::network_config::NetworkConfig;
42use sui_types::base_types::{AuthorityName, ObjectID};
43use sui_types::crypto::AuthorityKeyPair;
44use sui_types::digests::ChainIdentifier;
45use sui_types::executable_transaction::VerifiedExecutableTransaction;
46use sui_types::object::Object;
47use sui_types::sui_system_state::SuiSystemStateTrait;
48use sui_types::supported_protocol_versions::SupportedProtocolVersions;
49use sui_types::transaction::VerifiedTransaction;
50
51#[derive(Default, Clone)]
52pub struct TestAuthorityBuilder<'a> {
53    store_base_path: Option<PathBuf>,
54    store: Option<Arc<AuthorityStore>>,
55    transaction_deny_config: Option<TransactionDenyConfig>,
56    certificate_deny_config: Option<CertificateDenyConfig>,
57    protocol_config: Option<ProtocolConfig>,
58    reference_gas_price: Option<u64>,
59    node_keypair: Option<&'a AuthorityKeyPair>,
60    genesis: Option<&'a Genesis>,
61    /// Pre-built network config to avoid rebuilding genesis
62    network_config: Option<&'a NetworkConfig>,
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 by most tests.
68    insert_genesis_checkpoint: bool,
69    authority_overload_config: Option<AuthorityOverloadConfig>,
70    cache_config: Option<ExecutionCacheConfig>,
71    chain_override: Option<Chain>,
72    dev_inspect_disabled: bool,
73    /// Skip full RPC index initialization (use for tests that don't need RPC endpoints)
74    skip_rpc_index_init: bool,
75    /// Skip genesis owner/dynamic-field indexing (use for tests that don't query owned objects)
76    skip_genesis_owner_index: bool,
77}
78
79impl<'a> TestAuthorityBuilder<'a> {
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    pub fn with_store_base_path(mut self, path: PathBuf) -> Self {
85        assert!(self.store_base_path.replace(path).is_none());
86        self
87    }
88
89    pub fn with_starting_objects(mut self, objects: &'a [Object]) -> Self {
90        assert!(self.starting_objects.replace(objects).is_none());
91        self
92    }
93
94    pub fn with_store(mut self, store: Arc<AuthorityStore>) -> Self {
95        assert!(self.store.replace(store).is_none());
96        self
97    }
98
99    pub fn with_transaction_deny_config(mut self, config: TransactionDenyConfig) -> Self {
100        assert!(self.transaction_deny_config.replace(config).is_none());
101        self
102    }
103
104    pub fn with_dev_inspect_disabled(mut self) -> Self {
105        self.dev_inspect_disabled = true;
106        self
107    }
108
109    pub fn with_certificate_deny_config(mut self, config: CertificateDenyConfig) -> Self {
110        assert!(self.certificate_deny_config.replace(config).is_none());
111        self
112    }
113
114    pub fn with_protocol_config(mut self, config: ProtocolConfig) -> Self {
115        assert!(self.protocol_config.replace(config).is_none());
116        self
117    }
118
119    pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
120        // If genesis is already set then setting rgp is meaningless since it will be overwritten.
121        assert!(self.genesis.is_none());
122        assert!(
123            self.reference_gas_price
124                .replace(reference_gas_price)
125                .is_none()
126        );
127        self
128    }
129
130    pub fn with_genesis_and_keypair(
131        mut self,
132        genesis: &'a Genesis,
133        keypair: &'a AuthorityKeyPair,
134    ) -> Self {
135        assert!(self.genesis.replace(genesis).is_none());
136        assert!(self.node_keypair.replace(keypair).is_none());
137        self
138    }
139
140    pub fn with_keypair(mut self, keypair: &'a AuthorityKeyPair) -> Self {
141        assert!(self.node_keypair.replace(keypair).is_none());
142        self
143    }
144
145    /// When providing a network config, we will use the \node_idx validator's
146    /// key as the keypair for the new node.
147    pub fn with_network_config(self, config: &'a NetworkConfig, node_idx: usize) -> Self {
148        self.with_genesis_and_keypair(
149            &config.genesis,
150            config.validator_configs()[node_idx].protocol_key_pair(),
151        )
152    }
153
154    /// Provide a pre-built network config to avoid rebuilding genesis.
155    /// This is useful when creating multiple authorities that should share the same genesis.
156    pub fn with_shared_network_config(mut self, config: &'a NetworkConfig) -> Self {
157        assert!(self.network_config.replace(config).is_none());
158        self
159    }
160
161    pub fn disable_indexer(mut self) -> Self {
162        self.disable_indexer = true;
163        self
164    }
165
166    /// Skip full RPC index initialization. This is much faster for tests
167    /// that don't need RPC endpoint functionality.
168    pub fn skip_rpc_index_init(mut self) -> Self {
169        self.skip_rpc_index_init = true;
170        self
171    }
172
173    /// Skip genesis owner/dynamic-field indexing. This is much faster for tests
174    /// that don't query owned objects via RPC (e.g., get_owned_objects).
175    pub fn skip_genesis_owner_index(mut self) -> Self {
176        self.skip_genesis_owner_index = true;
177        self
178    }
179
180    pub fn insert_genesis_checkpoint(mut self) -> Self {
181        self.insert_genesis_checkpoint = true;
182        self
183    }
184
185    pub fn with_expensive_safety_checks(mut self, config: ExpensiveSafetyCheckConfig) -> Self {
186        assert!(self.expensive_safety_checks.replace(config).is_none());
187        self
188    }
189
190    pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
191        self.accounts = accounts;
192        self
193    }
194
195    pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
196        assert!(self.authority_overload_config.replace(config).is_none());
197        self
198    }
199
200    pub fn with_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
201        self.cache_config = Some(config);
202        self
203    }
204
205    pub fn with_chain_override(mut self, chain: Chain) -> Self {
206        self.chain_override = Some(chain);
207        self
208    }
209
210    pub async fn build(self) -> Arc<AuthorityState> {
211        // `_guard` must be declared here so it is not dropped before
212        // `AuthorityPerEpochStore::new` is called
213        //
214        // Only create a guard if an explicit protocol_config was provided.
215        // If no protocol_config is provided, the caller is responsible for setting up
216        // any necessary protocol config overrides (e.g., disable_preconsensus_locking=false
217        // for tests that use the Quorum Driver flow with extract_cert).
218        let _guard = self
219            .protocol_config
220            .clone()
221            .map(|config| ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone()));
222
223        // Use pre-built network config if available, otherwise build one
224        let owned_network_config;
225        let local_network_config: &NetworkConfig = if let Some(config) = self.network_config {
226            config
227        } else {
228            let mut local_network_config_builder =
229                sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
230                    .with_accounts(self.accounts)
231                    .with_reference_gas_price(self.reference_gas_price.unwrap_or(500));
232            if let Some(protocol_config) = &self.protocol_config {
233                local_network_config_builder =
234                    local_network_config_builder.with_protocol_version(protocol_config.version);
235            }
236            owned_network_config = local_network_config_builder.build();
237            &owned_network_config
238        };
239        let genesis = &self.genesis.unwrap_or(&local_network_config.genesis);
240        let genesis_committee = genesis.committee();
241        let path = self.store_base_path.unwrap_or_else(|| {
242            let dir = std::env::temp_dir();
243            let store_base_path =
244                dir.join(format!("DB_{:?}", nondeterministic!(ObjectID::random())));
245            std::fs::create_dir(&store_base_path).unwrap();
246            store_base_path
247        });
248        let mut config = local_network_config.validator_configs()[0].clone();
249        let registry = Registry::new();
250
251        let authority_store = match self.store {
252            Some(store) => store,
253            None => {
254                let perpetual_tables_options = AuthorityPerpetualTablesOptions::default();
255                let perpetual_tables = Arc::new(AuthorityPerpetualTables::open(
256                    &path.join("store"),
257                    Some(perpetual_tables_options),
258                    None,
259                ));
260                // unwrap ok - for testing only.
261                AuthorityStore::open_with_committee_for_testing(
262                    perpetual_tables,
263                    &genesis_committee,
264                    genesis,
265                )
266                .await
267                .unwrap()
268            }
269        };
270
271        if let Some(cache_config) = self.cache_config {
272            config.execution_cache = cache_config;
273        }
274
275        let keypair = if let Some(keypair) = self.node_keypair {
276            keypair
277        } else {
278            config.protocol_key_pair()
279        };
280
281        let secret = Arc::pin(keypair.copy());
282        let name: AuthorityName = secret.public().into();
283        let cache_metrics = Arc::new(ResolverMetrics::new(&registry));
284        let signature_verifier_metrics = SignatureVerifierMetrics::new(&registry);
285        let epoch_flags = EpochFlag::default_flags_for_new_epoch(&config);
286        let epoch_start_configuration = EpochStartConfiguration::new(
287            genesis.sui_system_object().into_epoch_start_state(),
288            *genesis.checkpoint().digest(),
289            &genesis.objects(),
290            epoch_flags,
291        )
292        .unwrap();
293        let expensive_safety_checks = self.expensive_safety_checks.unwrap_or_default();
294
295        let pruner_watermarks = Arc::new(PrunerWatermarks::default());
296        let checkpoint_store =
297            CheckpointStore::new(&path.join("checkpoints"), pruner_watermarks.clone());
298        let backpressure_manager =
299            BackpressureManager::new_from_checkpoint_store(&checkpoint_store);
300
301        let cache_traits = build_execution_cache(
302            &Default::default(),
303            &registry,
304            &authority_store,
305            backpressure_manager.clone(),
306        );
307
308        let chain_id = ChainIdentifier::from(*genesis.checkpoint().digest());
309        let chain = match self.chain_override {
310            Some(chain) => chain,
311            None => chain_id.chain(),
312        };
313
314        let epoch_store = AuthorityPerEpochStore::new(
315            name,
316            Arc::new(genesis_committee.clone()),
317            &path.join("store"),
318            None,
319            EpochMetrics::new(&registry),
320            epoch_start_configuration,
321            cache_traits.backing_package_store.clone(),
322            cache_traits.object_store.clone(),
323            cache_metrics,
324            signature_verifier_metrics,
325            &expensive_safety_checks,
326            (chain_id, chain),
327            checkpoint_store
328                .get_highest_executed_checkpoint_seq_number()
329                .unwrap()
330                .unwrap_or(0),
331            0,
332            Arc::new(SubmittedTransactionCacheMetrics::new(&registry)),
333        )
334        .expect("failed to create authority per epoch store");
335
336        let committee_store = Arc::new(CommitteeStore::new(
337            path.join("epochs"),
338            &genesis_committee,
339            None,
340        ));
341
342        if self.insert_genesis_checkpoint {
343            checkpoint_store.insert_genesis_checkpoint(
344                genesis.checkpoint(),
345                genesis.checkpoint_contents().clone(),
346                &epoch_store,
347            );
348        }
349        let index_store = if self.disable_indexer {
350            None
351        } else {
352            Some(Arc::new(IndexStore::new(
353                path.join("indexes"),
354                &registry,
355                epoch_store
356                    .protocol_config()
357                    .max_move_identifier_len_as_option(),
358                false,
359            )))
360        };
361
362        let rpc_index = if self.disable_indexer {
363            None
364        } else if self.skip_rpc_index_init {
365            Some(Arc::new(RpcIndexStore::new_without_init(&path)))
366        } else {
367            Some(Arc::new(
368                RpcIndexStore::new(
369                    &path,
370                    &authority_store,
371                    &checkpoint_store,
372                    &epoch_store,
373                    &cache_traits.backing_package_store,
374                    pruner_watermarks.checkpoint_id.clone(),
375                    sui_config::RpcConfig::default(),
376                )
377                .await,
378            ))
379        };
380
381        let transaction_deny_config = self.transaction_deny_config.unwrap_or_default();
382        let certificate_deny_config = self.certificate_deny_config.unwrap_or_default();
383        let authority_overload_config = self.authority_overload_config.unwrap_or_default();
384        let mut pruning_config = AuthorityStorePruningConfig::default();
385        if !epoch_store
386            .protocol_config()
387            .simplified_unwrap_then_delete()
388        {
389            // We cannot prune tombstones if simplified_unwrap_then_delete is not enabled.
390            pruning_config.set_killswitch_tombstone_pruning(true);
391        }
392
393        config.transaction_deny_config = transaction_deny_config;
394        config.certificate_deny_config = certificate_deny_config;
395        config.authority_overload_config = authority_overload_config;
396        config.authority_store_pruning_config = pruning_config;
397        config.dev_inspect_disabled = self.dev_inspect_disabled;
398
399        let chain_identifier = ChainIdentifier::from(*genesis.checkpoint().digest());
400        let policy_config = config.policy_config.clone();
401        let firewall_config = config.firewall_config.clone();
402
403        let genesis_objects_for_index = if self.skip_genesis_owner_index {
404            &[][..]
405        } else {
406            genesis.objects()
407        };
408        let state = AuthorityState::new(
409            name,
410            secret,
411            SupportedProtocolVersions::SYSTEM_DEFAULT,
412            authority_store,
413            cache_traits,
414            epoch_store.clone(),
415            committee_store,
416            index_store,
417            rpc_index,
418            checkpoint_store,
419            &registry,
420            genesis_objects_for_index,
421            &DBCheckpointConfig::default(),
422            config.clone(),
423            chain_identifier,
424            policy_config,
425            firewall_config,
426            Arc::new(PrunerWatermarks::default()),
427        )
428        .await;
429
430        // Set up randomness with no-op consensus (DKG will not complete).
431        if epoch_store.randomness_state_enabled() {
432            let consensus_client = Box::new(MockConsensusClient::new(
433                Arc::downgrade(&state),
434                ConsensusMode::Noop,
435            ));
436            let randomness_manager = RandomnessManager::try_new(
437                Arc::downgrade(&epoch_store),
438                consensus_client,
439                randomness::Handle::new_stub(),
440                config.protocol_key_pair(),
441            )
442            .await;
443            if let Some(randomness_manager) = randomness_manager {
444                // Randomness might fail if test configuration does not permit DKG init.
445                // In that case, skip setting it up.
446                epoch_store
447                    .set_randomness_manager(randomness_manager)
448                    .await
449                    .unwrap();
450            }
451        }
452
453        // For any type of local testing that does not actually spawn a node, the checkpoint executor
454        // won't be started, which means we won't actually execute the genesis transaction. In that case,
455        // the genesis objects (e.g. all the genesis test coins) won't be accessible. Executing it
456        // explicitly makes sure all genesis objects are ready for use.
457        state
458            .try_execute_immediately(
459                &VerifiedExecutableTransaction::new_from_checkpoint(
460                    VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
461                    genesis.epoch(),
462                    genesis.checkpoint().sequence_number,
463                ),
464                ExecutionEnv::new(),
465                &state.epoch_store_for_testing(),
466            )
467            .await
468            .unwrap();
469
470        let batch = state
471            .get_cache_commit()
472            .build_db_batch(epoch_store.epoch(), &[*genesis.transaction().digest()]);
473
474        state.get_cache_commit().commit_transaction_outputs(
475            epoch_store.epoch(),
476            batch,
477            &[*genesis.transaction().digest()],
478        );
479
480        // We want to insert these objects directly instead of relying on genesis because
481        // genesis process would set the previous transaction field for these objects, which would
482        // change their object digest. This makes it difficult to write tests that want to use
483        // these objects directly.
484        // TODO: we should probably have a better way to do this.
485        if let Some(starting_objects) = self.starting_objects {
486            state
487                .insert_objects_unsafe_for_testing_only(starting_objects)
488                .await
489                .unwrap();
490        };
491
492        state
493    }
494}