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