Skip to main content

sui_single_node_benchmark/
single_node.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::command::Component;
5use crate::mock_storage::InMemoryObjectStore;
6use std::collections::{BTreeMap, HashMap};
7use std::sync::Arc;
8use sui_core::authority::authority_per_epoch_store::AuthorityPerEpochStore;
9use sui_core::authority::authority_store_tables::LiveObject;
10use sui_core::authority::shared_object_version_manager::{
11    AssignedTxAndVersions, AssignedVersions, Schedulable,
12};
13use sui_core::authority::test_authority_builder::TestAuthorityBuilder;
14use sui_core::authority::{AuthorityState, ExecutionEnv};
15use sui_core::authority_server::{ValidatorService, ValidatorServiceMetrics};
16use sui_core::checkpoints::checkpoint_executor::CheckpointExecutor;
17use sui_core::consensus_adapter::{ConsensusAdapter, ConsensusAdapterMetrics};
18use sui_core::global_state_hasher::GlobalStateHasher;
19use sui_core::mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider};
20use sui_core::mock_consensus::{ConsensusMode, MockConsensusClient};
21use sui_test_transaction_builder::{PublishData, TestTransactionBuilder};
22use sui_types::base_types::{AuthorityName, ObjectRef, SuiAddress, TransactionDigest};
23use sui_types::committee::Committee;
24use sui_types::crypto::{AccountKeyPair, AuthoritySignature, Signer};
25use sui_types::effects::{TransactionEffects, TransactionEffectsAPI};
26use sui_types::executable_transaction::VerifiedExecutableTransaction;
27use sui_types::execution_params::ExecutionOrEarlyError;
28use sui_types::messages_checkpoint::{VerifiedCheckpoint, VerifiedCheckpointContents};
29use sui_types::object::Object;
30use sui_types::transaction::{
31    DEFAULT_VALIDATOR_GAS_PRICE, Transaction, TransactionDataAPI, VerifiedTransaction,
32};
33
34#[derive(Clone)]
35pub struct SingleValidator {
36    validator_service: Arc<ValidatorService>,
37    epoch_store: Arc<AuthorityPerEpochStore>,
38}
39
40impl SingleValidator {
41    pub(crate) async fn new(genesis_objects: &[Object], component: Component) -> Self {
42        let validator = TestAuthorityBuilder::new()
43            .with_starting_objects(genesis_objects)
44            // This is needed to properly run checkpoint executor.
45            .insert_genesis_checkpoint()
46            .build()
47            .await;
48        let epoch_store = validator.epoch_store_for_testing().clone();
49        let consensus_mode = match component {
50            Component::ValidatorWithFakeConsensus => ConsensusMode::DirectSequencing,
51            _ => ConsensusMode::Noop,
52        };
53        let consensus_adapter = Arc::new(ConsensusAdapter::new(
54            Arc::new(MockConsensusClient::new(
55                Arc::downgrade(&validator),
56                consensus_mode,
57            )),
58            validator.checkpoint_store.clone(),
59            validator.name,
60            100_000,
61            100_000,
62            ConsensusAdapterMetrics::new_test(),
63            Arc::new(tokio::sync::Notify::new()),
64        ));
65        // TODO: for validator benchmarking purposes, we should allow for traffic control
66        // to be configurable and introduce traffic control benchmarks to test
67        // against different policies
68        let validator_service = Arc::new(ValidatorService::new_for_tests(
69            validator,
70            consensus_adapter,
71            Arc::new(ValidatorServiceMetrics::new_for_tests()),
72        ));
73        Self {
74            validator_service,
75            epoch_store,
76        }
77    }
78
79    pub fn get_validator(&self) -> &Arc<AuthorityState> {
80        self.validator_service.validator_state()
81    }
82
83    pub fn get_epoch(&self) -> u64 {
84        self.epoch_store.epoch()
85    }
86
87    /// Publish a package, returns the package object and the updated gas object.
88    pub async fn publish_package(
89        &self,
90        publish_data: PublishData,
91        sender: SuiAddress,
92        keypair: &AccountKeyPair,
93        gas: ObjectRef,
94    ) -> (ObjectRef, ObjectRef) {
95        let tx_builder = TestTransactionBuilder::new(sender, gas, DEFAULT_VALIDATOR_GAS_PRICE)
96            .publish_with_data_async(publish_data)
97            .await;
98        let transaction = tx_builder.build_and_sign(keypair);
99        let effects = self.execute_raw_transaction(transaction).await;
100        let package = effects
101            .all_changed_objects()
102            .into_iter()
103            .filter_map(|(oref, owner, _)| owner.is_immutable().then_some(oref))
104            .next()
105            .unwrap();
106        let updated_gas = effects.gas_object().unwrap().0;
107        (package, updated_gas)
108    }
109
110    pub async fn execute_raw_transaction(&self, transaction: Transaction) -> TransactionEffects {
111        let executable = VerifiedExecutableTransaction::new_from_consensus(
112            VerifiedTransaction::new_unchecked(transaction),
113            0,
114        );
115        let effects = self
116            .get_validator()
117            .try_execute_immediately(&executable, ExecutionEnv::new(), &self.epoch_store)
118            .unwrap()
119            .0;
120        assert!(effects.status().is_ok());
121        effects
122    }
123
124    /// Creates a VerifiedExecutableTransaction from a Transaction using MFP style certification.
125    fn create_executable(&self, transaction: Transaction) -> VerifiedExecutableTransaction {
126        VerifiedExecutableTransaction::new_from_consensus(
127            VerifiedTransaction::new_unchecked(transaction),
128            self.epoch_store.epoch(),
129        )
130    }
131
132    pub async fn execute_transaction(
133        &self,
134        transaction: Transaction,
135        assigned_versions: &AssignedVersions,
136        component: Component,
137    ) -> (TransactionEffects, std::time::Duration) {
138        let executable = self.create_executable(transaction);
139        let start = std::time::Instant::now();
140        let effects = match component {
141            Component::Baseline => {
142                self.get_validator()
143                    .try_execute_immediately(
144                        &executable,
145                        ExecutionEnv::new().with_assigned_versions(assigned_versions.clone()),
146                        &self.epoch_store,
147                    )
148                    .unwrap()
149                    .0
150            }
151            Component::WithTxManager => {
152                // Manually enqueue since we don't have consensus.
153                self.get_validator().execution_scheduler().enqueue(
154                    vec![(
155                        executable.clone().into(),
156                        ExecutionEnv::new().with_assigned_versions(assigned_versions.clone()),
157                    )],
158                    &self.epoch_store,
159                );
160                self.get_validator()
161                    .wait_for_transaction_execution_for_testing(&executable)
162                    .await
163            }
164            Component::ValidatorWithoutConsensus | Component::ValidatorWithFakeConsensus => {
165                // Execute the transaction directly using try_execute_executable_for_test
166                let (signed_effects, _) = self
167                    .get_validator()
168                    .try_execute_executable_for_test(
169                        &executable,
170                        ExecutionEnv::new().with_assigned_versions(assigned_versions.clone()),
171                    )
172                    .await;
173                signed_effects.into_inner().into_data()
174            }
175            Component::CheckpointExecutor | Component::ExecutionOnly => {
176                unreachable!()
177            }
178        };
179        let elapsed = start.elapsed();
180        assert!(effects.status().is_ok());
181        (effects, elapsed)
182    }
183
184    pub(crate) async fn execute_transaction_in_memory(
185        &self,
186        store: InMemoryObjectStore,
187        transaction: Transaction,
188        assigned_versions: &AssignedVersions,
189    ) -> TransactionEffects {
190        let input_objects = transaction
191            .data()
192            .intent_message()
193            .value
194            .input_objects()
195            .unwrap();
196        let executable = self.create_executable(transaction);
197        let objects = store
198            .read_objects_for_execution(&executable.key(), assigned_versions, &input_objects)
199            .unwrap();
200
201        let (gas_status, input_objects) = sui_transaction_checks::check_certificate_input(
202            &executable,
203            objects,
204            self.epoch_store.protocol_config(),
205            self.epoch_store.reference_gas_price(),
206        )
207        .unwrap();
208        let (kind, signer, gas_data) = executable.transaction_data().execution_parts();
209        let (inner_temp_store, _, effects, _timings, _) =
210            self.epoch_store.executor().execute_transaction_to_effects(
211                &store,
212                self.epoch_store.protocol_config(),
213                self.get_validator().metrics.execution_metrics.clone(),
214                false,
215                ExecutionOrEarlyError::ok(None),
216                &self.epoch_store.epoch(),
217                0,
218                input_objects,
219                sui_types::base_types::SystemObjectVersions::empty(),
220                // The benchmark only measures execution throughput and never withdraws object
221                // funds, so there are no unsettled withdrawals to account for.
222                &sui_types::accumulator_root::EmptyUnsettledObjectFunds,
223                gas_data,
224                gas_status,
225                kind,
226                None, // compat_args
227                signer,
228                *executable.digest(),
229                &mut None,
230            );
231        assert!(effects.status().is_ok());
232        store.commit_objects(inner_temp_store);
233        effects
234    }
235
236    pub(crate) async fn build_checkpoints(
237        &self,
238        transactions: Vec<Transaction>,
239        mut all_effects: BTreeMap<TransactionDigest, TransactionEffects>,
240        checkpoint_size: usize,
241    ) -> Vec<(VerifiedCheckpoint, VerifiedCheckpointContents)> {
242        let mut builder = MockCheckpointBuilder::new(
243            self.get_validator()
244                .get_checkpoint_store()
245                .get_latest_certified_checkpoint()
246                .unwrap()
247                .unwrap(),
248        );
249        let mut checkpoints = vec![];
250        for transaction in transactions {
251            let effects = all_effects.remove(transaction.digest()).unwrap();
252            builder.push_transaction(VerifiedTransaction::new_unchecked(transaction), effects);
253            if builder.size() == checkpoint_size {
254                let (checkpoint, _, full_contents) = builder.build(self, 0);
255                checkpoints.push((checkpoint, full_contents));
256            }
257        }
258        if builder.size() > 0 {
259            let (checkpoint, _, full_contents) = builder.build(self, 0);
260            checkpoints.push((checkpoint, full_contents));
261        }
262        checkpoints
263    }
264
265    pub fn create_checkpoint_executor(&self) -> CheckpointExecutor {
266        let validator = self.get_validator();
267        CheckpointExecutor::new_for_tests(
268            self.epoch_store.clone(),
269            validator.get_checkpoint_store().clone(),
270            validator.clone(),
271            Arc::new(GlobalStateHasher::new_for_tests(
272                validator.get_global_state_hash_store().clone(),
273            )),
274        )
275    }
276
277    pub(crate) fn create_in_memory_store(&self) -> InMemoryObjectStore {
278        let objects: HashMap<_, _> = self
279            .get_validator()
280            .get_global_state_hash_store()
281            .iter_cached_live_object_set_for_testing(false)
282            .map(|o| match o {
283                LiveObject::Normal(object) => (object.id(), object),
284                LiveObject::Wrapped(_) => unreachable!(),
285            })
286            .collect();
287        InMemoryObjectStore::new(objects)
288    }
289
290    pub(crate) async fn assigned_shared_object_versions(
291        &self,
292        transactions: &[Transaction],
293    ) -> AssignedTxAndVersions {
294        let executables: Vec<_> = transactions
295            .iter()
296            .map(|tx| self.create_executable(tx.clone()))
297            .collect();
298        let assignables: Vec<_> = executables.iter().map(Schedulable::Transaction).collect();
299        self.epoch_store
300            .assign_shared_object_versions_idempotent(
301                self.get_validator().get_object_cache_reader().as_ref(),
302                assignables.iter(),
303            )
304            .unwrap()
305    }
306}
307
308impl ValidatorKeypairProvider for SingleValidator {
309    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
310        assert_eq!(name, &self.get_validator().name);
311        &*self.get_validator().secret
312    }
313
314    fn get_committee(&self) -> &Committee {
315        self.epoch_store.committee().as_ref()
316    }
317}