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