sui_core/authority/
authority_test_utils.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use fastcrypto::hash::MultisetHash;
6use fastcrypto::traits::KeyPair;
7use sui_types::base_types::FullObjectRef;
8use sui_types::crypto::{AccountKeyPair, AuthorityKeyPair};
9use sui_types::utils::to_sender_signed_transaction;
10
11use super::shared_object_version_manager::AssignedVersions;
12use super::test_authority_builder::TestAuthorityBuilder;
13use super::*;
14
15#[cfg(test)]
16use super::shared_object_version_manager::{AssignedTxAndVersions, Schedulable};
17
18// =============================================================================
19// MFP (Mysticeti Fast Path) Test Helpers
20//
21// The MFP transaction flow is:
22//   1. Client signs transaction and submits to a validator.
23//   2. The validator validates transaction and submits it to consensus.
24//   3. Consensus finalizes the transaction and outputs it in a commit.
25//   4. Transactions in the commit are filtered, sequenced and processed. Then they are sent to execution.
26//
27// =============================================================================
28
29/// Validates a transaction.
30/// This is the MFP "voting" phase - similar to what happens when a validator
31/// receives a transaction before submitting to consensus.
32///
33/// Returns the verified transaction ready for consensus submission.
34pub fn vote_transaction(
35    authority: &AuthorityState,
36    transaction: Transaction,
37) -> Result<VerifiedTransaction, SuiError> {
38    let epoch_store = authority.load_epoch_store_one_call_per_task();
39    transaction.validity_check(&epoch_store.tx_validity_check_context())?;
40    let verified_tx = epoch_store
41        .verify_transaction_require_no_aliases(transaction)?
42        .into_tx();
43
44    // Validate the transaction.
45    authority.handle_vote_transaction(&epoch_store, verified_tx.clone())?;
46
47    Ok(verified_tx)
48}
49
50/// Creates a VerifiedExecutableTransaction from a signed transaction.
51/// This validates the transaction, votes on it, and creates an executable
52/// as if it came out of consensus.
53pub fn create_executable_transaction(
54    authority: &AuthorityState,
55    transaction: Transaction,
56) -> Result<VerifiedExecutableTransaction, SuiError> {
57    let epoch_store = authority.load_epoch_store_one_call_per_task();
58    let verified_tx = vote_transaction(authority, transaction)?;
59    Ok(VerifiedExecutableTransaction::new_from_consensus(
60        verified_tx,
61        epoch_store.epoch(),
62    ))
63}
64
65/// Submits a transaction to consensus for ordering and version assignment.
66/// This only simulates the consensus submission process by assigning versions
67/// to shared objects.
68///
69/// Returns the executable transaction (now certified by consensus) and assigned versions.
70/// The transaction is NOT automatically executed - use `execute_from_consensus` for that.
71pub async fn submit_to_consensus(
72    authority: &AuthorityState,
73    transaction: Transaction,
74) -> Result<(VerifiedExecutableTransaction, AssignedVersions), SuiError> {
75    let epoch_store = authority.load_epoch_store_one_call_per_task();
76
77    // First validate and vote
78    let verified_tx = vote_transaction(authority, transaction)?;
79
80    // Create executable - the transaction is now "certified" by consensus
81    let executable =
82        VerifiedExecutableTransaction::new_from_consensus(verified_tx, epoch_store.epoch());
83
84    // Assign shared object versions
85    let assigned_versions = authority
86        .epoch_store_for_testing()
87        .assign_shared_object_versions_for_tests(
88            authority.get_object_cache_reader().as_ref(),
89            std::slice::from_ref(&executable.clone()),
90        )?;
91
92    let versions = assigned_versions
93        .into_map()
94        .get(&executable.key())
95        .cloned()
96        .unwrap_or_else(|| AssignedVersions::new(vec![], None));
97
98    Ok((executable, versions))
99}
100
101/// Executes a transaction that has already been sequenced through consensus.
102pub async fn execute_from_consensus(
103    authority: &AuthorityState,
104    executable: VerifiedExecutableTransaction,
105    assigned_versions: AssignedVersions,
106) -> (TransactionEffects, Option<ExecutionError>) {
107    let env = ExecutionEnv::new().with_assigned_versions(assigned_versions);
108    authority.execution_scheduler.enqueue(
109        vec![(executable.clone().into(), env.clone())],
110        &authority.epoch_store_for_testing(),
111    );
112
113    let (result, execution_error_opt) = authority
114        .try_execute_executable_for_test(&executable, env)
115        .await;
116    let effects = result.inner().data().clone();
117    (effects, execution_error_opt)
118}
119
120/// This is the primary test helper for executing transactions end-to-end.
121///
122/// Returns the executable transaction and signed effects.
123pub async fn submit_and_execute(
124    authority: &AuthorityState,
125    transaction: Transaction,
126) -> Result<(VerifiedExecutableTransaction, SignedTransactionEffects), SuiError> {
127    submit_and_execute_with_options(authority, None, transaction, false).await
128}
129
130/// Options:
131/// - `fullnode`: Optionally sync and execute on a fullnode as well
132/// - `with_shared`: Whether the transaction involves shared objects (triggers version assignment)
133pub async fn submit_and_execute_with_options(
134    authority: &AuthorityState,
135    fullnode: Option<&AuthorityState>,
136    transaction: Transaction,
137    with_shared: bool,
138) -> Result<(VerifiedExecutableTransaction, SignedTransactionEffects), SuiError> {
139    let (exec, effects, _) =
140        submit_and_execute_with_error(authority, fullnode, transaction, with_shared).await?;
141    Ok((exec, effects))
142}
143
144/// Complete MFP flow returning execution error if any.
145pub async fn submit_and_execute_with_error(
146    authority: &AuthorityState,
147    fullnode: Option<&AuthorityState>,
148    transaction: Transaction,
149    with_shared: bool,
150) -> Result<
151    (
152        VerifiedExecutableTransaction,
153        SignedTransactionEffects,
154        Option<ExecutionError>,
155    ),
156    SuiError,
157> {
158    let epoch_store = authority.load_epoch_store_one_call_per_task();
159
160    // Vote on the transaction.
161    let verified_tx = vote_transaction(authority, transaction)?;
162
163    // Create executable - transaction is now certified by consensus
164    let executable =
165        VerifiedExecutableTransaction::new_from_consensus(verified_tx, epoch_store.epoch());
166
167    // Assign shared object versions if needed
168    let assigned_versions = if with_shared {
169        let versions = authority
170            .epoch_store_for_testing()
171            .assign_shared_object_versions_for_tests(
172                authority.get_object_cache_reader().as_ref(),
173                std::slice::from_ref(&executable.clone()),
174            )?;
175        versions
176            .into_map()
177            .get(&executable.key())
178            .cloned()
179            .unwrap_or_else(|| AssignedVersions::new(vec![], None))
180    } else {
181        AssignedVersions::new(vec![], None)
182    };
183
184    // State accumulator for validation
185    let state_acc =
186        GlobalStateHasher::new_for_tests(authority.get_global_state_hash_store().clone());
187    let include_wrapped_tombstone = !authority
188        .epoch_store_for_testing()
189        .protocol_config()
190        .simplified_unwrap_then_delete();
191    let mut state =
192        state_acc.accumulate_cached_live_object_set_for_testing(include_wrapped_tombstone);
193
194    // Execute
195    let env = ExecutionEnv::new().with_assigned_versions(assigned_versions.clone());
196    let (result, execution_error_opt) = authority
197        .try_execute_executable_for_test(&executable, env.clone())
198        .await;
199
200    // Validate state accumulation
201    let state_after =
202        state_acc.accumulate_cached_live_object_set_for_testing(include_wrapped_tombstone);
203    let effects_acc = state_acc.accumulate_effects(
204        &[result.inner().data().clone()],
205        epoch_store.protocol_config(),
206    );
207    state.union(&effects_acc);
208    assert_eq!(state_after.digest(), state.digest());
209
210    // Execute on fullnode if provided
211    if let Some(fullnode) = fullnode {
212        fullnode
213            .try_execute_executable_for_test(&executable, env)
214            .await;
215    }
216
217    Ok((executable, result.into_inner(), execution_error_opt))
218}
219
220/// Enqueues multiple transactions for execution after they've been through consensus.
221pub async fn enqueue_and_execute_all(
222    authority: &AuthorityState,
223    executables: Vec<(VerifiedExecutableTransaction, ExecutionEnv)>,
224) -> Result<Vec<TransactionEffects>, SuiError> {
225    authority.execution_scheduler.enqueue(
226        executables
227            .iter()
228            .map(|(exec, env)| (exec.clone().into(), env.clone()))
229            .collect(),
230        &authority.epoch_store_for_testing(),
231    );
232    let mut output = Vec::new();
233    for (exec, _) in executables {
234        let effects = authority
235            .notify_read_effects_for_testing("", *exec.digest())
236            .await;
237        output.push(effects);
238    }
239    Ok(output)
240}
241
242/// Submits a transaction to consensus and schedules for execution.
243/// Returns assigned versions. Execution happens asynchronously.
244pub async fn submit_and_schedule(
245    authority: &AuthorityState,
246    transaction: Transaction,
247) -> Result<AssignedVersions, SuiError> {
248    let (executable, versions) = submit_to_consensus(authority, transaction).await?;
249
250    let env = ExecutionEnv::new().with_assigned_versions(versions.clone());
251    authority.execution_scheduler().enqueue_transactions(
252        vec![(executable, env)],
253        &authority.epoch_store_for_testing(),
254    );
255
256    Ok(versions)
257}
258
259pub async fn init_state_validator_with_fullnode() -> (Arc<AuthorityState>, Arc<AuthorityState>) {
260    use sui_types::crypto::get_authority_key_pair;
261
262    let validator = TestAuthorityBuilder::new().build().await;
263    let fullnode_key_pair = get_authority_key_pair().1;
264    let fullnode = TestAuthorityBuilder::new()
265        .with_keypair(&fullnode_key_pair)
266        .build()
267        .await;
268    (validator, fullnode)
269}
270
271pub async fn init_state_with_committee(
272    genesis: &Genesis,
273    authority_key: &AuthorityKeyPair,
274) -> Arc<AuthorityState> {
275    TestAuthorityBuilder::new()
276        .with_genesis_and_keypair(genesis, authority_key)
277        .build()
278        .await
279}
280
281pub async fn init_state_with_ids<I: IntoIterator<Item = (SuiAddress, ObjectID)>>(
282    objects: I,
283) -> Arc<AuthorityState> {
284    let state = TestAuthorityBuilder::new().build().await;
285    for (address, object_id) in objects {
286        let obj = Object::with_id_owner_for_testing(object_id, address);
287        state.insert_genesis_object(obj).await;
288    }
289    state
290}
291
292pub async fn init_state_with_ids_and_versions<
293    I: IntoIterator<Item = (SuiAddress, ObjectID, SequenceNumber)>,
294>(
295    objects: I,
296) -> Arc<AuthorityState> {
297    let state = TestAuthorityBuilder::new().build().await;
298    for (address, object_id, version) in objects {
299        let obj = Object::with_id_owner_version_for_testing(
300            object_id,
301            version,
302            Owner::AddressOwner(address),
303        );
304        state.insert_genesis_object(obj).await;
305    }
306    state
307}
308
309pub async fn init_state_with_objects<I: IntoIterator<Item = Object>>(
310    objects: I,
311) -> Arc<AuthorityState> {
312    let dir = tempfile::TempDir::new().unwrap();
313    let network_config = sui_swarm_config::network_config_builder::ConfigBuilder::new(&dir).build();
314    let genesis = network_config.genesis;
315    let keypair = network_config.validator_configs[0]
316        .protocol_key_pair()
317        .copy();
318    init_state_with_objects_and_committee(objects, &genesis, &keypair).await
319}
320
321pub async fn init_state_with_objects_and_committee<I: IntoIterator<Item = Object>>(
322    objects: I,
323    genesis: &Genesis,
324    authority_key: &AuthorityKeyPair,
325) -> Arc<AuthorityState> {
326    let state = init_state_with_committee(genesis, authority_key).await;
327    for o in objects {
328        state.insert_genesis_object(o).await;
329    }
330    state
331}
332
333pub async fn init_state_with_object_id(
334    address: SuiAddress,
335    object: ObjectID,
336) -> Arc<AuthorityState> {
337    init_state_with_ids(std::iter::once((address, object))).await
338}
339
340pub async fn init_state_with_ids_and_expensive_checks<
341    I: IntoIterator<Item = (SuiAddress, ObjectID)>,
342>(
343    objects: I,
344    config: ExpensiveSafetyCheckConfig,
345) -> Arc<AuthorityState> {
346    let state = TestAuthorityBuilder::new()
347        .with_expensive_safety_checks(config)
348        .build()
349        .await;
350    for (address, object_id) in objects {
351        let obj = Object::with_id_owner_for_testing(object_id, address);
352        state.insert_genesis_object(obj).await;
353    }
354    state
355}
356
357pub fn init_transfer_transaction(
358    authority_state: &AuthorityState,
359    sender: SuiAddress,
360    secret: &AccountKeyPair,
361    recipient: SuiAddress,
362    object_ref: ObjectRef,
363    gas_object_ref: ObjectRef,
364    gas_budget: u64,
365    gas_price: u64,
366) -> VerifiedTransaction {
367    let data = TransactionData::new_transfer(
368        recipient,
369        FullObjectRef::from_fastpath_ref(object_ref),
370        sender,
371        gas_object_ref,
372        gas_budget,
373        gas_price,
374    );
375    let tx = to_sender_signed_transaction(data, secret);
376    authority_state
377        .epoch_store_for_testing()
378        .verify_transaction_require_no_aliases(tx)
379        .unwrap()
380        .into_tx()
381}
382
383#[cfg(test)]
384pub async fn submit_batch_to_consensus<C>(
385    authority: &AuthorityState,
386    transactions: &[Transaction],
387    consensus_handler: &mut crate::consensus_handler::ConsensusHandler<C>,
388    captured_transactions: &crate::consensus_test_utils::CapturedTransactions,
389) -> (Vec<Schedulable>, AssignedTxAndVersions)
390where
391    C: crate::checkpoints::CheckpointServiceNotify + Send + Sync + 'static,
392{
393    use crate::consensus_test_utils::TestConsensusCommit;
394    use sui_types::messages_consensus::ConsensusTransaction;
395    use sui_types::transaction::PlainTransactionWithClaims;
396
397    let consensus_transactions: Vec<ConsensusTransaction> = transactions
398        .iter()
399        .map(|tx| {
400            ConsensusTransaction::new_user_transaction_v2_message(
401                &authority.name,
402                PlainTransactionWithClaims::no_aliases(tx.clone()),
403            )
404        })
405        .collect();
406
407    let epoch_store = authority.epoch_store_for_testing();
408    let round = epoch_store.get_highest_pending_checkpoint_height() + 1;
409    let timestamp_ms = epoch_store.epoch_start_state().epoch_start_timestamp_ms();
410    let sub_dag_index = 0;
411
412    let commit =
413        TestConsensusCommit::new(consensus_transactions, round, timestamp_ms, sub_dag_index);
414
415    consensus_handler
416        .handle_consensus_commit_for_test(commit)
417        .await;
418
419    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
420
421    let (scheduled_txns, assigned_tx_and_versions) = {
422        let mut captured = captured_transactions.lock();
423        assert!(
424            !captured.is_empty(),
425            "Expected transactions to be scheduled"
426        );
427        let (scheduled_txns, assigned_tx_and_versions, _) = captured.remove(0);
428        (scheduled_txns, assigned_tx_and_versions)
429    };
430
431    (scheduled_txns, assigned_tx_and_versions)
432}
433
434pub async fn assign_versions_and_schedule(
435    authority: &AuthorityState,
436    executable: &VerifiedExecutableTransaction,
437) -> AssignedVersions {
438    let assigned_versions = authority
439        .epoch_store_for_testing()
440        .assign_shared_object_versions_for_tests(
441            authority.get_object_cache_reader().as_ref(),
442            std::slice::from_ref(&executable.clone()),
443        )
444        .unwrap();
445
446    let versions = assigned_versions
447        .into_map()
448        .get(&executable.key())
449        .cloned()
450        .unwrap_or_else(|| AssignedVersions::new(vec![], None));
451
452    let env = ExecutionEnv::new().with_assigned_versions(versions.clone());
453    authority.execution_scheduler().enqueue_transactions(
454        vec![(executable.clone(), env)],
455        &authority.epoch_store_for_testing(),
456    );
457
458    versions
459}
460
461/// Assigns shared object versions for an executable without scheduling for execution.
462/// This is used when you need version assignment but want to control execution separately.
463pub async fn assign_shared_object_versions(
464    authority: &AuthorityState,
465    executable: &VerifiedExecutableTransaction,
466) -> AssignedVersions {
467    let assigned_versions = authority
468        .epoch_store_for_testing()
469        .assign_shared_object_versions_for_tests(
470            authority.get_object_cache_reader().as_ref(),
471            std::slice::from_ref(&executable.clone()),
472        )
473        .unwrap();
474
475    assigned_versions
476        .into_map()
477        .get(&executable.key())
478        .cloned()
479        .unwrap_or_else(|| AssignedVersions::new(vec![], None))
480}