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