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