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::{ObjectsCompactionFilter, PrunerWatermarks};
9use crate::authority::authority_store_tables::{
10    AuthorityPerpetualTables, AuthorityPerpetualTablesOptions, AuthorityPrunerTables,
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::execution_scheduler::SchedulingSource;
21use crate::jsonrpc_index::IndexStore;
22use crate::mock_consensus::{ConsensusMode, MockConsensusClient};
23use crate::module_cache_metrics::ResolverMetrics;
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    starting_objects: Option<&'a [Object]>,
63    expensive_safety_checks: Option<ExpensiveSafetyCheckConfig>,
64    disable_indexer: bool,
65    accounts: Vec<AccountConfig>,
66    /// By default, we don't insert the genesis checkpoint, which isn't needed by most tests.
67    insert_genesis_checkpoint: bool,
68    authority_overload_config: Option<AuthorityOverloadConfig>,
69    cache_config: Option<ExecutionCacheConfig>,
70    chain_override: Option<Chain>,
71}
72
73impl<'a> TestAuthorityBuilder<'a> {
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    pub fn with_store_base_path(mut self, path: PathBuf) -> Self {
79        assert!(self.store_base_path.replace(path).is_none());
80        self
81    }
82
83    pub fn with_starting_objects(mut self, objects: &'a [Object]) -> Self {
84        assert!(self.starting_objects.replace(objects).is_none());
85        self
86    }
87
88    pub fn with_store(mut self, store: Arc<AuthorityStore>) -> Self {
89        assert!(self.store.replace(store).is_none());
90        self
91    }
92
93    pub fn with_transaction_deny_config(mut self, config: TransactionDenyConfig) -> Self {
94        assert!(self.transaction_deny_config.replace(config).is_none());
95        self
96    }
97
98    pub fn with_certificate_deny_config(mut self, config: CertificateDenyConfig) -> Self {
99        assert!(self.certificate_deny_config.replace(config).is_none());
100        self
101    }
102
103    pub fn with_protocol_config(mut self, config: ProtocolConfig) -> Self {
104        assert!(self.protocol_config.replace(config).is_none());
105        self
106    }
107
108    pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
109        // If genesis is already set then setting rgp is meaningless since it will be 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].protocol_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 with_chain_override(mut self, chain: Chain) -> Self {
174        self.chain_override = Some(chain);
175        self
176    }
177
178    pub async fn build(self) -> Arc<AuthorityState> {
179        // `_guard` must be declared here so it is not dropped before
180        // `AuthorityPerEpochStore::new` is called
181        //
182        // Only create a guard if an explicit protocol_config was provided.
183        // If no protocol_config is provided, the caller is responsible for setting up
184        // any necessary protocol config overrides (e.g., disable_preconsensus_locking=false
185        // for tests that use the Quorum Driver flow with extract_cert).
186        let _guard = self
187            .protocol_config
188            .clone()
189            .map(|config| ProtocolConfig::apply_overrides_for_testing(move |_, _| config.clone()));
190
191        let mut local_network_config_builder =
192            sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
193                .with_accounts(self.accounts)
194                .with_reference_gas_price(self.reference_gas_price.unwrap_or(500));
195        if let Some(protocol_config) = &self.protocol_config {
196            local_network_config_builder =
197                local_network_config_builder.with_protocol_version(protocol_config.version);
198        }
199        let local_network_config = local_network_config_builder.build();
200        let genesis = &self.genesis.unwrap_or(&local_network_config.genesis);
201        let genesis_committee = genesis.committee().unwrap();
202        let path = self.store_base_path.unwrap_or_else(|| {
203            let dir = std::env::temp_dir();
204            let store_base_path =
205                dir.join(format!("DB_{:?}", nondeterministic!(ObjectID::random())));
206            std::fs::create_dir(&store_base_path).unwrap();
207            store_base_path
208        });
209        let mut config = local_network_config.validator_configs()[0].clone();
210        let registry = Registry::new();
211        let mut pruner_db = None;
212        if config
213            .authority_store_pruning_config
214            .enable_compaction_filter
215        {
216            pruner_db = Some(Arc::new(AuthorityPrunerTables::open(&path.join("store"))));
217        }
218        let compaction_filter = pruner_db
219            .clone()
220            .map(|db| ObjectsCompactionFilter::new(db, &registry));
221
222        let authority_store = match self.store {
223            Some(store) => store,
224            None => {
225                let perpetual_tables_options = AuthorityPerpetualTablesOptions {
226                    compaction_filter,
227                    ..Default::default()
228                };
229                let perpetual_tables = Arc::new(AuthorityPerpetualTables::open(
230                    &path.join("store"),
231                    Some(perpetual_tables_options),
232                    None,
233                ));
234                // unwrap ok - for testing only.
235                AuthorityStore::open_with_committee_for_testing(
236                    perpetual_tables,
237                    &genesis_committee,
238                    genesis,
239                )
240                .await
241                .unwrap()
242            }
243        };
244        if let Some(cache_config) = self.cache_config {
245            config.execution_cache = cache_config;
246        }
247
248        let keypair = if let Some(keypair) = self.node_keypair {
249            keypair
250        } else {
251            config.protocol_key_pair()
252        };
253
254        let secret = Arc::pin(keypair.copy());
255        let name: AuthorityName = secret.public().into();
256        let cache_metrics = Arc::new(ResolverMetrics::new(&registry));
257        let signature_verifier_metrics = SignatureVerifierMetrics::new(&registry);
258        let epoch_flags = EpochFlag::default_flags_for_new_epoch(&config);
259        let epoch_start_configuration = EpochStartConfiguration::new(
260            genesis.sui_system_object().into_epoch_start_state(),
261            *genesis.checkpoint().digest(),
262            &genesis.objects(),
263            epoch_flags,
264        )
265        .unwrap();
266        let expensive_safety_checks = self.expensive_safety_checks.unwrap_or_default();
267
268        let pruner_watermarks = Arc::new(PrunerWatermarks::default());
269        let checkpoint_store =
270            CheckpointStore::new(&path.join("checkpoints"), pruner_watermarks.clone());
271        let backpressure_manager =
272            BackpressureManager::new_from_checkpoint_store(&checkpoint_store);
273
274        let cache_traits = build_execution_cache(
275            &Default::default(),
276            &registry,
277            &authority_store,
278            backpressure_manager.clone(),
279        );
280
281        let chain_id = ChainIdentifier::from(*genesis.checkpoint().digest());
282        let chain = match self.chain_override {
283            Some(chain) => chain,
284            None => chain_id.chain(),
285        };
286
287        let epoch_store = AuthorityPerEpochStore::new(
288            name,
289            Arc::new(genesis_committee.clone()),
290            &path.join("store"),
291            None,
292            EpochMetrics::new(&registry),
293            epoch_start_configuration,
294            cache_traits.backing_package_store.clone(),
295            cache_traits.object_store.clone(),
296            cache_metrics,
297            signature_verifier_metrics,
298            &expensive_safety_checks,
299            (chain_id, chain),
300            checkpoint_store
301                .get_highest_executed_checkpoint_seq_number()
302                .unwrap()
303                .unwrap_or(0),
304            Arc::new(SubmittedTransactionCacheMetrics::new(&registry)),
305        )
306        .expect("failed to create authority per epoch store");
307        let committee_store = Arc::new(CommitteeStore::new(
308            path.join("epochs"),
309            &genesis_committee,
310            None,
311        ));
312
313        if self.insert_genesis_checkpoint {
314            checkpoint_store.insert_genesis_checkpoint(
315                genesis.checkpoint(),
316                genesis.checkpoint_contents().clone(),
317                &epoch_store,
318            );
319        }
320        let index_store = if self.disable_indexer {
321            None
322        } else {
323            Some(Arc::new(IndexStore::new(
324                path.join("indexes"),
325                &registry,
326                epoch_store
327                    .protocol_config()
328                    .max_move_identifier_len_as_option(),
329                false,
330            )))
331        };
332        let rpc_index = if self.disable_indexer {
333            None
334        } else {
335            Some(Arc::new(
336                RpcIndexStore::new(
337                    &path,
338                    &authority_store,
339                    &checkpoint_store,
340                    &epoch_store,
341                    &cache_traits.backing_package_store,
342                    pruner_watermarks.checkpoint_id.clone(),
343                    sui_config::RpcConfig::default(),
344                )
345                .await,
346            ))
347        };
348
349        let transaction_deny_config = self.transaction_deny_config.unwrap_or_default();
350        let certificate_deny_config = self.certificate_deny_config.unwrap_or_default();
351        let authority_overload_config = self.authority_overload_config.unwrap_or_default();
352        let mut pruning_config = AuthorityStorePruningConfig::default();
353        if !epoch_store
354            .protocol_config()
355            .simplified_unwrap_then_delete()
356        {
357            // We cannot prune tombstones if simplified_unwrap_then_delete is not enabled.
358            pruning_config.set_killswitch_tombstone_pruning(true);
359        }
360
361        config.transaction_deny_config = transaction_deny_config;
362        config.certificate_deny_config = certificate_deny_config;
363        config.authority_overload_config = authority_overload_config;
364        config.authority_store_pruning_config = pruning_config;
365
366        let chain_identifier = ChainIdentifier::from(*genesis.checkpoint().digest());
367        let policy_config = config.policy_config.clone();
368        let firewall_config = config.firewall_config.clone();
369
370        let state = AuthorityState::new(
371            name,
372            secret,
373            SupportedProtocolVersions::SYSTEM_DEFAULT,
374            authority_store,
375            cache_traits,
376            epoch_store.clone(),
377            committee_store,
378            index_store,
379            rpc_index,
380            checkpoint_store,
381            &registry,
382            genesis.objects(),
383            &DBCheckpointConfig::default(),
384            config.clone(),
385            chain_identifier,
386            pruner_db,
387            policy_config,
388            firewall_config,
389            Arc::new(PrunerWatermarks::default()),
390        )
391        .await;
392
393        // Set up randomness with no-op consensus (DKG will not complete).
394        if epoch_store.randomness_state_enabled() {
395            let consensus_client = Box::new(MockConsensusClient::new(
396                Arc::downgrade(&state),
397                ConsensusMode::Noop,
398            ));
399            let randomness_manager = RandomnessManager::try_new(
400                Arc::downgrade(&epoch_store),
401                consensus_client,
402                randomness::Handle::new_stub(),
403                config.protocol_key_pair(),
404            )
405            .await;
406            if let Some(randomness_manager) = randomness_manager {
407                // Randomness might fail if test configuration does not permit DKG init.
408                // In that case, skip setting it up.
409                epoch_store
410                    .set_randomness_manager(randomness_manager)
411                    .await
412                    .unwrap();
413            }
414        }
415
416        // For any type of local testing that does not actually spawn a node, the checkpoint executor
417        // won't be started, which means we won't actually execute the genesis transaction. In that case,
418        // the genesis objects (e.g. all the genesis test coins) won't be accessible. Executing it
419        // explicitly makes sure all genesis objects are ready for use.
420        state
421            .try_execute_immediately(
422                &VerifiedExecutableTransaction::new_from_checkpoint(
423                    VerifiedTransaction::new_unchecked(genesis.transaction().clone()),
424                    genesis.epoch(),
425                    genesis.checkpoint().sequence_number,
426                ),
427                ExecutionEnv::new().with_scheduling_source(SchedulingSource::NonFastPath),
428                &state.epoch_store_for_testing(),
429            )
430            .await
431            .unwrap();
432
433        let batch = state
434            .get_cache_commit()
435            .build_db_batch(epoch_store.epoch(), &[*genesis.transaction().digest()]);
436
437        state.get_cache_commit().commit_transaction_outputs(
438            epoch_store.epoch(),
439            batch,
440            &[*genesis.transaction().digest()],
441        );
442
443        // We want to insert these objects directly instead of relying on genesis because
444        // genesis process would set the previous transaction field for these objects, which would
445        // change their object digest. This makes it difficult to write tests that want to use
446        // 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                .unwrap();
453        };
454        state
455    }
456}